summaryrefslogtreecommitdiff
path: root/src/test/java/com/seaofnodes/simple/SeparateCompilationTest.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/com/seaofnodes/simple/SeparateCompilationTest.java
parent741d78b6e840c88b7b183010c812ad982b342da1 (diff)
downloadSimple-linear-chapter28b.tar.gz
Simple-linear-chapter28b.zip
Chapter 28b: Separate Compilationlinear-chapter28blinear
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/com/seaofnodes/simple/SeparateCompilationTest.java')
-rw-r--r--src/test/java/com/seaofnodes/simple/SeparateCompilationTest.java90
1 files changed, 90 insertions, 0 deletions
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));
+ }
+}