From 741d78b6e840c88b7b183010c812ad982b342da1 Mon Sep 17 00:00:00 2001 From: Cliff Click Date: Mon, 10 Aug 2026 11:11:18 -0700 Subject: Chapter 28a: Modules and Lazy Class Initialization Co-authored-by: Hsiang-Ying Fu <66062782+fuhsnn@users.noreply.github.com> Co-authored-by: fuhsnn <66062782+fuhsnn@users.noreply.github.com> Co-authored-by: Symious <14933944+symious@users.noreply.github.com> Co-authored-by: Jun Kawasaki Co-authored-by: Claude Opus 5 --- .../java/com/seaofnodes/simple/ModulesTest.java | 248 +++++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 src/test/java/com/seaofnodes/simple/ModulesTest.java (limited to 'src/test/java/com/seaofnodes/simple/ModulesTest.java') diff --git a/src/test/java/com/seaofnodes/simple/ModulesTest.java b/src/test/java/com/seaofnodes/simple/ModulesTest.java new file mode 100644 index 00000000..be54b43d --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/ModulesTest.java @@ -0,0 +1,248 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.isa.eval.EvalRisc5; + +import com.seaofnodes.isa.eval.EvalArm64; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegAllocTestSupport.CheckedCodeGen; +import com.seaofnodes.simple.codegen.ParseAll; +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.util.Ary; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.Ignore; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class ModulesTest { + + @Test public void testFlagSpills() throws IOException { + com.seaofnodes.simple.codegen.FlagSpillTestSupport.run(); + } + + @Ignore + @Test + public void testJig() { + String src = "{ i32 -> i32 } close = \"C\"; val main = { u8[] a -> if (a# > 1) return close(0); return a[0]; };"; + CodeGen code = new CodeGen(src).driver(CodeGen.Phase.Encoding,"riscv","SystemV"); + assertEquals("return (arg+3);", code.print()); + assertEquals("3",Eval2.eval(code,0)); + } + + @Test public void testConditionalClassInitX86() throws IOException { + Path build=Path.of("build/objs"); + Files.createDirectories(build); + Path dir=Files.createTempDirectory(build,"init_flags_"); + Files.createDirectories(dir.resolve("Main")); + Files.writeString(dir.resolve("Main/B.smp"),"int value=17+arg;"); + // GCM can hoist the fused B.$init compare above the test of take. + // Its flags then cross another flag definition and need a legal split. + new CheckedCodeGen(dir.toString(),dir.toString(), + "Main","val run={int take -> return take ? B.value : 0; };",123,true,TypeInteger.BOT) + .driver(CodeGen.Phase.Encoding,"x86_64_v2","win64"); + // Remove successful fixtures; preserve the object if compilation fails. + Files.delete(dir.resolve("Main/B.smp")); + Files.delete(dir.resolve("Main")); + Files.delete(dir); + } + + @Test public void testPublicInstanceMethod() { + String src = """ + struct Scan { + val visible = { u8 c -> c==255; }; + val _hidden = { u8 c -> c==1; }; + }; + struct _Private { + val hiddenClassMethod = { u8 c -> c==2; }; + }; + return new Scan.visible(44); + """; + for( long seed : new long[]{1,123,987654321} ) { + CodeGen code = new CodeGen(src,seed,true).driver(CodeGen.Phase.Parse); + FunNode visible = null; + 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())); + checked++; + } + } + assertEquals(3,checked); + code.driver(CodeGen.Phase.TypeCheck); + assertTrue(visible.unknownCallers()); + assertEquals(TypeInteger.U8,visible.parm(3)._type); + assertEquals("0",Eval2.eval(code,0)); + } + } + + @Test public void testNeverReturnPointerRiscV() { + new CodeGen(""" + struct _S { int x; }; + !_S !s = new _S; + if(arg) while(1) { s.x += arg; } + return s; + """).driver(CodeGen.Phase.Encoding,"riscv","SystemV"); + } + + @Test public void testInlineNeverExit() { + String[] sources={ + "val _spin={ -> while(1) {} return 1; }; _spin(); return 2;", + // Two callers require cloning, including the non-exiting arm. + "val _spin={int x -> if(x) while(1) {} return 7; }; return _spin(arg)+_spin(arg);" + }; + for( int i=0; i n instanceof CallNode ? n : null)); + assertNotNull("The non-exiting path must survive",code._start.walk(n -> n instanceof LoopNode && n!=code._start ? n : null)); + if( i==1 ) assertEquals("14",Eval2.eval(code,0,10)); + assertNull(Eval2.eval(code,1,10)); + } + for( String cpu : new String[]{"x86_64_v2","arm","riscv"} ) + new CheckedCodeGen(sources[i]).driver(CodeGen.Phase.Encoding,cpu,"SystemV"); + } + } + + @Test + public void testExternData() throws IOException { + String src = """ + i32 counter="C"; + i8 small="C"; + f64 fraction="C"; + {int} bump="C"; + val test_global={ -> + val before=counter; + counter=before; + counter=++counter+1; + counter+=1; + val mid=bump(); + small++; + fraction=fraction+0.5; + return before*10000+mid*100+counter; + }; + """; + TestC.runC(src,"extern_data","",-1); + } + + @Test + public void testExternDataEncoding() { + for( String cpu : new String[]{"arm","riscv"} ) { + CodeGen code = new CheckedCodeGen("i32 counter=\"C\"; counter=counter+3; return counter;"); + // Exercise signed low-12 relocation arithmetic, past the first page. + int addr = 0x3800; + code._externDataAddresses.put("counter",addr); + code.driver(cpu,"SystemV",true,false); + byte[] image = new byte[1<<20]; + var bits = code._encoding._bits; + System.arraycopy(bits.buf(),0,image,0,bits.size()); + if( cpu.equals("arm") ) { + EvalArm64 arm = new EvalArm64(image,1<<16); + arm.st4(addr,-7); + assertEquals(0,arm.step(1000)); + assertEquals(-4,arm.regs[0]); + assertEquals(-4,arm.ld4s(addr)); + } else { + EvalRisc5 r5 = new EvalRisc5(image,1<<16); + r5.st4(addr,-7); + assertEquals(0,r5.step(1000)); + assertEquals(-4,r5.regs[10]); + assertEquals(-4,r5.ld4s(addr)); + } + } + } + + + @Test public void testPrintingConstantPool() throws Exception { + com.seaofnodes.simple.codegen.PrintRegTestSupport.checkConstantPool(); + } + + + @Test + public void testForwardConstructor() { + CodeGen code = new CodeGen("src/test/java/com/seaofnodes/simple/test_smp/forward_ctor", + "build/objs/forward_ctor_parse", + "m",null,123L,true,TypeInteger.BOT); + code.driver(CodeGen.Phase.TypeCheck); + } + + + @Test + public void testPostfixFieldUpdate() { + String src = """ + struct V { + u32 !len = 0; + i64[] !buf; + new V = { i64[] b -> buf=b; }; + val grow = { i64 n -> self; }; + val add = { i64 x -> grow(1).buf[len++] = x; self; }; + }; + return new V(new i64[1]).add(7).len; + """; + CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck); + assertEquals("1",Eval2.eval(code,0)); + } + + @Test + public void testStringEscapes() { + String src = "u8[~] s=\"A\\n\\t\\r\\\\\\\"B\"; " + + "return s#==7 && s[0]=='A' && s[1]==10 && s[2]==9 && s[3]==13 && " + + "s[4]==92 && s[5]==34 && s[6]=='B';"; + CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck); + assertEquals("1",Eval2.eval(code,0)); + } + + @Test + public void testCharacterEscapes() { + CodeGen code = new CodeGen("return '\\n'==10 && '\\t'==9 && '\\r'==13;") + .driver(CodeGen.Phase.TypeCheck); + assertEquals("1",Eval2.eval(code,0)); + } + + @Test + public void testFuzzerUnresolvedNumericLoop() { + CodeGen code = new CodeGen(""" + arg=0; + while(arg+0) + while(-arg) + arg=0; + """).driver(CodeGen.Phase.TypeCheck); + assertEquals("return 0;",code.print()); + } + + @Test + public void testFuzzerBadLCA() { + new CodeGen(""" + int v0=arg; + if(v0&&--v0?0:arg) {} + """).driver(CodeGen.Phase.LocalSched); + } + + + // Recursive search (TODO: gzip, archives) and delete all .o files + + + @Test + public void testConstantClassInitialization() throws IOException { + TestC.runSF("val x = 42; return 0;","constantClassInt","",0); + TestC.runSF("val f = { -> 7; }; return 0;","constantClassFunction","",0); + } + + +} -- cgit v1.2.3