summaryrefslogtreecommitdiff
path: root/chapter29/src/test
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-11-05 15:51:41 -0800
committerCliff Click <cliffc@acm.org>2026-11-05 15:51:41 -0800
commit8f460340cfe01a4945635251bcd33b257b699bf7 (patch)
treefb6336272df6a1379e4195fc5ab6ea0c8e165458 /chapter29/src/test
parent8d92810d9a2545d851d5986715a98fda24bb3c3a (diff)
downloadSimple-8f460340cfe01a4945635251bcd33b257b699bf7.tar.gz
Simple-8f460340cfe01a4945635251bcd33b257b699bf7.zip
Remove a bunch of extra graph walks pre-Optoescape-flowch29-cleanup
Removed most; moved a few into Opto setup pass. Lots of modest bug fixes
Diffstat (limited to 'chapter29/src/test')
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/ArithNodeTest.java3
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/CallEndTest.java54
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/Chapter10Test.java20
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/Chapter12Test.java15
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/Chapter17bTest.java12
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/Chapter19Test.java2
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/Chapter20Test.java1
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/ConstructionGroupTest.java40
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/ConstructorCompletionTest.java14
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/TypeTest.java20
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java25
11 files changed, 174 insertions, 32 deletions
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/ArithNodeTest.java b/chapter29/src/test/java/com/seaofnodes/simple/ArithNodeTest.java
index b28a6af3..1e6c6fc0 100644
--- a/chapter29/src/test/java/com/seaofnodes/simple/ArithNodeTest.java
+++ b/chapter29/src/test/java/com/seaofnodes/simple/ArithNodeTest.java
@@ -15,7 +15,8 @@ public class ArithNodeTest {
Node zero = ConstantNode.seed(TypeInteger.ZERO);
for( Node op : new Node[]{new MulNode(x,zero,(byte)1),new AndNode(null,x,zero),
new SubNode(x,x,(byte)1),new BoolNode.EQ(x,x,(byte)1),
- new BoolNode.NE(x,x,(byte)1),new BoolNode.ULT(x,x),new NotNode(x)} ) {
+ new BoolNode.NE(x,x,(byte)1),new BoolNode.ULT(x,x),new NotNode(x),
+ new ConvertNode(TypeInteger.BOT,x),new ToIntegerNode(x),new ToFloatNode(x),new PtrToIntNode(x)} ) {
assertSame(op.label(),TypeScalar.BOT,op.compute());
Node replacement = op.idealize();
assertTrue(op.label(),replacement==null || !replacement.compute().isConstant());
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/CallEndTest.java b/chapter29/src/test/java/com/seaofnodes/simple/CallEndTest.java
index a174bdf7..f57fd96a 100644
--- a/chapter29/src/test/java/com/seaofnodes/simple/CallEndTest.java
+++ b/chapter29/src/test/java/com/seaofnodes/simple/CallEndTest.java
@@ -7,6 +7,60 @@ import org.junit.Test;
import static org.junit.Assert.*;
public class CallEndTest {
+ // An unknown value can become a private object or callback. Both escape
+ // sinks must reserve those possibilities before the value is known.
+ @Test public void testUnknownEscapesRefineToPrivateValues() {
+ CodeGen code = new CodeGen("return 0;").parse();
+ code._phase = CodeGen.Phase.IterPostOpto;
+ StopCUNode stop = code.compunit()._stop;
+ stop._type = TypeTuple.make(Type.CONTROL,TypeMem.START,Type.BOTTOM);
+ ReturnNode ret = (ReturnNode)stop.in(0);
+ TypeFunPtr callback = TypeFunPtr.make1((byte)2,true,TypeFunPtr.TEMPTY,TypeInteger.BOT,100);
+ TypeMemPtr object = TypeMemPtr.make(TypeStruct.make("_Private",false,
+ Field.make("callback",callback,100,false)));
+ code.externFunc(101,"retain");
+ TypeFunPtr retain = TypeFunPtr.make1((byte)2,true,
+ new Type[]{TypeScalar.BOT},TypeInteger.BOT,101);
+ Node value = ConstantNode.raw(TypeScalar.BOT);
+ CallNode call = new CallNode(null,ConstantNode.raw(Type.CONTROL),
+ ConstantNode.raw(TypeMem.START),value,ConstantNode.raw(retain));
+ call._type = Type.CONTROL;
+ GlobalEscapeNode global = new GlobalEscapeNode();
+ global._type = TypeMem.START;
+ for( Type broad : new Type[]{Type.BOTTOM,TypeScalar.BOT} ) {
+ value._type = broad;
+ ret._type = TypeTuple.make(Type.CONTROL,TypeMem.START,broad);
+ TypeTuple unknownStop = stop.compute();
+ TypeMem unknownGlobal = global.compute();
+ assertSame(XInt.FULL,((TypeMem)unknownStop.mem())._escFs);
+ assertSame(XInt.FULL,((TypeMem)unknownStop.mem())._escAs);
+ assertSame(XInt.FULL,unknownGlobal._escFs);
+ assertSame(XInt.FULL,unknownGlobal._escAs);
+ for( Type precise : new Type[]{object,callback} ) {
+ assertTrue(precise.isa(broad));
+ value._type = precise;
+ ret._type = TypeTuple.make(Type.CONTROL,TypeMem.START,precise);
+ assertTrue(stop.compute().isa(unknownStop));
+ assertTrue(global.compute().isa(unknownGlobal));
+ }
+ }
+ }
+
+ @Test public void testUnresolvedOffsetInInlineBody() {
+ CodeGen code = new CodeGen("val f = { int[] data -> return data#; }; return f;").parse();
+ for( FunNode fun : code._linker )
+ if( fun!=null && "f".equals(fun._name) ) {
+ // Keep the offset unresolved: an early inline must copy it
+ // with its data parameter, not share it with the old body.
+ FldOffNode off = new FldOffNode(fun.parm(3),"<<",null).init();
+ fun.ret().setDef(2,off);
+ assertSame(Type.BOTTOM,off._type);
+ assertTrue("Unresolved field offset belongs to the function",fun.body().get(off._nid));
+ return;
+ }
+ fail("Missing test function");
+ }
+
@Test public void testReturnWideningMonotonicity() {
new CodeGen("return 0;").parse();
TypeFunPtr sig = TypeFunPtr.make1((byte)2,true,TypeFunPtr.TEMPTY,TypeInteger.I32,100);
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter29/src/test/java/com/seaofnodes/simple/Chapter10Test.java
index 1a954110..eadaf9fe 100644
--- a/chapter29/src/test/java/com/seaofnodes/simple/Chapter10Test.java
+++ b/chapter29/src/test/java/com/seaofnodes/simple/Chapter10Test.java
@@ -158,7 +158,7 @@ else
return v;
""");
code.driver(CodeGen.Phase.FinalizeIR);
- assertEquals("Stop[ return Test.Vector2D; return MemTop; return Test.Vector2D; ]", code.print());
+ assertEquals("Stop[ return Test.Vector2D; return Test.Vector2D; ]", code.print());
}
@Test
@@ -196,7 +196,7 @@ while (arg) {
return bar.a;
""");
code.driver(CodeGen.Phase.FinalizeIR);
- assertEquals("Stop[ return Phi(Loop,0,(Phi_a+2)); return MemTop; return Test.Bar; ]", code.print());
+ assertEquals("Stop[ return Phi(Loop,0,(Phi_a+2)); return Test.Bar; ]", code.print());
}
@Test
@@ -245,10 +245,10 @@ struct _Bar { int a; };
!_Bar? !bar = new _Bar;
if (arg) bar = null;
if( bar ) bar.a = 1;
-return bar.a;
+return bar;
""");
- code.driver(CodeGen.Phase.FinalizeIR);
- assertEquals("return Phi(Region,1,.a);", code.print());
+ code.driver(CodeGen.Phase.TypeCheck);
+ assertEquals("return Phi(Region,null,Test._Bar);", code.print());
}
@Test
@@ -318,7 +318,7 @@ while( i.x < i.len ) {
return sum;
""");
code.driver(CodeGen.Phase.FinalizeIR);
- assertEquals("Stop[ return Phi(Loop,0,(Phi(Loop,0,(Phi_x+1))+Phi_sum)); return MemTop; return Test.Iter; ]", code.print());
+ assertEquals("Stop[ return Phi(Loop,0,(Phi(Loop,0,(Phi_x+1))+Phi_sum)); return Test.Iter; ]", code.print());
}
@@ -337,7 +337,7 @@ while(arg) {
return ret;
""");
code.driver(CodeGen.Phase.FinalizeIR);
- assertEquals("Stop[ return Phi(Loop,Test.s0,Phi(Region,Test.s0,Phi_ret)); return MemTop; return Test.s0; ]", code.print());
+ assertEquals("Stop[ return Phi(Loop,Test.s0,Phi(Region,Test.s0,Phi_ret)); return Test.s0; ]", code.print());
}
@Test
@@ -390,7 +390,7 @@ s0 b = new s0();
return a.x * 100 + b.x * 10 + cnt;
""");
code.driver(CodeGen.Phase.FinalizeIR);
- assertEquals("Stop[ return 112; return MemTop; return Test.s0; ]", code.print());
+ assertEquals("Stop[ return 112; return Test.s0; ]", code.print());
}
@@ -433,7 +433,7 @@ else return new s0;
if(new s0.f0) return 0;
""");
code.driver(CodeGen.Phase.TypeCheck);
- assertEquals("Stop[ return Test.s0; return MemTop; return Test.s0; ]", code.print());
+ assertEquals("Stop[ return Test.s0; return Test.s0; ]", code.print());
}
@Test
@@ -461,7 +461,7 @@ s0 v1 = v0;
return v1;
""");
code.driver(CodeGen.Phase.FinalizeIR);
- assertEquals("Stop[ return (const)Test.s0; return MemTop; return Test.s0; ]", code.print());
+ assertEquals("Stop[ return (const)Test.s0; return Test.s0; ]", code.print());
}
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter29/src/test/java/com/seaofnodes/simple/Chapter12Test.java
index 156e091d..c6817308 100644
--- a/chapter29/src/test/java/com/seaofnodes/simple/Chapter12Test.java
+++ b/chapter29/src/test/java/com/seaofnodes/simple/Chapter12Test.java
@@ -79,6 +79,19 @@ return f0.i.f.i.i;
assertEquals("return 17;", code.print());
}
+ @Test public void testPrivateRecursiveReturn() {
+ CodeGen code = new CodeGen("""
+ struct _A { !Test._B? !b; };
+ struct _B { !_A? !a; };
+ !_A !_a = new _A;
+ !_B !_b = new _B;
+ _a.b = _b;
+ _b.a = _a;
+ return _a.b.a.b;
+ """).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("return Test._B;",code.print());
+ }
+
@Test
public void testNullRef0() {
CodeGen code = new CodeGen(
@@ -101,7 +114,7 @@ N n = new N(new M);
return n.next;
""");
code.driver(CodeGen.Phase.FinalizeIR);
- assertEquals("Stop[ return (const)Test.M; return MemTop; return Test.M; return MemTop; return MemTop; return Test.N; ]", code.print());
+ assertEquals("Stop[ return (const)Test.M; return Test.M; return Test.N; ]", code.print());
}
@Test
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/Chapter17bTest.java b/chapter29/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
index 1e99bcd9..98197b77 100644
--- a/chapter29/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
+++ b/chapter29/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
@@ -293,7 +293,9 @@ struct A { !B !b; new A = { ->
A x = new A;
""");
try { code.driver(CodeGen.Phase.TypeCheck); fail(); }
- catch( Exception e ) { assertEquals("Field 'b' might not be initialized",e.getMessage()); }
+ // The unused constructor return value can die; its unwritten field
+ // still fails the completion check at TypeCheck.
+ catch( Exception e ) { assertEquals("'Test.A' is not fully initialized, field 'b' is only partially set in the constructor",e.getMessage()); }
}
@Test
@@ -305,7 +307,7 @@ A x = new A;
"new A = { -> }; !B !b;", // Field declared later
} ) {
CodeGen code = new CodeGen("struct B {}; struct A { "+fields+" }; return 0;");
- try { code.parse(); fail(fields); }
+ try { code.driver(CodeGen.Phase.TypeCheck); fail(fields); }
catch( Parser.ParseException e ) { assertEquals("'Test.A' is not fully initialized, field 'b' is only partially set in the constructor",e.getMessage()); }
}
}
@@ -314,7 +316,7 @@ A x = new A;
public void testDefaultConstructorFields() {
for( String field : new String[] { "!B !b;", "!B !b;" } ) {
CodeGen code = new CodeGen("struct B {}; struct A { "+field+" }; return new A;");
- try { code.parse(); fail(field); }
+ try { code.driver(CodeGen.Phase.TypeCheck); fail(field); }
catch( Parser.ParseException e ) { assertEquals("'Test.A' is not fully initialized, field 'b' needs to be set in a constructor",e.getMessage()); }
}
for( String fields : new String[] {
@@ -550,7 +552,7 @@ struct _A {
};
return new _A;
""");
- code.driver(CodeGen.Phase.FinalizeIR);
+ code.driver(CodeGen.Phase.TypeCheck);
assertEquals("return Test._A;", code.print());
assertEquals("Test._A{f1=null,f2=null}", Eval2.eval(code,0));
}
@@ -565,7 +567,7 @@ struct _A {
};
return new _A(new !B?[0]).array_of_b;
""");
- code.driver(CodeGen.Phase.FinalizeIR);
+ code.driver(CodeGen.Phase.TypeCheck);
assertEquals("return []*B?;", code.print());
assertEquals("*B {}?[]", Eval2.eval(code,0));
}
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/Chapter19Test.java b/chapter29/src/test/java/com/seaofnodes/simple/Chapter19Test.java
index e6249d82..50ca8922 100644
--- a/chapter29/src/test/java/com/seaofnodes/simple/Chapter19Test.java
+++ b/chapter29/src/test/java/com/seaofnodes/simple/Chapter19Test.java
@@ -118,7 +118,7 @@ val hashCode = { !String self ->
hashCode(new String("Hello, World!"));
""";
CodeGen code = new CodeGen(src).driver(Phase.LocalSched);
- assertEquals("Stop[ return Phi(Region,123456789,(!=0)Phi(Loop,0,(.[]+((Phi_hash<<5)-Phi_hash)))); return MemTop; return MemTop; return Test.String; return Phi(Region,0,1,0,1); return Phi(Region,(!=0)._hashCode,Phi(Region,123456789,(!=0)Phi(Loop,0,(.[]+((Phi_hash<<5)-Phi_hash))))); ]", code.print());
+ assertEquals("Stop[ return Phi(Region,123456789,(!=0)Phi(Loop,0,(((Phi_hash<<5)-Phi_hash)+.[]))); return Test.String; return Phi(Region,0,1,0,1); return Phi(Region,(!=0)._hashCode,Phi(Region,123456789,(!=0)Phi(Loop,0,(((Phi_hash<<5)-Phi_hash)+.[])))); ]", code.print());
assertEquals("4029215624828139541", Eval2.eval(code, 2));
}
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/Chapter20Test.java b/chapter29/src/test/java/com/seaofnodes/simple/Chapter20Test.java
index 66a318ae..6cf92dc3 100644
--- a/chapter29/src/test/java/com/seaofnodes/simple/Chapter20Test.java
+++ b/chapter29/src/test/java/com/seaofnodes/simple/Chapter20Test.java
@@ -79,6 +79,7 @@ return f(s);
@Test public void testAllocatorUnion() { com.seaofnodes.simple.codegen.RegAllocTestSupport.union(); }
@Test public void testAllocatorCopyClobber() throws Exception { com.seaofnodes.simple.codegen.RegAllocTestSupport.copyClobber(); }
@Test public void testAllocatorCommutativePhi() { com.seaofnodes.simple.codegen.RegAllocTestSupport.commutativePhi(); }
+ @Test public void testAllocatorCommutativeLoop() { com.seaofnodes.simple.codegen.RegAllocTestSupport.commutativeLoop(); }
@Test public void testAllocatorKills() { com.seaofnodes.simple.codegen.RegAllocTestSupport.killWithoutResult(); }
@Test public void testAllocatorDependencies() { com.seaofnodes.simple.codegen.RegAllocTestSupport.nullUseMask(); }
@Test public void testAllocatorCloneClass() { com.seaofnodes.simple.codegen.RegAllocTestSupport.cloneRegisterClass(); }
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/ConstructionGroupTest.java b/chapter29/src/test/java/com/seaofnodes/simple/ConstructionGroupTest.java
index cea6ca91..b5bbfce8 100644
--- a/chapter29/src/test/java/com/seaofnodes/simple/ConstructionGroupTest.java
+++ b/chapter29/src/test/java/com/seaofnodes/simple/ConstructionGroupTest.java
@@ -21,7 +21,7 @@ public class ConstructionGroupTest {
private void rejects(String src, String message) {
try {
- new CodeGen(src).parse();
+ new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
fail("Accepted: "+src);
} catch( Parser.ParseException e ) {
assertTrue(e.getMessage(),e.getMessage().contains(message));
@@ -40,12 +40,25 @@ public class ConstructionGroupTest {
.driver(CodeGen.Phase.Encoding,TestC.CPU_PORT,TestC.CALL_CONVENTION,true,false,0);
}
- @Test public void testNoIncompleteLookupOrEscape() {
+ @Test public void testIncompleteChildReadAfterInlining() {
// This lookup supplies a live result. Inlining must not turn self
// complete inside the body before checking the use of child.
rejects(parent("child = new Child(self); payload = child.parent==null;")+
"return new Parent(arg).payload;","incomplete object");
- rejects(parent("payload = 1; child = new Child(self); int x = child.parent==null;")+"return 0;","incomplete object");
+ }
+
+ @Test public void testInitializerFeedsConstructor() {
+ // Private memory comes from the initializer's real call edge. An
+ // unknown-caller default must neither erase it nor poison its fields.
+ String src = "struct C { int !x = 7; new C = { -> x = x+1; }; }; return new C().x;";
+ for( long seed : new long[]{1,123,987654321} ) {
+ CodeGen code = new CodeGen(src,seed,true).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("8",Eval2.eval(code,0));
+ assertEquals("Stop[ return 8; return Test.C; ]",code.print());
+ }
+ }
+
+ @Test public void testNoIncompleteLookupOrEscape() {
rejects(parent("payload = 1; child = new Child(self); read(child);")+"return 0;","ordinary function");
rejects(parent("payload = 1; child = new Child(self);").replace("parent = par;","parent = par; value = par.payload;")+
"return 0;","incomplete object");
@@ -54,7 +67,7 @@ public class ConstructionGroupTest {
struct C { int~ x; new C = { -> box.value = self; x = 1; }; };
return 0;
""","ordinary object");
- rejects("struct C { C next = self; int~ x; new C = { -> int y = next.x; x = 7; }; }; return 0;",
+ rejects("struct C { C next = self; int~ x; new C = { -> x = next.x; }; }; return new C().x;",
"incomplete object");
}
@@ -115,14 +128,16 @@ public class ConstructionGroupTest {
@Test public void testDeclaredQualifierAndOwnReceiver() {
// A complete caller does not grant permission to read a ^ formal.
- rejects("""
+ for( String body : new String[]{"value = par.payload;", "value = par.payload*0;",
+ "int x = par.payload; value = x*0;", "value = par.payload==par.payload;"} )
+ rejects("""
struct Parent { int payload = 7; };
struct Child {
int value;
- new Child = { Parent ^par -> value = par.payload; };
+ new Child = { Parent ^par -> BODY };
};
return new Child(new Parent).value;
- ""","incomplete object");
+ """.replace("BODY",body),"incomplete object");
// The self exception belongs to the receiver, not every pointer of its class.
rejects("""
struct C {
@@ -132,4 +147,15 @@ public class ConstructionGroupTest {
return new C(new C(null)).value;
""","incomplete object");
}
+
+ @Test public void testUnusedIncompleteReadCanDie() {
+ String src = """
+ struct Parent { int payload = 7; };
+ struct Child { int value = 5;
+ new Child = { Parent ^par -> int unused = par.payload; };
+ };
+ return new Child(new Parent).value;
+ """;
+ assertEquals("5",Eval2.eval(new CodeGen(src).driver(CodeGen.Phase.TypeCheck),0));
+ }
}
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/ConstructorCompletionTest.java b/chapter29/src/test/java/com/seaofnodes/simple/ConstructorCompletionTest.java
index cbb9ee37..a5fc236b 100644
--- a/chapter29/src/test/java/com/seaofnodes/simple/ConstructorCompletionTest.java
+++ b/chapter29/src/test/java/com/seaofnodes/simple/ConstructorCompletionTest.java
@@ -30,10 +30,10 @@ public class ConstructorCompletionTest {
"int~ x; int y = x*0; new A = { -> x = 7; };",
// Constructor entry includes the initializer's early return.
// Its later Store of 2 must not excuse the earlier read of x.
- "int~ x; if(arg) return 0; x = 1; new A = { -> int y = x; x = 2; };"
+ "int~ x; int seen; if(arg) return 0; x = 1; new A = { -> seen = x; x = 2; };"
} )
try {
- new CodeGen("struct A { "+body+" }; return 0;").parse();
+ new CodeGen("struct A { "+body+" }; return 0;").driver(CodeGen.Phase.TypeCheck);
fail("Read a field before its constructor wrote it");
} catch( Parser.ParseException e ) {
assertTrue(e.getMessage(),e.getMessage().contains("might not be initialized"));
@@ -51,7 +51,7 @@ public class ConstructorCompletionTest {
private void rejects(String fields, String field, boolean explicit) {
String src = "struct B {}; struct A { "+fields+" }; return 0;";
try {
- new CodeGen(src).parse();
+ new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
fail(src);
} catch( Parser.ParseException e ) {
assertEquals("'Test.A' is not fully initialized, field '"+field+
@@ -95,8 +95,8 @@ public class ConstructorCompletionTest {
@Test public void testNonreturningPaths() {
// An initializer parsed in dead enclosing code can have TOP memory
// rather than a struct-shaped result.
- new CodeGen("while(false) { struct A { int x; }; } return 0;").parse();
- new CodeGen("struct A { int~ x; new A = { -> while(true) {} }; }; return 0;").parse();
+ new CodeGen("while(false) { struct A { int x; }; } return 0;").driver(CodeGen.Phase.TypeCheck);
+ new CodeGen("struct A { int~ x; new A = { -> while(true) {} }; }; return 0;").driver(CodeGen.Phase.TypeCheck);
rejects("int~ x; new A = { -> if(arg) return 0; while(true) {} };","x",true);
CodeGen code = new CodeGen("""
struct A {
@@ -113,7 +113,7 @@ public class ConstructorCompletionTest {
// Its Return lacks that field, so completion must consult <init> too.
rejects("val A = { -> }; B b;","b",true);
rejects("int~ x; val A = { -> if(arg) x = 1; };","x",true);
- new CodeGen("struct B {}; struct A { val A = { -> }; B b = new B; }; return 0;").parse();
- new CodeGen("struct A { val A = { -> while(true) {} }; int~ x; }; return 0;").parse();
+ new CodeGen("struct B {}; struct A { val A = { -> }; B b = new B; }; return 0;").driver(CodeGen.Phase.TypeCheck);
+ new CodeGen("struct A { val A = { -> while(true) {} }; int~ x; }; return 0;").driver(CodeGen.Phase.TypeCheck);
}
}
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/TypeTest.java b/chapter29/src/test/java/com/seaofnodes/simple/TypeTest.java
index 68239396..16b75fdc 100644
--- a/chapter29/src/test/java/com/seaofnodes/simple/TypeTest.java
+++ b/chapter29/src/test/java/com/seaofnodes/simple/TypeTest.java
@@ -7,6 +7,26 @@ import static org.junit.Assert.assertSame;
public class TypeTest {
+ @Test public void testOpaqueExternalEscapes() {
+ TypeMem defaults = TypeMem.START.makeFrom(XInt.make(2),XInt.make(3));
+ TypeMem prior = TypeMem.START.makeFrom(XInt.make(5),XInt.make(7));
+ TypeMemPtr opaque = TypeMemPtr.makeNullable(TypeStruct.forward("OpaqueExternal"));
+ TypeMem escaped = prior.escapesFrom(opaque,defaults);
+ assertSame(XInt.meet(prior._escFs,defaults._escFs),escaped._escFs);
+ assertSame(XInt.meet(prior._escAs,defaults._escAs),escaped._escAs);
+ // Before type closure the same name might still resolve locally.
+ assertSame(XInt.FULL,prior.escapesFrom(opaque,TypeMem.BOT)._escFs);
+ for( Type unknown : new Type[]{Type.BOTTOM,TypeScalar.BOT,TypePtr.PTR,
+ TypeMemPtr.make(TypeStruct.open("LocalStillOpen"))} ) {
+ TypeMem broad = prior.escapesFrom(unknown,defaults);
+ assertSame(XInt.FULL,broad._escFs);
+ assertSame(XInt.FULL,broad._escAs);
+ }
+ // Concrete private callbacks must not be restricted to public names.
+ TypeFunPtr callback = TypeFunPtr.make1((byte)2,true,TypeFunPtr.TEMPTY,TypeInteger.BOT,11);
+ assertSame(XInt.make(11),TypeMem.START.escapesFrom(callback,defaults)._escFs);
+ }
+
// Aggregate finality is carried by its fields. Canonicalizing the unused
// aggregate bit must commute with dual, even for named (non-high) structs.
@Test public void testAggregateMemoryDual() {
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java b/chapter29/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java
index 292f1ae7..adbb10a5 100644
--- a/chapter29/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java
+++ b/chapter29/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java
@@ -1,6 +1,7 @@
package com.seaofnodes.simple.codegen;
import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.node.cpus.x86_64_v2.SubX86;
import com.seaofnodes.simple.node.cpus.x86_64_v2.x86_64_v2;
import com.seaofnodes.simple.type.TypeInteger;
import static org.junit.Assert.*;
@@ -124,6 +125,30 @@ public class RegAllocTestSupport {
assertTrue(BuildLRG.run(0,new RegAlloc(code)));
}
+ public static void commutativeLoop() {
+ for( String op : new String[]{"+","*","^","&","|"} )
+ for( boolean reversed : new boolean[]{false,true} ) {
+ String src = "return { u8[] data -> int h = 1; for(int i = 0; i < data#; i++) " +
+ "h = ((h<<5)-h) "+op+" data[i]; return h; };";
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.LocalSched,"x86_64_v2","SystemV");
+ // Exercise both destructive operands regardless of ideal-node
+ // numbering. The shifted value overlaps the old loop hash.
+ int found = 0;
+ for( CFGNode bb : code._cfg )
+ for( Node n : bb.outs() )
+ if( n instanceof MachNode mach && mach.commutes() && mach.twoAddress()==1 &&
+ (n.in(1) instanceof SubX86 || n.in(2) instanceof SubX86) ) {
+ if( (n.in(1) instanceof SubX86)==reversed ) n.swap12();
+ found++;
+ }
+ assertEquals(op,1,found);
+ code.driver(CodeGen.Phase.RegAlloc,"x86_64_v2","SystemV");
+ checkRegisters(code);
+ assertEquals(op+" reversed="+reversed,8,code._regAlloc._spillScaled);
+ code.encode();
+ }
+ }
+
private static class CallOp extends CallNode implements MachNode {
CallOp(Node ctrl, Node def) { super(null,ctrl,null,def,def); }
@Override public RegMask regmap(int i) { return i==2 ? A : i==3 ? B : null; }