summaryrefslogtreecommitdiff
path: root/chapter28a/src/test/java/com
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-11-06 16:06:02 -0800
committerCliff Click <cliffc@acm.org>2026-11-06 16:06:02 -0800
commitce3b04603d7af995004291d9a7eb668f313e6772 (patch)
tree45d3b45026c54e0a35aa3bd37069c7cccb0729bf /chapter28a/src/test/java/com
parent8f460340cfe01a4945635251bcd33b257b699bf7 (diff)
downloadSimple-ce3b04603d7af995004291d9a7eb668f313e6772.tar.gz
Simple-ce3b04603d7af995004291d9a7eb668f313e6772.zip
Cleanup public escapes
Drop "frozen" public escapes, and recompute in Start/Stop so Opto gets a precise escape set. Rename XInt.free -> XInt.alloc Fix some String tests to not escape by default
Diffstat (limited to 'chapter28a/src/test/java/com')
-rw-r--r--chapter28a/src/test/java/com/seaofnodes/simple/Chapter10Test.java20
-rw-r--r--chapter28a/src/test/java/com/seaofnodes/simple/Chapter12Test.java2
-rw-r--r--chapter28a/src/test/java/com/seaofnodes/simple/Chapter18Test.java2
-rw-r--r--chapter28a/src/test/java/com/seaofnodes/simple/Chapter19Test.java2
-rw-r--r--chapter28a/src/test/java/com/seaofnodes/simple/Chapter20Test.java17
-rw-r--r--chapter28a/src/test/java/com/seaofnodes/simple/Chapter21Test.java31
-rw-r--r--chapter28a/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java22
-rw-r--r--chapter28a/src/test/java/com/seaofnodes/simple/EscapeFlowTest.java112
-rw-r--r--chapter28a/src/test/java/com/seaofnodes/simple/ModulesTest.java36
9 files changed, 206 insertions, 38 deletions
diff --git a/chapter28a/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter28a/src/test/java/com/seaofnodes/simple/Chapter10Test.java
index 39c7040e..04e66a6f 100644
--- a/chapter28a/src/test/java/com/seaofnodes/simple/Chapter10Test.java
+++ b/chapter28a/src/test/java/com/seaofnodes/simple/Chapter10Test.java
@@ -156,7 +156,7 @@ else
return v;
""");
code.driver(CodeGen.Phase.FinalizeIR);
- assertEquals("Stop[ return Test.Vector2D; return MEM[ 2:___ 3:.x=0; 4:.y=0;]; return Test.Vector2D; ]", code.print());
+ assertEquals("Stop[ return Test.Vector2D; return Test.Vector2D; ]", code.print());
}
@Test
@@ -194,7 +194,7 @@ while (arg) {
return bar.a;
""");
code.driver(CodeGen.Phase.FinalizeIR);
- assertEquals("Stop[ return Phi(Loop,0,(Phi_a+2)); return MEM[ 2:___ 3:.a=0;]; return Test.Bar; ]", code.print());
+ assertEquals("Stop[ return Phi(Loop,0,(Phi_a+2)); return Test.Bar; ]", code.print());
}
@Test
@@ -243,10 +243,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
@@ -316,7 +316,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 MEM[ 2:___ 3:.x=0; 4:.len=0;]; return Test.Iter; ]", code.print());
+ assertEquals("Stop[ return Phi(Loop,0,(Phi(Loop,0,(Phi_x+1))+Phi_sum)); return Test.Iter; ]", code.print());
}
@@ -335,7 +335,7 @@ while(arg) {
return ret;
""");
code.driver(CodeGen.Phase.FinalizeIR);
- assertEquals("Stop[ return Phi(Loop,Test.s0,Phi(Region,Test.s0,Phi_ret)); return MEM[ 2:___ 3:.v0=0;]; return Test.s0; ]", code.print());
+ assertEquals("Stop[ return Phi(Loop,Test.s0,Phi(Region,Test.s0,Phi_ret)); return Test.s0; ]", code.print());
}
@Test
@@ -387,7 +387,7 @@ s0 b = new s0();
return a.x * 100 + b.x * 10 + cnt;
""");
code.driver(CodeGen.Phase.FinalizeIR);
- assertEquals("Stop[ return 112; return MEM[ 2:___ 3:___ 4:.x=1;]; return Test.s0; ]", code.print());
+ assertEquals("Stop[ return 112; return Test.s0; ]", code.print());
}
@@ -430,7 +430,7 @@ else return new s0;
if(new s0.f0) return 0;
""");
code.driver(CodeGen.Phase.TypeCheck);
- assertEquals("Stop[ return Test.s0; return MEM[ 2:___ 3:.f0=0;]; return Test.s0; ]", code.print());
+ assertEquals("Stop[ return Test.s0; return Test.s0; ]", code.print());
}
@Test
@@ -458,7 +458,7 @@ s0 v1 = v0;
return v1;
""");
code.driver(CodeGen.Phase.FinalizeIR);
- assertEquals("Stop[ return (const)Test.s0; return MEM[ 2:___ 3:.f0=0;]; return Test.s0; ]", code.print());
+ assertEquals("Stop[ return (const)Test.s0; return Test.s0; ]", code.print());
}
diff --git a/chapter28a/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter28a/src/test/java/com/seaofnodes/simple/Chapter12Test.java
index fae7611d..973b15d9 100644
--- a/chapter28a/src/test/java/com/seaofnodes/simple/Chapter12Test.java
+++ b/chapter28a/src/test/java/com/seaofnodes/simple/Chapter12Test.java
@@ -101,7 +101,7 @@ N n = new N(new M);
return n.next;
""");
code.driver(CodeGen.Phase.FinalizeIR);
- assertEquals("Stop[ return (const)Test.M; return MEM[ 2:___ 3:.m=0;]; return Test.M; return MEM[ 2:___ 3:___ 4:___ 5:___ 6:___ 7:.next=Parm_m(Test.N.N,*Test.M {i64 !m; }); 8:.i=0;]; return MEM[ 2:___ 3:___ 4:___ 5:___ 6:___ 7:___ 8:.i=0;]; return Test.N; ]", code.print());
+ assertEquals("Stop[ return (const)Test.M; return Test.M; return Test.N; ]", code.print());
}
@Test
diff --git a/chapter28a/src/test/java/com/seaofnodes/simple/Chapter18Test.java b/chapter28a/src/test/java/com/seaofnodes/simple/Chapter18Test.java
index 632b9539..8e109991 100644
--- a/chapter28a/src/test/java/com/seaofnodes/simple/Chapter18Test.java
+++ b/chapter28a/src/test/java/com/seaofnodes/simple/Chapter18Test.java
@@ -345,7 +345,7 @@ for(;;) {
}
""");
code.driver(Phase.LocalSched);
- assertEquals("Stop[ return Top; return Parm_i(x,i64); ]", code.print());
+ assertEquals("return Top;", code.print());
assertNull( Eval2.eval( code, 0 ) ); // Infinite loop, <clinit> never exits
}
diff --git a/chapter28a/src/test/java/com/seaofnodes/simple/Chapter19Test.java b/chapter28a/src/test/java/com/seaofnodes/simple/Chapter19Test.java
index d5fe207a..3cc00922 100644
--- a/chapter28a/src/test/java/com/seaofnodes/simple/Chapter19Test.java
+++ b/chapter28a/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 MEM[ 2:___ 3:___ 4:___ 5:.cs=Parm_s(Test.String.String,*[]u8[final]); 6:._hashCode=0;]; return MEM[ 2:___ 3:___ 4:___ 5:___ 6:._hashCode=0;]; 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/chapter28a/src/test/java/com/seaofnodes/simple/Chapter20Test.java b/chapter28a/src/test/java/com/seaofnodes/simple/Chapter20Test.java
index cdfd16a7..7eb1f255 100644
--- a/chapter28a/src/test/java/com/seaofnodes/simple/Chapter20Test.java
+++ b/chapter28a/src/test/java/com/seaofnodes/simple/Chapter20Test.java
@@ -241,13 +241,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++ )
@@ -255,9 +248,17 @@ 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));
+};
""";
+ // Return the public hashCode wrapper; keep its helper private.
testCPU(src,"x86_64_v2", "SystemV",12,null);
- testCPU(src,"riscv" , "SystemV",6,null);
+ testCPU(src,"riscv" , "SystemV",8,null);
testCPU(src,"arm" , "SystemV",6,null);
}
diff --git a/chapter28a/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/chapter28a/src/test/java/com/seaofnodes/simple/Chapter21Test.java
index 2fcf65e9..e15c2581 100644
--- a/chapter28a/src/test/java/com/seaofnodes/simple/Chapter21Test.java
+++ b/chapter28a/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,9 +375,17 @@ 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));
+};
""";
+ // Return the public hashCode wrapper; keep its helper private.
testCPU(src,"x86_64_v2", "SystemV",12,null);
- testCPU(src,"riscv" , "SystemV",6,null);
+ testCPU(src,"riscv" , "SystemV",8,null);
testCPU(src,"arm" , "SystemV",6,null);
}
@@ -407,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++ )
@@ -421,6 +415,13 @@ 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", "",12);
}
diff --git a/chapter28a/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java b/chapter28a/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java
index 6b4a987d..c8a00d19 100644
--- a/chapter28a/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java
+++ b/chapter28a/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java
@@ -132,6 +132,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/chapter28a/src/test/java/com/seaofnodes/simple/EscapeFlowTest.java b/chapter28a/src/test/java/com/seaofnodes/simple/EscapeFlowTest.java
new file mode 100644
index 00000000..71a281b5
--- /dev/null
+++ b/chapter28a/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);
+ }
+}
diff --git a/chapter28a/src/test/java/com/seaofnodes/simple/ModulesTest.java b/chapter28a/src/test/java/com/seaofnodes/simple/ModulesTest.java
index be54b43d..69f8d862 100644
--- a/chapter28a/src/test/java/com/seaofnodes/simple/ModulesTest.java
+++ b/chapter28a/src/test/java/com/seaofnodes/simple/ModulesTest.java
@@ -11,6 +11,9 @@ 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.XInt;
+import com.seaofnodes.simple.type.TypeMem;
+import com.seaofnodes.simple.type.TypeTuple;
import com.seaofnodes.simple.util.Ary;
import java.io.File;
import java.io.FileWriter;
@@ -71,27 +74,56 @@ public class ModulesTest {
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));
}
}
+ @Test public void testPublicFunctionAlias() {
+ CodeGen code = new CodeGen("""
+ val _hidden = { int x -> x; };
+ val visible = _hidden;
+ val _private = { int x -> x+1; };
+ return 0;
+ """).parse();
+ int published = -1, unpublished = -1;
+ for( FunNode fun : code._linker ) {
+ if( fun==null || fun._name==null ) continue;
+ if( fun._name.equals("_hidden") ) published = fun.sig().fidx();
+ if( fun._name.equals("_private") ) unpublished = fun.sig().fidx();
+ }
+ assertTrue(published>=0 && unpublished>=0);
+ var clz = code.compunit()._clz;
+ 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 testNeverReturnPointerRiscV() {
new CodeGen("""
struct _S { int x; };