diff options
| author | Cliff Click <cliffc@acm.org> | 2026-11-05 15:51:41 -0800 |
|---|---|---|
| committer | Cliff Click <cliffc@acm.org> | 2026-11-05 15:51:41 -0800 |
| commit | 8f460340cfe01a4945635251bcd33b257b699bf7 (patch) | |
| tree | fb6336272df6a1379e4195fc5ab6ea0c8e165458 /chapter29/src/test | |
| parent | 8d92810d9a2545d851d5986715a98fda24bb3c3a (diff) | |
| download | Simple-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')
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; } |
