1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
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));
}
}
|