summaryrefslogtreecommitdiff
path: root/chapter29/src/test/java
diff options
context:
space:
mode:
Diffstat (limited to 'chapter29/src/test/java')
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/Chapter20Test.java23
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/Chapter21Test.java38
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/Chapter25Test.java66
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java22
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/EscapeFlowTest.java112
5 files changed, 215 insertions, 46 deletions
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/Chapter20Test.java b/chapter29/src/test/java/com/seaofnodes/simple/Chapter20Test.java
index 6cf92dc3..f5eb2b36 100644
--- a/chapter29/src/test/java/com/seaofnodes/simple/Chapter20Test.java
+++ b/chapter29/src/test/java/com/seaofnodes/simple/Chapter20Test.java
@@ -242,13 +242,6 @@ val equals = { !String !self, !String s ->
return true;
};
-// Return the String hashCode (cached, and never 0)
-val hashCode = { !String self ->
- self._hashCode
- ? self._hashCode
- : (self._hashCode = _hashCodeString(self));
-};
-
val _hashCodeString = { !String self ->
int hash=0;
for( int i=0; i< self.cs#; i++ )
@@ -256,11 +249,19 @@ val _hashCodeString = { !String self ->
if( !hash ) hash = 123456789;
return hash;
};
+
+// Return the String hashCode (cached, and never 0)
+val hashCode = { !String self ->
+ self._hashCode
+ ? self._hashCode
+ : (self._hashCode = _hashCodeString(self));
+};
+
""";
- // The implicit return exposes _hashCodeString, so retain its body too.
- testCPU(src,"x86_64_v2", "SystemV",21,null);
- testCPU(src,"riscv" , "SystemV",9,null);
- testCPU(src,"arm" , "SystemV",7,null);
+ // Return the public hashCode wrapper; keep its helper private.
+ testCPU(src,"x86_64_v2", "SystemV",12,null);
+ testCPU(src,"riscv" , "SystemV",8,null);
+ testCPU(src,"arm" , "SystemV",6,null);
}
@Test
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/chapter29/src/test/java/com/seaofnodes/simple/Chapter21Test.java
index f1dc54e2..e15c2581 100644
--- a/chapter29/src/test/java/com/seaofnodes/simple/Chapter21Test.java
+++ b/chapter29/src/test/java/com/seaofnodes/simple/Chapter21Test.java
@@ -368,13 +368,6 @@ val equals = { !String !self, !String s ->
return true;
};
-// Return the String hashCode (cached, and never 0)
-val hashCode = { !String self ->
- self._hashCode
- ? self._hashCode
- : (self._hashCode = _hashCodeString(self));
-};
-
val _hashCodeString = { !String self ->
int hash=0;
for( int i=0; i< self.cs#; i++ )
@@ -382,11 +375,18 @@ val _hashCodeString = { !String self ->
if( !hash ) hash = 123456789;
return hash;
};
+
+// Return the String hashCode (cached, and never 0)
+val hashCode = { !String self ->
+ self._hashCode
+ ? self._hashCode
+ : (self._hashCode = _hashCodeString(self));
+};
""";
- // The implicit return exposes _hashCodeString, so retain its body too.
- testCPU(src,"x86_64_v2", "SystemV",21,null);
- testCPU(src,"riscv" , "SystemV",9,null);
- testCPU(src,"arm" , "SystemV",7,null);
+ // Return the public hashCode wrapper; keep its helper private.
+ testCPU(src,"x86_64_v2", "SystemV",12,null);
+ testCPU(src,"riscv" , "SystemV",8,null);
+ testCPU(src,"arm" , "SystemV",6,null);
}
@Test public void testStringExport() throws IOException {
@@ -408,13 +408,6 @@ val equals = { !String !self, !String s ->
return true;
};
-// Return the String hashCode (cached, and never 0)
-val hashCode = { !String self ->
- self._hashCode
- ? self._hashCode
- : (self._hashCode = _hashCodeString(self));
-};
-
val _hashCodeString = { !String self ->
int hash=0;
for( int i=0; i< self.cs#; i++ )
@@ -422,8 +415,15 @@ val _hashCodeString = { !String self ->
if( !hash ) hash = 123456789;
return hash;
};
+
+// Return the String hashCode (cached, and never 0)
+val hashCode = { !String self ->
+ self._hashCode
+ ? self._hashCode
+ : (self._hashCode = _hashCodeString(self));
+};
""";
- TestC.runC(src, "stringHash", "",21);
+ TestC.runC(src, "stringHash", "",12);
}
@Test public void testLoop2() {
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/Chapter25Test.java b/chapter29/src/test/java/com/seaofnodes/simple/Chapter25Test.java
index f5eba8f5..5f615282 100644
--- a/chapter29/src/test/java/com/seaofnodes/simple/Chapter25Test.java
+++ b/chapter29/src/test/java/com/seaofnodes/simple/Chapter25Test.java
@@ -5,6 +5,7 @@ import com.seaofnodes.isa.eval.EvalRisc5;
import com.seaofnodes.isa.eval.EvalArm64;
import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.parser.Parser;
import com.seaofnodes.simple.codegen.RegAllocTestSupport.CheckedCodeGen;
import com.seaofnodes.simple.codegen.ElfReader;
import com.seaofnodes.simple.codegen.ParseAll;
@@ -12,6 +13,8 @@ import com.seaofnodes.simple.node.CallNode;
import com.seaofnodes.simple.node.FunNode;
import com.seaofnodes.simple.node.LoopNode;
import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.type.TypeMem;
+import com.seaofnodes.simple.type.TypeTuple;
import com.seaofnodes.simple.type.XInt;
import com.seaofnodes.simple.util.Ary;
import java.io.File;
@@ -73,21 +76,25 @@ public class Chapter25Test {
for( long seed : new long[]{1,123,987654321} ) {
CodeGen code = new CodeGen(src,seed,true).driver(CodeGen.Phase.Parse);
FunNode visible = null;
+ int[] privateFidxs = XInt.EMPTY;
int checked = 0;
for( FunNode fun : code._linker ) {
if( fun==null || fun._name==null ) continue;
if( fun._name.equals("visible") ) {
- assertTrue(code.publicFIDX(fun.sig().fidx()));
visible = fun;
checked++;
}
if( fun._name.equals("_hidden") || fun._name.equals("hiddenClassMethod") ) {
- assertFalse(code.publicFIDX(fun.sig().fidx()));
+ privateFidxs = XInt.make(privateFidxs,fun.sig().fidx());
checked++;
}
}
assertEquals(3,checked);
code.driver(CodeGen.Phase.TypeCheck);
+ TypeMem outside = ((TypeTuple)code._start._type).mem();
+ assertTrue(XInt.bit(outside._escFs,visible.sig().fidx()));
+ for( int fidx = XInt.next(privateFidxs,0); fidx>=0; fidx = XInt.next(privateFidxs,fidx) )
+ assertFalse(XInt.bit(outside._escFs,fidx));
assertTrue(visible.unknownCallers());
assertEquals(TypeInteger.U8,visible.parm(3)._type);
assertEquals("0",Eval2.eval(code,0));
@@ -101,25 +108,52 @@ public class Chapter25Test {
val _private = { int x -> x+1; };
return 0;
""").parse();
- int checked = 0;
+ int published = -1, unpublished = -1;
for( FunNode fun : code._linker ) {
if( fun==null || fun._name==null ) continue;
- if( fun._name.equals("_hidden") ) {
- // A public field can publish a privately named function.
- assertTrue(code.publicFIDX(fun.sig().fidx()));
- checked++;
- }
- if( fun._name.equals("_private") ) {
- assertFalse(code.publicFIDX(fun.sig().fidx()));
- checked++;
- }
+ if( fun._name.equals("_hidden") ) published = fun.sig().fidx();
+ if( fun._name.equals("_private") ) unpublished = fun.sig().fidx();
}
- assertEquals(2,checked);
+ assertTrue(published>=0 && unpublished>=0);
var clz = code.compunit()._clz;
- assertTrue(XInt.bit(code.publicAliases(),clz.field("visible")._alias));
- assertFalse(XInt.bit(code.publicAliases(),clz.field("_hidden")._alias));
- assertFalse(XInt.bit(code.publicAliases(),clz.field("_private")._alias));
code.driver(CodeGen.Phase.TypeCheck);
+ TypeMem outside = ((TypeTuple)code._start._type).mem();
+ // A public field can publish a privately named function.
+ assertTrue(XInt.bit(outside._escFs,published));
+ assertFalse(XInt.bit(outside._escFs,unpublished));
+ assertTrue(XInt.bit(outside._escAs,clz.field("visible")._alias));
+ assertFalse(XInt.bit(outside._escAs,clz.field("_hidden")._alias));
+ assertFalse(XInt.bit(outside._escAs,clz.field("_private")._alias));
+ }
+
+ @Test public void testImportedEscapeExclusions() throws IOException {
+ Path build=Path.of("build/objs");
+ Files.createDirectories(build);
+ Path dir=Files.createTempDirectory(build,"escape_exclusions_");
+ CodeGen library=new CodeGen(null,dir.toString(),null,"Lib","""
+ val _hidden_noInline={int n -> n+100;};
+ val apply_noInline={{int->int} f,int n -> f(n);};
+ return apply_noInline;
+ """,123,true,TypeInteger.BOT).driver("x86_64_v2","win64",false,false);
+ int savedHidden=((com.seaofnodes.simple.type.TypeFunPtr)
+ library.compunit()._clz.field("_hidden_noInline")._t).fidx();
+ // Allocate unrelated IDs before loading Lib so saved exclusions must
+ // remap by owner/slot, not by their numeric positions in the file.
+ CodeGen code=new CodeGen(null,dir.toString(),new Ary<>(new String[]{dir.toString()}),
+ "Client","""
+ val _other={int n -> n-1;};
+ val inc={int n -> n+1;};
+ return Lib.apply_noInline(inc,arg);
+ """,987654321,true,TypeInteger.BOT).driver(CodeGen.Phase.TypeCheck);
+ var clz=(com.seaofnodes.simple.type.TypeStruct)Parser.TYPES.get("class:Lib");
+ int hidden=((com.seaofnodes.simple.type.TypeFunPtr)clz.field("_hidden_noInline")._t).fidx();
+ assertNotEquals(savedHidden,hidden);
+ TypeMem outside=((TypeTuple)code._start._type).mem();
+ assertTrue(XInt.isHigh(outside._escFs));
+ assertFalse(XInt.bit(outside._escFs,hidden));
+ assertEquals("42",Eval2.eval(code,41));
+ Files.delete(dir.resolve("Lib.o"));
+ Files.delete(dir);
}
@Test public void testNeverReturnPointerRiscV() {
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java b/chapter29/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java
index 88ebb6b7..dae64fb0 100644
--- a/chapter29/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java
+++ b/chapter29/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java
@@ -158,6 +158,28 @@ public class ClassInitDependenciesTest {
assertTrue(touches(code,deps,"Root/B","Root"));
}
+ @Test public void testUnknownCallbackCycle() throws Exception {
+ for( long seed : new long[]{1,123,987654321} ) {
+ try {
+ compile("A",Map.of(
+ "A", """
+ { {int->int} -> {int->int} } bounce="C";
+ val callback_noInline={int n -> B.value;};
+ val f=bounce(callback_noInline);
+ int value=f(1);
+ """,
+ "B", "int value=A.value;"),seed);
+ fail("Expected callback initialization cycle");
+ } catch( Parser.ParseException ex ) {
+ String message=ex.getMessage();
+ assertTrue(message,message.startsWith("Cyclic class initialization:"));
+ assertTrue(message,message.contains(" -> unknown call"));
+ assertTrue(message,message.contains("unknown call -> "));
+ assertTrue(message,message.contains("Root.B -> Root.A"));
+ }
+ }
+ }
+
@Test public void testCycles() throws Exception {
cycle("A",Map.of("A","val get_noInline={ ->\n B.value;\n};\nint value=get_noInline();",
"B","int value=A.value;"),"A -> B -> A");
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/EscapeFlowTest.java b/chapter29/src/test/java/com/seaofnodes/simple/EscapeFlowTest.java
new file mode 100644
index 00000000..71a281b5
--- /dev/null
+++ b/chapter29/src/test/java/com/seaofnodes/simple/EscapeFlowTest.java
@@ -0,0 +1,112 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class EscapeFlowTest {
+ // A cofinite outside set must not revive private allocation helpers.
+ @Test public void testOpaqueRoundTrip() {
+ for( long seed : new long[]{1,123,987654321} ) {
+ CodeGen code = new CodeGen("""
+ struct _A { B? !b; new _A = { B? p -> b=p; }; };
+ val wrap = { B? b -> return new _A(b); };
+ return wrap;
+ """,seed,true).driver(CodeGen.Phase.TypeCheck);
+ TypeMem outside = ((TypeTuple)code._start._type).mem();
+ assertTrue(XInt.isHigh(outside._escFs));
+ assertNotSame(XInt.FULL,outside._escFs);
+ for( FunNode fun : code._linker )
+ if( fun!=null && fun._name!=null && fun._name.contains("_A.") )
+ assertFalse(fun._name,fun.unknownCallers());
+ }
+ }
+
+ // Foreign callbacks remain possible. The default callback argument reads
+ // Start memory rather than a frozen declaration constant.
+ @Test public void testUnknownCallerCallback() {
+ CodeGen code = new CodeGen("""
+ val _hidden_noInline = { int n -> return n+2; };
+ val apply_noInline = { {int->int} f,int n -> return f(n); };
+ return apply_noInline;
+ """).parse();
+ TypeMemPtr self = (TypeMemPtr)named(code,"apply_noInline").parm(2).in(1)._type;
+ assertTrue(self._one);
+ assertEquals("class:Test",self._obj._name);
+ code.driver(CodeGen.Phase.TypeCheck);
+ FunNode apply = named(code,"apply_noInline");
+ assertTrue(apply.unknownCallers());
+ ParmNode callback = apply.parm(3);
+ assertTrue(callback.in(1) instanceof ProjNode);
+ TypeFunPtr type = (TypeFunPtr)callback._type;
+ assertTrue(XInt.isHigh(type.fidxs()));
+ assertSame(TypeInteger.BOT,apply.ret().expr()._type);
+ // Only <clinit> and apply escape; forwarding #self must not expose _hidden.
+ assertEquals("Stop[ return { apply_noInline}; return #2; ]",code.print());
+ }
+
+ @Test public void testNativeCallbackRoundTrip() {
+ CodeGen code = new CodeGen("""
+ val _callback_noInline = { int n -> return n+1; };
+ { {int->int} -> {int->int} } bounce = "C";
+ return bounce(_callback_noInline)(arg);
+ """).driver(CodeGen.Phase.TypeCheck);
+ FunNode callback = named(code,"_callback_noInline");
+ assertTrue(callback.unknownCallers());
+ assertTrue(XInt.bit(((TypeTuple)code._start._type).mem()._escFs,callback.sig().fidx()));
+ }
+
+ // Call remains CONTROL. Changing only the outside set must wake CallEnd
+ // and extend the possible callback returned by the native factory.
+ @Test public void testOutsideReturnDependency() {
+ CodeGen code = new CodeGen("return 0;").parse();
+ code._phase = CodeGen.Phase.Opto;
+ code.externFunc(100,"factory");
+ TypeFunPtr callback = TypeFunPtr.make(false,TypeFunPtr.TEMPTY,TypeInteger.BOT);
+ TypeFunPtr factory = TypeFunPtr.make1((byte)2,true,TypeFunPtr.TEMPTY,callback,100);
+ CallNode call = new CallNode(null,ConstantNode.raw(Type.CONTROL),
+ ConstantNode.raw(TypeMem.START),ConstantNode.raw(factory)).init();
+ CallEndNode end = new CallEndNode(call,TypeRPC.BOT);
+ int[] foreign = XInt.dual(XInt.make(7));
+ code._start._type = TypeTuple.make(Type.CONTROL,TypeMem.START.makeFrom(foreign,XInt.EMPTY),TypeInteger.BOT);
+ TypeTuple before = (TypeTuple)end.compute();
+ assertFalse(XInt.bit(((TypeFunPtr)before.ret()).fidxs(),7));
+ boolean depends = false;
+ for( int i = 0; i<code._start.nDeps(); i++ ) depends |= code._start.dep(i)==end;
+ assertTrue(depends);
+ code._start._type = TypeTuple.make(Type.CONTROL,TypeMem.START.makeFrom(XInt.FULL,XInt.EMPTY),TypeInteger.BOT);
+ TypeTuple after = (TypeTuple)end.compute();
+ assertTrue(before.isa(after));
+ assertTrue(XInt.bit(((TypeFunPtr)after.ret()).fidxs(),7));
+ }
+
+ // Public construction exposes the allocator; private-memory helpers
+ // remain internal and can inline into it.
+ @Test public void testPublicAllocator() {
+ CodeGen code = new CodeGen("""
+ struct Box { int n; new Box = { int x -> n=x; }; };
+ return new Box(arg).n;
+ """).parse();
+ FunNode allocator = named(code,"Test.Box.<new>");
+ int[] helpers = XInt.EMPTY;
+ for( FunNode fun : code._linker )
+ if( fun!=null && fun.sig().ret() instanceof TypeMem )
+ helpers = XInt.make(helpers,fun.sig().fidx());
+ assertNotSame(XInt.EMPTY,helpers);
+ code.driver(CodeGen.Phase.TypeCheck);
+ TypeMem outside = ((TypeTuple)code._start._type).mem();
+ assertTrue(allocator.unknownCallers());
+ assertTrue(XInt.bit(outside._escFs,allocator.sig().fidx()));
+ for( int fidx = XInt.next(helpers,0); fidx>=0; fidx = XInt.next(helpers,fidx) )
+ assertFalse(XInt.bit(outside._escFs,fidx));
+ assertEquals("42",Eval2.eval(code,42));
+ }
+
+ private static FunNode named(CodeGen code, String name) {
+ for( FunNode fun : code._linker )
+ if( fun!=null && name.equals(fun._name) ) return fun;
+ throw new AssertionError("Missing function "+name);
+ }
+}