summaryrefslogtreecommitdiff
path: root/src/test/java
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-08-10 11:11:18 -0700
committerCliff Click <cliffc@acm.org>2026-10-24 15:56:44 -0700
commit7914681413f3e3562733786aa02b1fc7adfbe9b0 (patch)
treea33a763882a3b65712835c71bdabb9873ceee55c /src/test/java
parent741d78b6e840c88b7b183010c812ad982b342da1 (diff)
downloadSimple-7914681413f3e3562733786aa02b1fc7adfbe9b0.tar.gz
Simple-7914681413f3e3562733786aa02b1fc7adfbe9b0.zip
Chapter 28b: Separate Compilationlinear-chapter28b
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 <jun784@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'src/test/java')
-rw-r--r--src/test/java/com/seaofnodes/simple/BoundaryTest.java7
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter10Test.java2
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter17bTest.java3
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter25Test.java528
-rw-r--r--src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java165
-rw-r--r--src/test/java/com/seaofnodes/simple/ModulesTest.java248
-rw-r--r--src/test/java/com/seaofnodes/simple/NativeExecutionTest.java1
-rw-r--r--src/test/java/com/seaofnodes/simple/PhaseTest.java27
-rw-r--r--src/test/java/com/seaofnodes/simple/SeparateCompilationTest.java90
-rw-r--r--src/test/java/com/seaofnodes/simple/Simple.java16
-rw-r--r--src/test/java/com/seaofnodes/simple/SpillStats.java10
-rw-r--r--src/test/java/com/seaofnodes/simple/StaticDataTest.java19
-rw-r--r--src/test/java/com/seaofnodes/simple/TestC.java2
-rw-r--r--src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java4
14 files changed, 819 insertions, 303 deletions
diff --git a/src/test/java/com/seaofnodes/simple/BoundaryTest.java b/src/test/java/com/seaofnodes/simple/BoundaryTest.java
index 68d8175f..b736ccb3 100644
--- a/src/test/java/com/seaofnodes/simple/BoundaryTest.java
+++ b/src/test/java/com/seaofnodes/simple/BoundaryTest.java
@@ -75,14 +75,9 @@ public class BoundaryTest {
}
}
- @Test public void testLibrarySource() {
- CodeGen code=new CodeGen("return sys.char.toupper('a');").driver(CodeGen.Phase.TypeCheck);
- assertEquals("65",Eval2.eval(code,0));
- }
-
@Test public void testLazyTables() {
for( long seed : new long[]{1,123,987654321} ) {
- CodeGen code=new CodeGen("src/test/smp/lazy","build/objs","App/Main",null,seed,true,TypeInteger.BOT)
+ CodeGen code=new CodeGen("src/test/smp/lazy","build/objs",null,"App/Main",null,seed,true,TypeInteger.BOT)
.driver(CodeGen.Phase.TypeCheck);
assertEquals("0",Eval2.eval(code,0));
assertEquals("125",Eval2.eval(code,1));
diff --git a/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/src/test/java/com/seaofnodes/simple/Chapter10Test.java
index 39c7040e..6892db0c 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter10Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter10Test.java
@@ -39,6 +39,7 @@ public class Chapter10Test {
}
+
private static final String NULLABLE_POINT_SOURCE = """
struct Point { int x; new Point = { int v -> x = v; }; };
!Point?[] !points = new !Point?[2];
@@ -373,6 +374,7 @@ return ret.v0;
}
+
@Test
public void testModGlobalInConstructor() {
CodeGen code = new CodeGen(
diff --git a/src/test/java/com/seaofnodes/simple/Chapter17bTest.java b/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
index 62dbbde4..4019fd38 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
@@ -300,7 +300,7 @@ A x = new A;
for( String fields : new String[] {
"!B !b; new A = { -> if (arg) b = new B; };",
"!B !b; new A = { -> if (arg) return 0; b = new B; };",
- "!B !b; new A = { !B !b -> b = new B; };", // Mutable argument shadows field
+ "!B !b; new A = { !B !b -> b = new B; };", // Argument shadows field
"new A = { -> }; !B !b;", // Field declared later
} ) {
CodeGen code = new CodeGen("struct B {}; struct A { "+fields+" }; return 0;");
@@ -538,6 +538,7 @@ for(;;arg++;) {}
}
+
// ---------------------------------------------------------------
@Test
public void testForward0() {
diff --git a/src/test/java/com/seaofnodes/simple/Chapter25Test.java b/src/test/java/com/seaofnodes/simple/Chapter25Test.java
new file mode 100644
index 00000000..31a616b5
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/Chapter25Test.java
@@ -0,0 +1,528 @@
+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.ElfReader;
+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 Chapter25Test {
+
+ @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_");
+ // Keep B separately compiled so its first-touch check survives.
+ new CodeGen(null,dir.toString(),null,"B","int value=17+arg;",123,true,TypeInteger.BOT)
+ .driver("x86_64_v2","win64",false,false);
+ // 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(null,dir.toString(),new Ary<>(new String[]{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("B.o"));
+ 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<sources.length; i++ ) {
+ for( long seed : new long[]{1,123,987654321} ) {
+ CodeGen code=new CodeGen(sources[i],seed,true).driver(CodeGen.Phase.TypeCheck);
+ assertNull("Calls should inline",code._start.walk(n -> 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 testExternDataImports() throws IOException {
+ Path dir = Files.createTempDirectory(Path.of("build/objs"),"extern_data_");
+ String globals = "i32 counter=\"C\"; val read={ -> counter; };";
+ CodeGen globalCode = new CodeGen(null,dir.toString(),null,"Globals",globals,126,true,TypeInteger.BOT)
+ .driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,false,false);
+ var clz = globalCode.compunit()._clz;
+ int counter = clz.find("counter");
+ assertTrue(clz._fields[counter]._extern);
+ assertEquals(-1,clz.offset(counter));
+ assertEquals(clz.remove(counter).size(),clz.size());
+ // Load the serialized declaration and ideal graph from Globals.o.
+ String src = """
+ i32 counter="C";
+ val test_global={ ->
+ val before=Globals.counter;
+ Globals.counter=before+1;
+ counter+=2;
+ return before*100+Globals.read();
+ };
+ """;
+ Ary<String> paths = new Ary<>(new String[]{dir.toString()});
+ for( boolean imported : new boolean[]{true,false} ) {
+ Path root = dir.resolve(imported ? "binary" : "source");
+ Files.createDirectories(root.resolve("Main"));
+ if( !imported ) Files.writeString(root.resolve("Main/Globals.smp"),globals);
+ new CodeGen(root.toString(),root.toString(),imported ? paths : null,"Main",src,126,true,TypeInteger.BOT)
+ .driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,false,false);
+ assertEquals("",TestC.gcc(root+"/Main.o","",TestC.C_DRIVERS_DIR+"extern_data_import.c",
+ (String)null,new Ary<>(new String[]{dir+"/Globals.o"}),root+"/program"+(TestC.OS.startsWith("Windows") ? ".exe" : "")));
+ }
+ }
+
+ @Test public void testPrintingConstantPool() throws Exception {
+ com.seaofnodes.simple.codegen.PrintRegTestSupport.checkConstantPool();
+ }
+
+ private static final String SYS_BLDDIR = "build/objs/lib_"+TestC.CPU_ABI;
+ private static final File SYS_FILE = new File(SYS_BLDDIR+"/sys.o");
+
+ @Test
+ public void testForwardConstructor() {
+ CodeGen code = new CodeGen("src/test/java/com/seaofnodes/simple/test_smp/forward_ctor",
+ "build/objs/forward_ctor_parse",null,
+ "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);
+ }
+
+ @Test @Ignore
+ public void testModule0() throws IOException {
+ String MODDIR = "src/test/java/com/seaofnodes/simple/test0";
+ String BLDDIR = "build/objs/test0";
+ String a_obj = BLDDIR+"/A.o";
+ String ab_obj = BLDDIR+"/A/B.o";
+ // Remove any prior results so the test runs from scratch.
+ delELFiles(new File(BLDDIR));
+ writeB(MODDIR,5);
+
+ // Compile MODDIR/A.smp into MODDIR/A.o
+ // Since A refers to B also:
+ // Compile MODDIR/A/B.smp into MODDIR/A/B.o
+ CodeGen code1 = new CodeGen(MODDIR, BLDDIR,null,
+ "A",null,123L,true,TypeInteger.BOT);
+ code1.driver(CodeGen.Phase.Export,TestC.CPU_PORT,TestC.CALL_CONVENTION);
+
+ // Verify produces A.o, A/B.o
+ File a_file = new File( a_obj);
+ File ab_file = new File(ab_obj);
+ assertTrue( a_file.exists() );
+ assertTrue( ab_file.exists() );
+ long a_msec1 = a_file.lastModified();
+ long ab_msec1 = ab_file.lastModified();
+
+ // Link and execute: arg is true, so compute "5+1" as the exit code
+ String rez1 = TestC.gcc("A", 1.2, a_obj, ab_obj);
+ assertEquals("exec exit code: 6",rez1);
+
+ // Compile again A, expecting both A.o and A/B.o to be up-to-date and not compiled
+ CodeGen code2 = new CodeGen(MODDIR, BLDDIR, null,
+ "A",null,123L,true,TypeInteger.BOT);
+ code2.driver(CodeGen.Phase.Export,TestC.CPU_PORT,TestC.CALL_CONVENTION);
+
+ assertTrue( a_file.exists() );
+ assertTrue( ab_file.exists() );
+ long a_msec2 = a_file.lastModified();
+ long ab_msec2 = ab_file.lastModified();
+
+ assertEquals( a_msec1, a_msec2 );
+ assertEquals( ab_msec1, ab_msec2 );
+
+ // Touch A.smp and recompile. A/B.o should not recompile.
+ new File(MODDIR+"/A.smp").setLastModified(System.currentTimeMillis());
+ CodeGen code3 = new CodeGen(MODDIR, BLDDIR,null,
+ "A",null,123L,true,TypeInteger.BOT);
+ code3.driver(CodeGen.Phase.Export,TestC.CPU_PORT,TestC.CALL_CONVENTION);
+
+ long a_msec3 = a_file.lastModified();
+ long ab_msec3 = ab_file.lastModified();
+ assertTrue ( a_msec1 < a_msec3 );
+ assertEquals( ab_msec1, ab_msec3 );
+
+ // Link and execute: arg is true, so compute "5+1" as the exit code
+ String rez3 = TestC.gcc("A", 1.2, a_obj, ab_obj);
+ assertEquals("exec exit code: 6",rez3);
+
+
+ // Modify B.smp and recompile A/B.o; it should recompile and A.o should not.
+ writeB(MODDIR,7);
+ CodeGen code4 = new CodeGen(MODDIR, BLDDIR,null,
+ "A/B",null,123L,true,TypeInteger.BOT);
+ code4.driver(CodeGen.Phase.Export,TestC.CPU_PORT,TestC.CALL_CONVENTION);
+
+ long a_msec4 = a_file.lastModified();
+ long ab_msec4 = ab_file.lastModified();
+ assertEquals( a_msec3, a_msec4 );
+ assertTrue ( ab_msec3 < ab_msec4 );
+
+ // Link and execute: uses stale A.o, so remains '6' not '7+1' == 8
+ String rez4 = TestC.gcc("A", 1.2, a_obj, ab_obj);
+ assertEquals("exec exit code: 6",rez4);
+
+ // Recompile A.o, it should recompile despite not being touched because
+ // it depends on A/B.o which recompiled in the prior step.
+ CodeGen code5 = new CodeGen(MODDIR, BLDDIR,null,
+ "A",null,123L,true,TypeInteger.BOT);
+ code5.driver(CodeGen.Phase.Export,TestC.CPU_PORT,TestC.CALL_CONVENTION);
+
+ long a_msec5 = a_file.lastModified();
+ long ab_msec5 = ab_file.lastModified();
+ assertTrue ( a_msec4 < a_msec5 );
+ assertEquals( ab_msec4, ab_msec5 );
+
+ // Link and execute: updates A.o from B.o inlining, without compiling B.o
+ String rez5 = TestC.gcc("A", 1.2, a_obj, ab_obj);
+ assertEquals("exec exit code: 8",rez5);
+ // Reset for next time
+ writeB(MODDIR,5);
+ }
+
+ private void writeB(String MODDIR, int x) throws IOException {
+ var bsmp = new FileWriter(MODDIR+"/A/B.smp");
+ bsmp.write("val x="+x+";\n");
+ bsmp.close();
+ }
+
+
+ // Recursive search (TODO: gzip, archives) and delete all .o files
+ private void delELFiles( File dir) {
+ if( dir.isDirectory() )
+ for( File f : dir.listFiles() )
+ delELFiles(f);
+ else if( dir.getName().endsWith(".o") )
+ dir.delete();
+ }
+
+
+ @Test
+ public void testSys() {
+ assertTrue("Missing "+SYS_FILE+"; run make tests_sys", SYS_FILE.exists());
+
+ // Can read the ELF files
+ CodeGen code1 = new CodeGen("return 0;");
+ ElfReader sys_elf = ElfReader.load(SYS_FILE, null);
+ sys_elf.loadPublicTypes(code1);
+
+ // Elf files are sane
+
+ // Sys depends on io, libc, char, collections, and array utilities.
+ assertArrayEquals(new String[]{"sys", "sys/adt", "sys/adt/bitset", "sys/ary",
+ "sys/aryi64", "sys/aryu8", "sys/char", "sys/io", "sys/libc", "sys/scan"},sys_elf._deps);
+ assertSame("class:sys" ,sys_elf._clz._name);
+ }
+
+ @Test
+ public void testConstantClassInitialization() throws IOException {
+ TestC.runSF("val x = 42; return 0;","constantClassInt","",0);
+ TestC.runSF("val f = { -> 7; }; return 0;","constantClassFunction","",0);
+ }
+
+ @Test
+ public void testHelloWorld() throws IOException {
+ String expected = "Hello, World!\n";
+ String prog = "return sys.io.p(\""+expected+"\") - "+expected.length()+";";
+ // tests_raw1 concurrently builds Chapter22Test's helloWorld executable.
+ TestC.run(prog,"helloWorldSys",new Ary<>(new String[]{SYS_BLDDIR}),
+ TestC.CALL_CONVENTION, null, null, expected,0);
+ }
+
+ @Test
+ public void testHelloWorldDriver() throws Exception {
+ String out = "build/objs/helloWorldDriver/";
+ Simple.main(new String[]{"-L",SYS_BLDDIR,"-o",out,"--norun","src/test/smp/examples/A_helloWorld.smp"});
+ assertTrue(Files.isRegularFile(Path.of(out,"A_helloWorld.o")));
+ }
+
+ @Test
+ public void testHelloWorldDriverLibFile() throws Exception {
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ PrintStream saved = System.out;
+ try( PrintStream capture = new PrintStream(output,true,StandardCharsets.UTF_8) ) {
+ System.setOut(capture);
+ Simple.main(new String[]{"-L",SYS_FILE.toString(),"-o","build/objs/helloWorldDriverLibFile/",
+ "src/test/smp/examples/A_helloWorld.smp"});
+ } finally {
+ System.setOut(saved);
+ }
+ assertEquals("Hello, World!\n",output.toString(StandardCharsets.UTF_8));
+ }
+
+ @Test
+ public void testHelloWorldNoInline() throws Exception {
+ String base = "helloWorldNoInline";
+ String expected = "Hello, World!\n";
+ String prog = "return sys.io.p_noInline(\""+expected+"\") - "+expected.length()+";";
+ CodeGen code = new CheckedCodeGen(null,"build/objs",new Ary<>(new String[]{SYS_BLDDIR}),
+ base,prog,123L,true,TypeInteger.BOT);
+ code.driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,false,true);
+
+ String obj = "build/objs/"+base+".o";
+ String exe = "build/objs/"+base+(TestC.OS.startsWith("Windows") ? ".exe" : "");
+ String syms = TestC.exec(TestC.TEST_TIMEOUT_SECONDS,"nm",obj);
+ assertTrue(syms, syms.contains(" U sys.io.p_noInline"));
+
+ TestC.linkExe(obj,null,null,new Ary<>(new String[]{SYS_FILE.toString()}),exe);
+ String rez = TestC.exec(TestC.TEST_TIMEOUT_SECONDS,exe);
+ assertEquals(expected,rez);
+ }
+
+ @Test
+ public void testRedirectedRead() throws IOException {
+ String src = """
+u8[] buf = new u8[10];
+i64 ptr = buf;
+int rez = sys.libc.read(0,ptr,buf#);
+return rez < buf# ? 0 : sys.libc._exit(-2);
+""";
+ TestC.run(src,"redirectedRead",new Ary<>(new String[]{SYS_BLDDIR}),
+ TestC.CALL_CONVENTION, null, null,
+ "abc", "", -1);
+ }
+
+ @Test
+ public void testErrnoAccessor() throws IOException {
+ String src = "val status=sys.libc.close(-1); return status != -1 || sys.libc.errno()==0;";
+ TestC.run(src,"errnoAccessor",new Ary<>(new String[]{SYS_BLDDIR}),
+ TestC.CALL_CONVENTION,null,null,"",-1);
+ }
+
+ @Test
+ public void testBubbles() throws IOException {
+ String src = Files.readString( Path.of("src/test/smp/examples/BubbleSort.smp"));
+ String exe = TestC.compile(src,"BubbleSort",new Ary<>(new String[]{SYS_BLDDIR}),
+ TestC.CALL_CONVENTION,null,null,-1);
+ assertEquals("[-17, 2, 3, 999]\n",
+ TestC.exec(exe,"[3, 2,-17, 999 ]"));
+ assertEquals("[1, 2, 3, 4, 4, 5]\n",
+ TestC.exec(exe,"[4, 5, 3, 1, 4, 2]"));
+ assertEquals("[1, 2, 3, 4, 5]\n",
+ TestC.exec(exe,"[1, 2, 3, 4, 5]"));
+ assertEquals("[1, 2, 3, 4, 5, 6, 7, 8, 9]\n",
+ TestC.exec(exe,"[9, 8, 7, 6, 5, 4, 3, 2, 1]"));
+
+ String usage = "Usage: please provide a list of at least two integers to sort in the format \"[1, 2, 3, 4, 5]\"\n";
+ assertEquals(usage,TestC.exec(exe));
+ assertEquals(usage,TestC.exec(exe,""));
+ assertEquals(usage,TestC.exec(exe,"[1]"));
+ assertEquals(usage,TestC.exec(exe,"[4 5 3]"));
+ }
+
+ @Test
+ public void testCapitalize() throws IOException {
+ String src = Files.readString(Path.of("src/test/smp/examples/Capitalize.smp"));
+ String exe = TestC.compile(src,"Capitalize",new Ary<>(new String[]{SYS_BLDDIR}),
+ TestC.CALL_CONVENTION,null,null,-1);
+ assertEquals("Hello world\n",TestC.exec(exe,"hello world"));
+ assertEquals("Hello World\n",TestC.exec(exe,"Hello World"));
+ assertEquals("123 apples\n",TestC.exec(exe,"123 apples"));
+ assertEquals("Usage: please provide a string\n",TestC.exec(exe));
+ assertEquals("Usage: please provide a string\n",TestC.exec(exe,""));
+ assertEquals("Use quotes around multiple strings.\n",TestC.exec(exe,"hello","world"));
+ }
+
+ @Test
+ public void testDijkstra() throws IOException {
+ String src = Files.readString(Path.of("src/test/smp/examples/Dijkstra.smp"));
+ String matrix = "[0, 2, 0, 6, 0, 2, 0, 3, 8, 5, 0, 3, 0, 0, 7, 6, 8, 0, 0, 9, 0, 5, 7, 9, 0]";
+ String exe = TestC.compile(src,"Dijkstra",new Ary<>(new String[]{SYS_BLDDIR}),
+ TestC.CALL_CONVENTION,null,null,-1);
+ assertEquals("2\n",TestC.exec(exe,matrix,"0","1"));
+ String usage = "Usage: please provide three inputs: a serialized matrix, a source node and a destination node\n";
+ assertEquals("7\n",TestC.exec(exe,matrix,"0","4"));
+ assertEquals(usage,TestC.exec(exe));
+ assertEquals(usage,TestC.exec(exe,"","",""));
+ assertEquals(usage,TestC.exec(exe,"[1, 0, 3, 0, 5, 1]","1","2"));
+ assertEquals(usage,TestC.exec(exe,"[0, 0, 0, 0]","0","1"));
+ }
+
+ @Test
+ public void testFileIO() throws IOException {
+ String src = Files.readString(Path.of("src/test/smp/examples/FileIO.smp"));
+ String exe = TestC.compile(src,"FileIO",new Ary<>(new String[]{SYS_BLDDIR}),
+ TestC.CALL_CONVENTION,null,null,-1);
+ assertEquals("File I/O succeeded\n",TestC.exec(exe));
+ }
+}
diff --git a/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java b/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java
index 6b4a987d..47e10f97 100644
--- a/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java
+++ b/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java
@@ -57,7 +57,7 @@ public class ClassInitDependenciesTest {
Files.createDirectories(path.getParent());
Files.writeString(path,source.getValue());
}
- return new CodeGen(root.toString(),root.resolve("obj").toString(),
+ return new CodeGen(root.toString(),root.resolve("obj").toString(),null,
"Root",null,seed,true,TypeInteger.BOT).driver(CodeGen.Phase.TypeCheck);
}
@@ -194,26 +194,119 @@ public class ClassInitDependenciesTest {
assertTrue(touches(code,new ClassInitDependencies(code),"Root/A","Root/B"));
code=compile("A",Map.of("A","{int} native=\"C\"; return native();"),123);
+ Path dir=tempDir("import-");
+ new CodeGen(null,dir.toString(),null,"B","int value=1;",123,true,TypeInteger.BOT)
+ .driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,false,false);
+ code=new CodeGen(null,dir.toString(),new Ary<>(new String[]{dir.toString()}),
+ "A","return B.value;",123,true,TypeInteger.BOT).driver(CodeGen.Phase.TypeCheck);
+ assertTrue(touches(code,new ClassInitDependencies(code),"A","B"));
}
- private void object(Path dir, Path unused, String name, String source) throws Exception {
- Path file=dir.resolve("Root/"+name+".smp");
- Files.createDirectories(file.getParent());
- Files.writeString(file,source);
+ private CodeGen object(Path dir, Path imports, String name, String source) {
+ return new CodeGen(null,dir.toString(),new Ary<>(new String[]{imports.toString()}),
+ name,source,123,true,TypeInteger.BOT)
+ .driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,false,false);
}
- private void module(Path dir, Path unused, String name, Map<String,String> sources) throws Exception {
- for( var entry : sources.entrySet() ) object(dir,dir,entry.getKey(),entry.getValue());
+ private CodeGen module(Path dir, Path imports, String name, Map<String,String> sources) throws Exception {
+ Path src=tempDir("import-");
+ for( var entry : sources.entrySet() ) {
+ Path file=src.resolve(entry.getKey()+".smp");
+ Files.createDirectories(file.getParent());
+ Files.writeString(file,entry.getValue());
+ }
+ return new CodeGen(src.toString(),dir.toString(),new Ary<>(new String[]{imports.toString()}),
+ name,null,123,true,TypeInteger.BOT).driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,false,false);
}
- private CodeGen entry(Path dir, String source, long seed) throws Exception {
- Files.writeString(dir.resolve("Root.smp"),"");
- object(dir,dir,"Main",source);
- return new CodeGen(dir.toString(),dir.toString(),"Root/Main",null,seed,true,TypeInteger.BOT);
+ @Test public void testImportedInitialization() throws Exception {
+ Path dir=tempDir("import-"), stubs=tempDir("import-");
+ object(stubs,stubs,"A","int value=arg;");
+ object(stubs,stubs,"B","int value=arg;");
+ // Each object is independently acyclic. Combining their real bodies
+ // must recheck the graph rather than trust either object's local order.
+ String asrc="int value=arg;\nvalue=B.value;";
+ CodeGen acode=module(dir,stubs,"A",Map.of("A",asrc));
+ String afile=acode._compunits.get("A").sourceFile();
+ module(dir,stubs,"B",Map.of("B","int value=arg; value=A.value;"));
+ for( String entry : new String[]{"A","B"} ) {
+ try {
+ new CodeGen(null,dir.toString(),new Ary<>(new String[]{dir.toString()}),
+ "Main","return "+entry+".value;",123,true,TypeInteger.BOT).driver(CodeGen.Phase.TypeCheck);
+ fail("Expected imported initialization cycle");
+ } catch( Parser.ParseException ex ) {
+ assertCycle(ex.getMessage(),"A -> B -> A");
+ assertTrue(ex.getMessage(),ex.getMessage().contains("value=B.value;\n -------^"));
+ assertTrue(ex.getMessage(),ex.getMessage().contains("\n"+afile+":2:8: error: cyclic class initialization\n"));
+ }
+ }
+ // The imported object must still report its saved path and offset
+ // when the original source is no longer available.
+ Files.delete(Path.of(afile));
+ try {
+ new CodeGen(null,dir.toString(),new Ary<>(new String[]{dir.toString()}),
+ "Main","return A.value;",123,true,TypeInteger.BOT).driver(CodeGen.Phase.TypeCheck);
+ fail("Expected imported initialization cycle");
+ } catch( Parser.ParseException ex ) {
+ assertCycle(ex.getMessage(),"A -> B -> A");
+ assertTrue(ex.getMessage(),ex.getMessage().contains("\n"+afile+": error: cyclic class initialization (source unavailable at byte offset "+asrc.indexOf('.')+")"));
+ }
}
- private CodeGen emit(Path dir, String name, String source) throws Exception {
- return entry(dir,source,123).driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,false,false);
+ @Test public void testImportedDiamondAndHelpers() throws Exception {
+ Path diamond=tempDir("import-");
+ CodeGen dcode=object(diamond,diamond,"D","int value=arg;");
+ var data=dcode._encoding._data.entries().stream().filter(obj -> obj.symbol.equals("D.$class")).findFirst().orElseThrow();
+ var dclz=dcode._compunits.get("D")._clz;
+ assertEquals(0,dcode._encoding._sdata.buf()[data.offset+dclz.offset(dclz.find("$init"))]);
+ object(diamond,diamond,"B","int value=D.value;");
+ object(diamond,diamond,"C","int value=D.value;");
+ for( long seed : new long[]{1,123,987654321} ) {
+ CodeGen code=new CodeGen(null,diamond.toString(),new Ary<>(new String[]{diamond.toString()}),
+ "Main","int value=B.value+C.value;",seed,true,TypeInteger.BOT).driver(CodeGen.Phase.TypeCheck);
+ CompUnit b=code._compunits.get("B"), d=code._compunits.get("D");
+ ClassInitDependencies deps=new ClassInitDependencies(code);
+ assertTrue(touches(code,deps,"B","D"));
+ assertTrue(touches(code,deps,"C","D"));
+ assertTrue(touches(code,deps,"Main","B"));
+ assertTrue(touches(code,deps,"Main","C"));
+ Field flag=d._clz.field("$init");
+ assertSame(TypeInteger.U1,flag._t);
+ assertFalse(flag._final);
+ assertNotEquals(flag._alias,b._clz.field("$init")._alias);
+ // Loading just the header/types uses the same alias identity and
+ // does not require allocating any graph nodes.
+ ElfReader elf=ElfReader.load(diamond.resolve("D.o").toFile(),d);
+ assertEquals(flag._alias,elf.loadPublicTypes(code).field("$init")._alias);
+ assertNull(elf._nodes);
+ }
+ object(diamond,diamond,"Helper","val get={ -> D.value; }; val get_noInline={ -> D.value; };");
+ for( String name : new String[]{"get","get_noInline"} ) {
+ CodeGen code=new CodeGen(null,diamond.toString(),new Ary<>(new String[]{diamond.toString()}),
+ "Main","return Helper."+name+"();",123,true,TypeInteger.BOT).driver(CodeGen.Phase.TypeCheck);
+ ClassInitDependencies deps=new ClassInitDependencies(code);
+ assertTrue(touches(code,deps,"Main","D"));
+ assertFalse(touches(code,deps,"Helper","D"));
+ }
+ object(diamond,diamond,"Wrapper","val get_noInline={ -> Helper.get_noInline(); };");
+ CodeGen wrapped=new CodeGen(null,diamond.toString(),new Ary<>(new String[]{diamond.toString()}),
+ "Main","return Wrapper.get_noInline();",123,true,TypeInteger.BOT).driver(CodeGen.Phase.TypeCheck);
+ assertTrue(touches(wrapped,new ClassInitDependencies(wrapped),"Main","D"));
+ }
+
+ @Test public void testInterleavedModules() throws Exception {
+ // Two objects, each providing a parent and children. Their orders
+ // must interleave: P.B precedes Q.A, which precedes P.A.
+ Path modules=tempDir("import-"), qstub=tempDir("import-");
+ module(qstub,qstub,"Q",Map.of("Q","val A=A;", "Q/A","int value=arg;"));
+ module(modules,qstub,"P",Map.of("P","val A=A; val B=B;",
+ "P/A","int value=arg; value=Q.A.value;", "P/B","int value=arg;"));
+ module(modules,modules,"Q",Map.of("Q","val A=A;", "Q/A","int value=arg; value=P.B.value;"));
+ CodeGen combined=new CodeGen(null,modules.toString(),new Ary<>(new String[]{modules.toString()}),
+ "Main","return P.A.value;",123,true,TypeInteger.BOT).driver(CodeGen.Phase.TypeCheck);
+ ClassInitDependencies deps=new ClassInitDependencies(combined);
+ assertTrue(touches(combined,deps,"Q/A","P/B"));
+ assertTrue(touches(combined,deps,"P/A","Q/A"));
}
@Test public void testCycleReportDirection() throws Exception {
@@ -346,28 +439,30 @@ public class ClassInitDependenciesTest {
object(dir,dir,"B","State.count++; int value=17+arg;");
String src="int value=arg ? B.value+B.value : 0; return value+100*State.count;";
for( long seed : new long[]{1,123,987654321} ) {
- CodeGen code=entry(dir,src,seed).driver(CodeGen.Phase.TypeCheck);
+ CodeGen code=new CodeGen(null,dir.toString(),new Ary<>(new String[]{dir.toString()}),
+ "Main",src,seed,true,TypeInteger.BOT).driver(CodeGen.Phase.TypeCheck);
assertNotNull(code._stop.walk(n -> n instanceof CLInitNode ? n : null));
assertEquals("0",Eval2.eval(code,0));
assertNull(code._stop.walk(n -> n instanceof CLInitNode ? n : null));
assertEquals("134",Eval2.eval(code,1));
}
- emit(dir,"Main","val run={int take -> "+src.replace("arg ?","take ?")+" };");
+ object(dir,dir,"Main","val run={int take -> "+src.replace("arg ?","take ?")+" };");
Path cfile=dir.resolve("check.c");
Files.writeString(cfile,"""
#include <stdint.h>
- __attribute__((CALL_CONV)) extern int64_t run(int64_t take) __asm__("Root.Main.run");
+ __attribute__((CALL_CONV)) extern int64_t run(int64_t take);
int main(void) {
return run(0)!=0 || run(1)!=134 || run(1)!=134 || run(0)!=100;
}
""");
String exe=dir.resolve(TestC.OS.startsWith("Windows") ? "check.exe" : "check").toString();
- TestC.linkExe(dir.resolve("Root/Main.o").toString(),TestC.CALL_CONVENTION.equals("win64") ? "ms_abi" : "sysv_abi",
- cfile.toString(),null,exe);
+ TestC.linkExe(dir.resolve("Main.o").toString(),TestC.CALL_CONVENTION.equals("win64") ? "ms_abi" : "sysv_abi",
+ cfile.toString(),new Ary<>(new String[]{dir.resolve("B.o").toString(),dir.resolve("State.o").toString()}),exe);
assertEquals("",TestC.exec(exe));
for( String cpu : new String[]{"arm","riscv"} )
- entry(dir,src,123).driver(CodeGen.Phase.Encoding,cpu,"SystemV");
+ new CodeGen(null,dir.toString(),new Ary<>(new String[]{dir.toString()}),
+ "Main",src,123,true,TypeInteger.BOT).driver(CodeGen.Phase.Encoding,cpu,"SystemV");
}
@Test public void testParentInitializationExecution() throws Exception {
@@ -376,29 +471,49 @@ public class ClassInitDependenciesTest {
module(dir,dir,"P",Map.of("P","val C=C; State.count=State.count*10+1;",
"P/C","State.count=State.count*10+2; int value=arg;"));
for( long seed : new long[]{1,123,987654321} ) {
- CodeGen code=entry(dir,"return P.C.value+State.count;",seed).driver(CodeGen.Phase.TypeCheck);
+ CodeGen code=new CodeGen(null,dir.toString(),new Ary<>(new String[]{dir.toString()}),
+ "Main","return P.C.value+State.count;",seed,true,TypeInteger.BOT).driver(CodeGen.Phase.TypeCheck);
assertEquals("12",Eval2.eval(code,99));
}
- emit(dir,"Main","val run={ -> P.C.value+State.count; };");
+ object(dir,dir,"Main","val run={ -> P.C.value+State.count; };");
Path cfile=dir.resolve("check.c");
Files.writeString(cfile,"""
#include <stdint.h>
- __attribute__((CALL_CONV)) extern int64_t run(void) __asm__("Root.Main.run");
+ __attribute__((CALL_CONV)) extern int64_t run(void);
int main(void) { return run()!=12 || run()!=12; }
""");
String exe=dir.resolve(TestC.OS.startsWith("Windows") ? "check.exe" : "check").toString();
- TestC.linkExe(dir.resolve("Root/Main.o").toString(),TestC.CALL_CONVENTION.equals("win64") ? "ms_abi" : "sysv_abi",
- cfile.toString(),null,exe);
+ TestC.linkExe(dir.resolve("Main.o").toString(),TestC.CALL_CONVENTION.equals("win64") ? "ms_abi" : "sysv_abi",
+ cfile.toString(),new Ary<>(new String[]{dir.resolve("P.o").toString(),dir.resolve("State.o").toString()}),exe);
assertEquals("",TestC.exec(exe));
}
@Test public void testNonreturningObject() throws Exception {
Path dir=tempDir("nonreturning-");
object(dir,dir,"Spin","val spin_noInline={ -> while(1) {} return 7; };");
- CodeGen code=entry(dir,"Spin.spin_noInline(); return 7;",123).driver(CodeGen.Phase.TypeCheck);
+ CodeGen code=new CodeGen(null,dir.toString(),new Ary<>(new String[]{dir.toString()}),
+ "Main","Spin.spin_noInline(); return 7;",123,true,TypeInteger.BOT).driver(CodeGen.Phase.TypeCheck);
assertNull(Eval2.eval(code,0,10));
}
+ @Test public void testImportedPrivateInitializer() throws Exception {
+ Path dir=tempDir("private-");
+ module(dir,dir,"Library",Map.of("Library","val read={ -> _Hidden.value; };",
+ "Library/_Hidden","int value=17+arg;"));
+ CodeGen code=new CodeGen(null,dir.toString(),new Ary<>(new String[]{dir.toString()}),
+ "Main","return Library.read();",123,true,TypeInteger.BOT).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("17",Eval2.eval(code,99));
+ // Even a request stopping at Select must drop imported bodies. They
+ // remain available only to the ideal evaluator above, not this object.
+ for( String cpu : new String[]{"x86_64_v2","arm","riscv"} ) {
+ CodeGen selected=new CodeGen(null,dir.toString(),new Ary<>(new String[]{dir.toString()}),
+ "Main","return Library.read();",123,true,TypeInteger.BOT).driver(CodeGen.Phase.Select,cpu,"SystemV");
+ assertNull(selected._serial);
+ for( CompUnit cu : selected._compunits.values() )
+ if( cu._src==null ) assertNull(cu._stop);
+ selected.driver(CodeGen.Phase.Encoding,cpu,"SystemV");
+ }
+ }
@Test public void testOverlappingFunctionAndClassCycles() throws Exception {
// Fixed function graph: Opto/inlining must not erase the overlap that
diff --git a/src/test/java/com/seaofnodes/simple/ModulesTest.java b/src/test/java/com/seaofnodes/simple/ModulesTest.java
deleted file mode 100644
index be54b43d..00000000
--- a/src/test/java/com/seaofnodes/simple/ModulesTest.java
+++ /dev/null
@@ -1,248 +0,0 @@
-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<sources.length; i++ ) {
- for( long seed : new long[]{1,123,987654321} ) {
- CodeGen code=new CodeGen(sources[i],seed,true).driver(CodeGen.Phase.TypeCheck);
- assertNull("Calls should inline",code._start.walk(n -> 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);
- }
-
-
-}
diff --git a/src/test/java/com/seaofnodes/simple/NativeExecutionTest.java b/src/test/java/com/seaofnodes/simple/NativeExecutionTest.java
index fa914d28..f3f65183 100644
--- a/src/test/java/com/seaofnodes/simple/NativeExecutionTest.java
+++ b/src/test/java/com/seaofnodes/simple/NativeExecutionTest.java
@@ -48,6 +48,7 @@ public class NativeExecutionTest {
@Test public void testHelloWorldArtifacts() throws IOException {
new Chapter22Test().testHelloWorld();
+ new Chapter25Test().testHelloWorld();
// Model a parallel run paused between Chapter 22's link and execution.
// Chapter 25 must not replace the executable Chapter 22 is about to run.
String exe = "build/objs/helloWorld"+(TestC.OS.startsWith("Windows") ? ".exe" : "");
diff --git a/src/test/java/com/seaofnodes/simple/PhaseTest.java b/src/test/java/com/seaofnodes/simple/PhaseTest.java
index 5ff7fcd6..7252378a 100644
--- a/src/test/java/com/seaofnodes/simple/PhaseTest.java
+++ b/src/test/java/com/seaofnodes/simple/PhaseTest.java
@@ -22,22 +22,43 @@ public class PhaseTest {
code.driver(Phase.TypeCheck);
code.driver(Phase.TypeCheck); // A repeated request does not rerun anything.
code.driver(Phase.LocalSched);
- assertEquals("Parse Iter Opto IterPostOpto FinalizeIR TypeCheck Lower LoopTree Schedule LocalSched",
+ assertEquals("Parse Iter Opto IterPostOpto FinalizeIR TypeCheck Lower LoopTree Unlink Schedule LocalSched",
String.join(" ",phases));
+ assertNull(code._serial);
assertNull(code._mach);
assertEquals("45",Eval2.eval(code,10));
}
- @Test public void testEncodingAndResume() {
+ @Test public void testEncodingWithoutSerialization() {
for( String cpu : new String[]{"x86_64_v2","arm","riscv"} ) {
CodeGen code = new CodeGen(SRC).driver(Phase.TypeCheck);
- code.driver(Phase.LoopTree,cpu,"SystemV");
+ code.driver(Phase.Unlink,cpu,"SystemV");
code.driver(Phase.Select,cpu,"SystemV");
code.driver(Phase.Encoding,cpu,"SystemV");
+ assertNull(code._serial);
byte[] staged = code._encoding._bits.toByteArray();
CodeGen direct = new CodeGen(SRC).driver(Phase.Encoding,cpu,"SystemV");
assertArrayEquals(staged,direct._encoding._bits.toByteArray());
}
}
+ @Test public void testExplicitSerializationAndMemoryExport() {
+ CodeGen code = new CodeGen(SRC).driver(Phase.Serialize);
+ assertNotNull(code._serial);
+ assertEquals(Phase.Serialize,code._phase);
+ code.driver(Phase.Encoding,"riscv","SystemV");
+ assertNotNull(code._serial);
+
+ CodeGen memory = new CodeGen(SRC).driver("riscv","SystemV",true,false);
+ assertNull(memory._serial);
+ assertEquals(Phase.Export,memory._phase);
+
+ CodeGen late = new CodeGen(SRC).driver(Phase.Encoding,"riscv","SystemV");
+ try {
+ late.exportELF(false,false);
+ fail("Cannot recover ideal IR after lowering and instruction selection");
+ } catch( IllegalStateException expected ) {
+ assertTrue(expected.getMessage().contains("Serialize before Lower"));
+ }
+ }
}
diff --git a/src/test/java/com/seaofnodes/simple/SeparateCompilationTest.java b/src/test/java/com/seaofnodes/simple/SeparateCompilationTest.java
new file mode 100644
index 00000000..4576bc94
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/SeparateCompilationTest.java
@@ -0,0 +1,90 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.Ary;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class SeparateCompilationTest {
+ @Test public void testImportedNativeExposure() throws Exception {
+ Path build=Path.of("build/objs/separate");
+ Files.createDirectories(build);
+ Path lib=Files.createTempDirectory(build,"callbacks-");
+ String src="""
+ val run={int n ->
+ val power={int base,int exponent ->
+ if(exponent<=0) return 1;
+ return base*power(base,exponent-1);
+ };
+ { {int int -> int} -> int } retain="C";
+ retain(power);
+ return power(2,n);
+ };
+ """;
+ new CodeGen(null,lib.toString(),null,"Callbacks",src,123,true,TypeInteger.BOT)
+ .driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,false,false);
+ CodeGen code=new CodeGen(null,null,new Ary<>(new String[]{lib.toString()}),
+ "Client","return Callbacks.run(arg);",123,true,TypeInteger.BOT)
+ .driver(CodeGen.Phase.Opto);
+ assertNull(code._compunits.get("Callbacks")._src);
+ ParmNode base=code._start.walk(n -> n instanceof ParmNode p && p._label.equals("base") ? p : null);
+ assertNotNull(base);
+ assertSame("Native code may call the saved callback with any base",TypeInteger.BOT,base._type);
+ code.driver(CodeGen.Phase.TypeCheck);
+ }
+
+ // The client root contains no library source. The saved constructor must
+ // keep local loop values, and retained calls must use the library's storage.
+ @Test public void testImportedConstructorAndState() throws Exception {
+ Path fixtures=Path.of("src/test/smp/separate");
+ Path build=Path.of("build/objs/separate");
+ Files.createDirectories(build);
+ Path lib=Files.createTempDirectory(build,"library-");
+ Path client=Files.createTempDirectory(build,"client-");
+ new CodeGen(fixtures.resolve("library").toString(),lib.toString(),null,
+ "Counters",null,123,true,TypeInteger.BOT)
+ .driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,false,false);
+ assertTrue(Files.isRegularFile(lib.resolve("Counters.o")));
+ assertFalse(Files.exists(fixtures.resolve("client/Counters.smp")));
+ var paths=new Ary<>(new String[]{lib.toString()});
+ for( long seed : new long[]{2,13,123} ) {
+ CodeGen code=new CodeGen(fixtures.resolve("client").toString(),client.toString(),paths,
+ "Main",null,seed,true,TypeInteger.BOT).driver(CodeGen.Phase.TypeCheck);
+ CompUnit imported=code._compunits.get("Counters");
+ assertNotNull(imported);
+ assertNull("Library source must not be parsed",imported._src);
+ assertEquals(lib.resolve("Counters.o").toRealPath(),imported._obj.toPath().toRealPath());
+ assertEquals("0",Eval2.eval(code,0));
+ assertEquals("107",Eval2.eval(code,3));
+ assertEquals("131",Eval2.eval(code,5));
+ }
+ new CodeGen(fixtures.resolve("client").toString(),client.toString(),paths,
+ "Main",null,123,true,TypeInteger.BOT)
+ .driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,false,false);
+ Path driver=client.resolve("driver.c");
+ Files.writeString(driver,"""
+ #include <stdio.h>
+ __attribute__((CALL_CONV)) extern long long run(long long);
+ int main(void) {
+ long long args[]={0,3,5,0,3}, expected[]={0,107,231,0,307};
+ for(int i=0;i<5;i++) {
+ long long actual=run(args[i]);
+ if(actual!=expected[i]) {
+ printf("call %d: got %lld, expected %lld\\n",i,actual,expected[i]);
+ return 1;
+ }
+ }
+ return 0;
+ }
+ """);
+ String exe=client.resolve(TestC.OS.startsWith("Windows")?"test.exe":"test").toString();
+ TestC.linkExe(client.resolve("Main.o").toString(),
+ TestC.CALL_CONVENTION.equals("win64")?"ms_abi":"sysv_abi",driver.toString(),
+ new Ary<>(new String[]{lib.resolve("Counters.o").toString()}),exe);
+ assertEquals("",TestC.exec(TestC.TEST_TIMEOUT_SECONDS,exe));
+ }
+}
diff --git a/src/test/java/com/seaofnodes/simple/Simple.java b/src/test/java/com/seaofnodes/simple/Simple.java
index 437fcd81..7cfdf57d 100644
--- a/src/test/java/com/seaofnodes/simple/Simple.java
+++ b/src/test/java/com/seaofnodes/simple/Simple.java
@@ -31,6 +31,7 @@ Options:
'/', then all result files use relative paths to 'output', using a input file
root-relative path. If --root is missing, the one result file is named 'output'
+ -L path - add an external Simple object search path
--eval - (slowly) evaluate the compiled code in emulator
--run - run the compiled code natively; this is the default
--main - export the top-level initializer as C `main`
@@ -104,6 +105,7 @@ Options:
String cpu = null;
String abi = null;
String out = null;
+ ArrayList<String> libPaths = new ArrayList<>();
// Parse command line
int i; for( i = 0; i < args.length; i++ ) {
@@ -144,6 +146,9 @@ Options:
case "--root": if( root!=null || i+1>=args.length ) bad_usage();
root=args[++i];
break;
+ case "-L": if (i + 1 >= args.length || args[i + 1].charAt(0) == '-') bad_usage();
+ libPaths.add(args[++i]);
+ break;
case "--cpu": if (cpu != null || i + 1 >= args.length || args[i + 1].charAt(0) == '-') bad_usage();
cpu = args[++i];
break;
@@ -218,11 +223,16 @@ Options:
if( cpu == null ) cpu = TestC.CPU_PORT;
if( abi == null ) abi = TestC.CALL_CONVENTION;
- // Read input file
+ String simpleHome = System.getenv("SIMPLE_HOME");
+ if( simpleHome != null && !simpleHome.isEmpty() )
+ libPaths.add(Path.of(simpleHome,cpu+"_"+abi).toString());
+
+ // Keep file-backed units so discovery sees parents and siblings.
src = input_filename.isEmpty() ? getInput() : null;
// Compilation pipeline
- CodeGen code = new CodeGen(modPath.toString(), outPath.toString(), srcName, src, 456, true, TypeInteger.BOT );
+ Ary<String> externPaths = libPaths.isEmpty() ? null : new Ary<>(libPaths.toArray(new String[0]));
+ CodeGen code = new CodeGen(modPath.toString(), outPath.toString(), externPaths, srcName, src, 456, true, TypeInteger.BOT );
code.driver(do_eval && !do_codegen ? Phase.TypeCheck : Phase.LastPhase,
cpu,abi, false, emit_main || do_run || do_eval, dump);
@@ -247,7 +257,7 @@ Options:
if ( !TestC.CPU_PORT.equals( cpu ) || !TestC.CALL_CONVENTION.equals( abi ) )
throw bad("cannot run code on not native target");
String exe = TestC.OS.startsWith("Windows") ? outBase+".exe" : outBase;
- String result = TestC.gcc(outBase+".o", null, null, "", TestC.linkObjs(null), exe);
+ String result = TestC.gcc(outBase+".o", null, null, "", TestC.linkObjs(externPaths), exe);
System.out.print(result);
}
}
diff --git a/src/test/java/com/seaofnodes/simple/SpillStats.java b/src/test/java/com/seaofnodes/simple/SpillStats.java
index 944f7ef5..d43f1a36 100644
--- a/src/test/java/com/seaofnodes/simple/SpillStats.java
+++ b/src/test/java/com/seaofnodes/simple/SpillStats.java
@@ -20,7 +20,7 @@ public class SpillStats extends RunListener {
@Override public void testFinished(Description test) { _test = null; }
public static void record25(CodeGen code) {
- if( ACTIVE==null || ACTIVE._test==null || ACTIVE._test.getTestClass()!=ModulesTest.class ) return;
+ if( ACTIVE==null || ACTIVE._test==null || ACTIVE._test.getTestClass()!=Chapter25Test.class ) return;
ACTIVE.record(code,"Chapter25",ACTIVE._test.getMethodName(),code._mach.getClass().getSimpleName(),code._callingConv);
}
@@ -54,7 +54,7 @@ public class SpillStats extends RunListener {
String[] row = line.split("\t");
try {
String src = Files.readString(dir.resolve(row[5]));
- CodeGen code = new CheckedCodeGen(null,null,null,src,123L,true,TypeInteger.BOT)
+ CodeGen code = new CheckedCodeGen(null,null,null,null,src,123L,true,TypeInteger.BOT)
.driver(CodeGen.Phase.RegAlloc,row[2],row[3]);
ACTIVE.record(code,row[0],row[1],row[2],row[3]);
} catch( Throwable error ) {
@@ -65,7 +65,7 @@ public class SpillStats extends RunListener {
}
// Include the library's allocation even when make's sys.o is up to date.
try {
- CodeGen code = new CheckedCodeGen("src/main/smp","build/spill-sys","sys",
+ CodeGen code = new CheckedCodeGen("src/main/smp","build/spill-sys",null,"sys",
Files.readString(Path.of("src/main/smp/sys.smp")),456L,true,TypeInteger.BOT)
.driver(CodeGen.Phase.Encoding,TestC.CPU_PORT,TestC.CALL_CONVENTION);
ACTIVE.record(code,"Chapter25","sys",TestC.CPU_PORT,TestC.CALL_CONVENTION);
@@ -75,7 +75,7 @@ public class SpillStats extends RunListener {
}
JUnitCore junit = new JUnitCore();
junit.addListener(ACTIVE);
- Result result = junit.run(ModulesTest.class);
+ Result result = junit.run(Chapter25Test.class);
for( Failure failure : result.getFailures() ) System.err.println(failure.getTrace());
failures += result.getFailureCount();
if( failures!=0 ) System.out.println("FAILED: totals are not a complete comparison.");
@@ -89,4 +89,4 @@ public class SpillStats extends RunListener {
ACTIVE = null;
if( failures!=0 ) System.exit(1);
}
-}
+} \ No newline at end of file
diff --git a/src/test/java/com/seaofnodes/simple/StaticDataTest.java b/src/test/java/com/seaofnodes/simple/StaticDataTest.java
index b8f5f1d1..88d0246d 100644
--- a/src/test/java/com/seaofnodes/simple/StaticDataTest.java
+++ b/src/test/java/com/seaofnodes/simple/StaticDataTest.java
@@ -69,7 +69,7 @@ public class StaticDataTest {
@Test public void testNativeImage() throws Exception {
Path dir=Path.of("build/objs/static-data");
Files.createDirectories(dir);
- CodeGen code=new CodeGen(null,dir.toString(),"Test",SRC,123L,true,
+ CodeGen code=new CodeGen(null,dir.toString(),null,"Test",SRC,123L,true,
TypeInteger.BOT)
.driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,false,false);
TypeStruct ts=(TypeStruct)root(code).type;
@@ -102,7 +102,7 @@ public class StaticDataTest {
for( int i=0; i<2; i++ ) {
String name=i==0 ? "A" : "B";
String src="{ i32 -> i32 } abs = \"C\"; val get"+name+"={ -> abs; }; return 0;";
- CodeGen code=new CodeGen(null,dir.toString(),name,src,123L,true,TypeInteger.BOT)
+ CodeGen code=new CodeGen(null,dir.toString(),null,name,src,123L,true,TypeInteger.BOT)
.driver(CodeGen.Phase.TypeCheck);
assertNull("The native address is initialized in the static image",
code._stop.walk(n -> n instanceof StoreNode st && st._name.equals("abs") ? n : null));
@@ -140,18 +140,19 @@ public class StaticDataTest {
}
}
- // Two source files must reference the same mutable class object.
+ // Two separately compiled users must reference the same mutable class object.
@Test public void testSharedClass() throws Exception {
Path dir=Path.of("build/objs/static-data-shared");
Files.createDirectories(dir);
- Files.createDirectories(dir.resolve("Main"));
+ Path lib=dir.resolve("lib");
+ Files.createDirectories(lib);
+ var paths=new Ary<>(new String[]{lib.toString()});
String[] names={"Globals","A","B"};
String[] sources={"int count=0;",
"val a={ -> Globals; };", "val b={ -> Globals; };"};
for( int i=0; i<names.length; i++ )
- Files.writeString(dir.resolve("Main/"+names[i]+".smp"),sources[i]);
- new CodeGen(dir.toString(),dir.toString(),"Main","val a={ -> A.a(); }; val b={ -> B.b(); };",123L,true,TypeInteger.BOT)
- .driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,false,false);
+ new CodeGen(null,(i==0?lib:dir).toString(),i==0?null:paths,names[i],sources[i],123L,true,TypeInteger.BOT)
+ .driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,false,false);
Path driver=dir.resolve("driver.c");
Files.writeString(driver,"""
#include <stdint.h>
@@ -164,9 +165,9 @@ public class StaticDataTest {
}
""");
String exe=dir.resolve(TestC.OS.startsWith("Windows")?"test.exe":"test").toString();
- TestC.linkExe(dir.resolve("Main.o").toString(),
+ TestC.linkExe(lib.resolve("Globals.o").toString(),
TestC.CALL_CONVENTION.equals("win64")?"ms_abi":"sysv_abi",
- driver.toString(),null,exe);
+ driver.toString(),new Ary<>(new String[]{dir+"/A.o",dir+"/B.o"}),exe);
assertEquals("",TestC.exec(TestC.TEST_TIMEOUT_SECONDS,exe));
}
}
diff --git a/src/test/java/com/seaofnodes/simple/TestC.java b/src/test/java/com/seaofnodes/simple/TestC.java
index 164168f6..b08be824 100644
--- a/src/test/java/com/seaofnodes/simple/TestC.java
+++ b/src/test/java/com/seaofnodes/simple/TestC.java
@@ -120,7 +120,7 @@ public abstract class TestC {
String obj = pathBase+".o";
String exe = pathBase+(OS.startsWith("Windows") ? ".exe" : "");
// Compile simple, emit ELF
- CodeGen code = new CheckedCodeGen(null,"build/objs",base,src,126L,true,TypeInteger.BOT);
+ CodeGen code = new CheckedCodeGen(null,"build/objs",externPaths,base,src,126L,true,TypeInteger.BOT);
code.driver( CPU_PORT, simple_conv, false, cfile==null );
linkExe(obj, c_conv, cfile, linkObjs(externPaths), exe);
diff --git a/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java b/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java
index 4398827e..292f1ae7 100644
--- a/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java
+++ b/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java
@@ -169,9 +169,9 @@ public class RegAllocTestSupport {
// Check the scheduled graph before encoding adds untyped branches or rewrites tail calls.
public static class CheckedCodeGen extends CodeGen {
public CheckedCodeGen(String src) { super(src); }
- public CheckedCodeGen(String modDir, String buildDir,
+ public CheckedCodeGen(String modDir, String buildDir, com.seaofnodes.simple.util.Ary<String> externPaths,
String srcName, String src, long seed, boolean reset, TypeInteger arg) {
- super(modDir,buildDir,srcName,src,seed,reset,arg);
+ super(modDir,buildDir,externPaths,srcName,src,seed,reset,arg);
}
@Override public CodeGen regAlloc() {
super.regAlloc();