summaryrefslogtreecommitdiff
path: root/chapter28b/src/test/java/com
diff options
context:
space:
mode:
Diffstat (limited to 'chapter28b/src/test/java/com')
-rw-r--r--chapter28b/src/test/java/com/seaofnodes/simple/Chapter25Test.java11
-rw-r--r--chapter28b/src/test/java/com/seaofnodes/simple/Chapter28bTest.java24
-rw-r--r--chapter28b/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java14
-rw-r--r--chapter28b/src/test/java/com/seaofnodes/simple/Eval2.java167
-rw-r--r--chapter28b/src/test/java/com/seaofnodes/simple/IncrementalBuildTest.java15
-rw-r--r--chapter28b/src/test/java/com/seaofnodes/simple/SelectiveLoadingTest.java113
-rw-r--r--chapter28b/src/test/java/com/seaofnodes/simple/SeparateCompilationTest.java1
-rw-r--r--chapter28b/src/test/java/com/seaofnodes/simple/Simple.java2
-rw-r--r--chapter28b/src/test/java/com/seaofnodes/simple/TestC.java57
9 files changed, 270 insertions, 134 deletions
diff --git a/chapter28b/src/test/java/com/seaofnodes/simple/Chapter25Test.java b/chapter28b/src/test/java/com/seaofnodes/simple/Chapter25Test.java
index 31a616b5..dbb2a5ca 100644
--- a/chapter28b/src/test/java/com/seaofnodes/simple/Chapter25Test.java
+++ b/chapter28b/src/test/java/com/seaofnodes/simple/Chapter25Test.java
@@ -198,7 +198,8 @@ public class Chapter25Test {
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" : "")));
+ (String)null,new Ary<>(new String[]{imported ? dir+"/Globals.o" : root+"/Main/Globals.o"}),
+ root+"/program"+(TestC.OS.startsWith("Windows") ? ".exe" : "")));
}
}
@@ -389,8 +390,9 @@ public class Chapter25Test {
// 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",
+ assertArrayEquals(new String[]{"sys/adt", "sys/ary",
"sys/aryi64", "sys/aryu8", "sys/char", "sys/io", "sys/libc", "sys/scan"},sys_elf._deps);
+ assertArrayEquals(new String[]{"sys"},sys_elf._units);
assertSame("class:sys" ,sys_elf._clz._name);
}
@@ -422,7 +424,7 @@ public class Chapter25Test {
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/",
+ Simple.main(new String[]{"-L",SYS_BLDDIR,"-o","build/objs/helloWorldDriverLibFile/",
"src/test/smp/examples/A_helloWorld.smp"});
} finally {
System.setOut(saved);
@@ -444,8 +446,7 @@ public class Chapter25Test {
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);
+ String rez = TestC.gcc(code,null,null,null,exe);
assertEquals(expected,rez);
}
diff --git a/chapter28b/src/test/java/com/seaofnodes/simple/Chapter28bTest.java b/chapter28b/src/test/java/com/seaofnodes/simple/Chapter28bTest.java
index fb418b9a..e0424573 100644
--- a/chapter28b/src/test/java/com/seaofnodes/simple/Chapter28bTest.java
+++ b/chapter28b/src/test/java/com/seaofnodes/simple/Chapter28bTest.java
@@ -1,23 +1,5 @@
package com.seaofnodes.simple;
-//
-//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 com.seaofnodes.simple.codegen.CodeGen;
import org.junit.Ignore;
import org.junit.Test;
@@ -39,9 +21,9 @@ struct C {
};
return new C(arg).x;
""";
- CodeGen code = new CodeGen(src).driver(CodeGen.Phase.Encoding,"riscv","SystemV");
- assertEquals("return (arg+3);", code.print());
- assertEquals("3",Eval2.eval(code,0));
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("Stop[ return Phi(Loop,0,Phi(Region,1,Phi_x)); return MEM[ 2:___ 3:.x=Phi(Loop,0,Phi(Region,1,Phi_x));]; return MEM[ 2:___ 3:.x=0;]; return Test.C; ]", code.print());
+ assertEquals("1",Eval2.eval(code,3));
}
}
diff --git a/chapter28b/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java b/chapter28b/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java
index 47e10f97..7f5fcd38 100644
--- a/chapter28b/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java
+++ b/chapter28b/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java
@@ -443,7 +443,7 @@ public class ClassInitDependenciesTest {
"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));
+ assertNotNull("Eval executes CLInit without lowering it",code._stop.walk(n -> n instanceof CLInitNode ? n : null));
assertEquals("134",Eval2.eval(code,1));
}
@@ -468,8 +468,13 @@ public class ClassInitDependenciesTest {
@Test public void testParentInitializationExecution() throws Exception {
Path dir=tempDir("execution-");
object(dir,dir,"State","int count=0;");
- 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;"));
+ CodeGen library = 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; return State.count;"));
+ // Direct entry into the child has no surrounding CLInit call/check.
+ // Its body must establish the parent itself, including an imported parent.
+ CodeGen child = new CodeGen(library._modDir,dir.toString(),new Ary<>(new String[]{dir.toString()}),
+ "P/C",null,123,true,TypeInteger.BOT).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("12",Eval2.eval(child,99));
for( long seed : new long[]{1,123,987654321} ) {
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);
@@ -484,7 +489,8 @@ public class ClassInitDependenciesTest {
""");
String exe=dir.resolve(TestC.OS.startsWith("Windows") ? "check.exe" : "check").toString();
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);
+ cfile.toString(),new Ary<>(new String[]{dir.resolve("P.o").toString(),dir.resolve("P/C.o").toString(),
+ dir.resolve("State.o").toString()}),exe);
assertEquals("",TestC.exec(exe));
}
diff --git a/chapter28b/src/test/java/com/seaofnodes/simple/Eval2.java b/chapter28b/src/test/java/com/seaofnodes/simple/Eval2.java
index 901a8990..fc87c13a 100644
--- a/chapter28b/src/test/java/com/seaofnodes/simple/Eval2.java
+++ b/chapter28b/src/test/java/com/seaofnodes/simple/Eval2.java
@@ -1,9 +1,10 @@
package com.seaofnodes.simple;
-import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.*;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.print.IRPrinter;
import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.Ary;
import com.seaofnodes.simple.util.SB;
import com.seaofnodes.simple.util.Utils;
import java.util.*;
@@ -11,7 +12,7 @@ import java.util.*;
public abstract class Eval2 {
// A mapping from Node ID to evaluation data Object.
- // Data Objsts are a boxed Long or Double; or an Object[] of fields, which
+ // Data Objects are a boxed Long or Double; or an Object[] of fields, which
// themselves are just more Objects.
private static class Frame {
private static int UID=1;
@@ -77,31 +78,24 @@ public abstract class Eval2 {
}
}
- // Classic Closure; an environment (stack of frames) and an execution point
- private static class Closure {
- // Where to return too; either a CallEnd or a Function
+ // A Classic Continuation; the suspended caller and the control point to
+ // resume after a return.
+ private static class Continuation {
+ // Resume a CallEnd or a CLInit's successor, or exit at Stop.
final CFGNode _cc;
- // Stack frame at the execution point. Includes Closures which
+ // Stack frame at the execution point. Includes Continuations which
// recursively include other stack frames
- private Frame _frame;
+ final Frame _frame;
- Closure(CFGNode cc, Frame frame) {
- _cc = cc; // One of Fun, CallEnd or Stop (null)
+ Continuation(CFGNode cc, Frame frame) {
+ _cc = cc;
_frame = frame;
assert _frame._refcnt >=0;
_frame._refcnt++;
}
- FunNode fun() { return (FunNode)_cc; }
- CallEndNode cend() { return (CallEndNode)_cc; }
- boolean isStop() { return _cc instanceof StopNode; } // or Stop
-
- //
- void pushFrame() {
- _frame = new Frame(_frame);
- _frame._refcnt++;
- }
+ boolean isStop() { return _cc instanceof StopNode; }
@Override public String toString() {
return _cc.toString();
@@ -119,12 +113,15 @@ public abstract class Eval2 {
public static String eval( CodeGen code, long arg ) { return eval(code,arg,1000); }
public static String eval( CodeGen code, long arg, int timeout ) {
+ code.driver(CodeGen.Phase.TypeCheck);
if( code._start.uctrl()==null ) return ""; // The empty program
SB trace = null; // new SB(); // TRACE, set to null for off, new SB() for on
- // Force local scheduling phase
- code.driver(CodeGen.Phase.LocalSched);
// Set global, so don't have to pass everywhere
CODE = code;
+ // Eval uses checked ideal IR, including CLInit, and does not run Lower.
+ // Objects loaded later are scheduled independently of this graph.
+ if( code._phase.ordinal()<CodeGen.Phase.Schedule.ordinal() ) code.scheduleForEval();
+ code.driver(CodeGen.Phase.LocalSched);
CLASSES.clear();
// Timer for when we run too long. Ticks once per backedge or function call
@@ -134,18 +131,12 @@ public abstract class Eval2 {
// fields, which themselves are just more Objects.
F = new Frame(null);
- // Start at the START
- CFGNode BB = code._start, prior = null;
- // Return from main hits Stop
- F.put(BB,new Closure(code._stop,F));
- // For all <clinit> / Clazzes, install their uninitialized memory
- for( FunNode fun : code._linker )
- if( fun!=null && fun.isClz() ) {
- TypeStruct clz = ((TypeMemPtr)fun.sig().arg(0))._obj;
- // Map from the <clinit> self parameter to the
- // singleton class structure.
- F.put(fun.parm(2),classObject(clz));
- }
+ FunNode entry = (FunNode)code._start.uctrl();
+ VALUE_CACHE.setX(0,"$mem");
+ VALUE_CACHE.setX(1,classObject(((TypeMemPtr)entry.sig().arg(0))._obj));
+ VALUE_CACHE.setX(2,arg);
+ CFGNode BB = enter(entry,code._stop);
+ CFGNode prior = null;
// ------------
@@ -158,7 +149,7 @@ public abstract class Eval2 {
// Parallel assign Phis
int i = BB instanceof RegionNode r
- ? parallelAssignPhis(r,prior,arg,trace)
+ ? parallelAssignPhis(r,prior,trace)
: 0;
// Compute the data nodes until BB control
@@ -170,33 +161,38 @@ public abstract class Eval2 {
prior = BB;
BB = BB.uctrl(); // Next unique control target
switch( prior ) {
- case StartNode start: F = new Frame(F); break; // Frame for the call to main, as-if Called
case CallNode call : BB = call(call); break;
+ case CLInitNode init: BB = call(init); break;
case NeverNode never: BB = never.cproj(0); break; // Synthetic exit is never taken.
case IfNode iff : Object p = val(iff.pred()); BB = iff.cproj( p==null || (p instanceof Long x && x==0L) ? 1 : 0); break;
- case ReturnNode ret : if( clj(ret.rpc()).isStop() ) return exit(ret); else BB = ret(ret); break;
+ case ReturnNode ret : if( continuation(ret).isStop() ) return exit(ret); else BB = ret(ret); break;
case FunNode fun : if( loopCnt++ > timeout ) return null; break; // Timeout
case LoopNode loop : if( loopCnt++ > timeout ) return null; break; // Timeout
case CallEndNode cend: break;
case CProjNode cproj: break;
case RegionNode r : break;
- default: throw Utils.TODO();
+ default: throw Utils.TODO("Eval control: "+prior.getClass().getSimpleName());
}
if( BB==null ) return null;
assert !BB.isDead();
}
}
-
- // Phi cache; size is limited to number of Phis at a merge point
- private static final Object[] PHICACHE = new Object[256];
- static int parallelAssignPhis(RegionNode r, CFGNode prior, long arg, SB trace) {
+ // Scratch values for parallel Phi assignment and call arguments. Each use
+ // is consumed before the next block executes; loading only schedules code.
+ private static final Ary<Object> VALUE_CACHE = new Ary<>(Object.class);
+ static int parallelAssignPhis(RegionNode r, CFGNode prior, SB trace) {
+
+ // enter() assigned parameters, including the return continuation,
+ // from runtime arguments rather than the compiler's call graph.
+ if( r instanceof FunNode ) {
+ int i = 0;
+ while( i<r.nOuts() && r.out(i) instanceof ParmNode ) i++;
+ return i;
+ }
// Compute input path on Phis for this Region
int path = r._inputs.find( prior );
- // Parameters read from prior frame, Phis from local frame
- Frame frame = (r instanceof FunNode ? F._prior : F);
- boolean isClz = r instanceof FunNode fun && fun.isClz();
// Parallel assign Phis. First parallel read and cache
int i;
@@ -205,26 +201,11 @@ public abstract class Eval2 {
// non-Phi is the end of all Phis.
if( !(r.out(i) instanceof PhiNode phi) ) break;
- // Parms read from Call args -
- // - except RPC is the actual prior frame
- // - except entry <clinit> takes the external program argument
- Object val;
- if( isClz && prior instanceof StartNode && phi instanceof ParmNode parm && parm._idx==3 )
- PHICACHE[i] = arg; // Reading the initial arguments to <clinit>
- else {
- Node n = phi instanceof ParmNode parm
- // RPC uses the prior Call directly;
- // Parms may not be linked, so read call args directly
- ? (parm._idx==0 ? prior : prior.in(parm._idx))
- // Phis read from path input
- : phi.in(path);
- // Read the frame before overwriting
- PHICACHE[i] = frame.get(n);
- }
+ VALUE_CACHE.setX(i,F.get(phi.in(path)));
}
// Parallel assign; might assign before read, so read from cache
for( int j=0; j < i; j++ )
- traceData(F.put0(r.out(j),PHICACHE[j]),trace);
+ traceData(F.put0(r.out(j),VALUE_CACHE.at(j)),trace);
// Return point in basic block past last Phi; the Phis have been
// evaluated here, and now we need to evaluate the rest of the block
return i;
@@ -233,27 +214,67 @@ public abstract class Eval2 {
// Make a call.
private static CFGNode call( CallNode call ) {
- // Set up the Return PC and return frame (which is just the current frame).
- // Really making a Continuation, but it's just a Closure
- Closure cont = new Closure(call.cend(),F);
- // Set the return continuation into the current frame RPC
- F.put(call, cont );
- // Next Frame to use
- F = new Frame(F);
- // Target function and environment
- return CODE.link(tfp(call.fptr()));
+ int nargs = call.nargs()+1; // Include memory.
+ for( int i = 0; i<nargs; i++ ) VALUE_CACHE.setX(i,val(call.in(i+1)));
+ return enter(CODE.link(tfp(call.fptr())),call.cend());
+ }
+
+ // Interpret the flag-test/call diamond that Lower would emit. The taken
+ // arm uses the ordinary call entry and RPC continuation. The initializer
+ // itself initializes its parent and sets its own $init flag.
+ private static CFGNode call(CLInitNode init) {
+ CompUnit cu = init.cu();
+ //if( cu==null ) return init.uctrl();
+ assert cu != null; // Post-optimizer the remaining CLInits refer to real classes
+ Object[] obj = classObject(cu._clz);
+ if( obj[cu._clz.find("$init")].equals(1L) )
+ return init.uctrl(); // Class already initialized!
+ VALUE_CACHE.setX(0,"$mem");
+ VALUE_CACHE.setX(1,obj);
+ VALUE_CACHE.setX(2,0L);
+ return enter(CODE.link(cu),init.uctrl());
+ }
+
+
+ private static FunNode enter(FunNode fun, CFGNode continuation) {
+ // If we find a Summary, then this Fun's code never got loaded.
+ // Load it now.
+ if( fun.summary()!=null ) {
+ int fidx = fun.sig().fidx();
+ // Load the matching .o, and all its CompUnits, and all the
+ // included FunNodes, including a replacement for this one.
+ ElfReader elf = SummaryNode.load(CODE,fun);
+ // Object files contain checked ideal IR, before scheduling. This is
+ // the first schedule for this object's bodies, even though the caller
+ // is already executing. Schedule only the new graph: revisiting the
+ // caller could change nodes whose values live in an active Frame.
+ // No optimization, CLInit lowering, or finalization.
+ ListScheduler.sched(GlobalCodeMotion.forEval(CODE,(StartNode)elf._nodes.at(0),(StopNode)elf._nodes.last()));
+ // Get the new replacement FunNode
+ fun = CODE.link(fidx);
+ }
+ Continuation cont = new Continuation(continuation,F);
+ F=new Frame(F);
+ for( Node use : fun._outputs )
+ if( use instanceof ParmNode parm )
+ F.put0(parm,parm._idx==0 ? cont : VALUE_CACHE.at(parm._idx-1));
+ return fun;
}
private static CFGNode ret( ReturnNode ret ) {
// Fetch return expression value and continuation from current frame
Object rez = val(ret.expr());
- Closure cont = clj(ret.rpc());
+ Continuation cont = continuation(ret);
// Lower Frame reference count; might delete frame
F.dec(); // Lower ref count
// Install replacement Frame; set return result over the CallEnd
F = cont._frame;
- F.put0(cont._cc,rez); // Set return value into CallEnd
- return cont._cc; // Return CallEnd as the new control
+ if( cont._cc instanceof CallEndNode ) F.put0(cont._cc,rez);
+ return cont._cc; // Resume after the call.
+ }
+
+ private static Continuation continuation(ReturnNode ret) {
+ return (Continuation)val(ret.rpc());
}
private static String exit( ReturnNode ret ) {
@@ -329,8 +350,6 @@ public abstract class Eval2 {
static TypeFunPtr tfp(Node n) {
return (TypeFunPtr)F.get(n);
}
- // Fetch and unbox a closure
- static Closure clj(Node n) { return (Closure)F.get(n); }
// Java box ints and floats. Other things can just be null or some informative string
private static Object con( Type t ) {
diff --git a/chapter28b/src/test/java/com/seaofnodes/simple/IncrementalBuildTest.java b/chapter28b/src/test/java/com/seaofnodes/simple/IncrementalBuildTest.java
index 501392f3..3c53c004 100644
--- a/chapter28b/src/test/java/com/seaofnodes/simple/IncrementalBuildTest.java
+++ b/chapter28b/src/test/java/com/seaofnodes/simple/IncrementalBuildTest.java
@@ -42,23 +42,26 @@ public class IncrementalBuildTest {
assertNotNull("Reparse A before loading its stale inlined body",code._compunits.get("App/A")._src);
assertNotNull(code._compunits.get("App/B")._src);
compile(src,obj,"App/Main");
- checkNative(obj,new Ary<>(new String[]{obj.resolve("App.o").toString()}));
+ checkNative(obj);
Path clean=Files.createDirectory(root.resolve("clean"));
assertEquals("9",Eval2.eval(client(src,clean),0));
compile(src,clean,"App/Main");
- checkNative(clean,null);
+ checkNative(clean);
}
- @Test public void testBundledSourceChangeAndCycle() throws Exception {
+ @Test public void testSourceChangeAndCycle() throws Exception {
Path root=fixture("bundle-");
Path src=root.resolve("src"), obj=root.resolve("obj");
write(src,"App/A","val value={int n -> n ? B.value(n-1) : 1; };",1000);
write(src,"App/B","val value={int n -> n ? A.value(n-1) : 5; };",1000);
write(src,"App/Main","return A.value(1);",1000);
compile(src,obj,"App/A");
+ assertEquals("One build timestamp for the cycle",Files.getLastModifiedTime(obj.resolve("App/A.o")),
+ Files.getLastModifiedTime(obj.resolve("App/B.o")));
stamp(obj.resolve("App/A.o"),3000);
- assertFalse("A owns both sides of the cycle",Files.exists(obj.resolve("App/B.o")));
+ assertTrue("Each side of the cycle owns an object",Files.exists(obj.resolve("App/B.o")));
+ stamp(obj.resolve("App/B.o"),3000);
CodeGen code=client(src,obj);
assertEquals("5",Eval2.eval(code,0));
assertNull(code._compunits.get("App/A")._src);
@@ -125,13 +128,15 @@ public class IncrementalBuildTest {
.driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,false,false);
}
- private static void checkNative(Path obj, Ary<String> libs) throws Exception {
+ private static void checkNative(Path obj) throws Exception {
Path driver=obj.resolve("driver.c");
Files.writeString(driver,"""
__attribute__((CALL_CONV)) extern long long run(void) __asm__("App.Main.run");
int main(void) { return run()==9 ? 0 : 1; }
""");
String exe=obj.resolve(TestC.OS.startsWith("Windows")?"test.exe":"test").toString();
+ Ary<String> libs = new Ary<>(new String[]{obj.resolve("App.o").toString(),
+ obj.resolve("App/A.o").toString(),obj.resolve("App/B.o").toString(),obj.resolve("App/C.o").toString()});
TestC.linkExe(obj.resolve("App/Main.o").toString(),
TestC.CALL_CONVENTION.equals("win64")?"ms_abi":"sysv_abi",driver.toString(),libs,exe);
assertEquals("",TestC.exec(TestC.TEST_TIMEOUT_SECONDS,exe));
diff --git a/chapter28b/src/test/java/com/seaofnodes/simple/SelectiveLoadingTest.java b/chapter28b/src/test/java/com/seaofnodes/simple/SelectiveLoadingTest.java
new file mode 100644
index 00000000..7d42f924
--- /dev/null
+++ b/chapter28b/src/test/java/com/seaofnodes/simple/SelectiveLoadingTest.java
@@ -0,0 +1,113 @@
+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.io.IOException;
+import java.nio.file.*;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class SelectiveLoadingTest {
+ @Test public void testCycleRefinementLoadsConditionHelper() throws Exception {
+ Path base = Path.of("build/objs/selective");
+ Files.createDirectories(base);
+ Path root = Files.createTempDirectory(base,"cycle-");
+ Path stubs = Files.createDirectory(root.resolve("stubs"));
+ Path lib = Files.createDirectory(root.resolve("lib"));
+ object(stubs,null,"A","int value=arg;");
+ object(stubs,null,"B","int value=arg;");
+ StringBuilder helper = new StringBuilder("val test={bool n -> ");
+ // Large parse, small final body: it will not request speculative loading.
+ for( int i = 0; i<80; i++ ) helper.append("val x").append(i).append("=n+0;");
+ helper.append("return n;};");
+ object(lib,null,"Predicate",helper.toString());
+ Ary<String> inputs = new Ary<>(new String[]{stubs.toString(),lib.toString()});
+ object(lib,inputs,"A","int value=1; if(Predicate.test(false)) value=B.value;");
+ object(lib,inputs,"B","int value=A.value;");
+ CodeGen code = new CodeGen(null,root.toString(),new Ary<>(new String[]{lib.toString()}),
+ "Main","return A.value;",123,true,TypeInteger.BOT).driver(CodeGen.Phase.FinalizeIR);
+ assertNotNull(function(code,"test").summary());
+ assertNotNull("Summaries alone report a potential cycle",new ClassInitDependencies(code).check());
+ code.driver(CodeGen.Phase.TypeCheck);
+ assertNull("The helper is outside A/B's apparent cycle",function(code,"test").summary());
+ assertEquals("1",Eval2.eval(code,0));
+ }
+
+ private static void object(Path dir, Ary<String> paths, String name, String source) {
+ new CodeGen(null,dir.toString(),paths,name,source,123,true,TypeInteger.BOT)
+ .driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,false,false);
+ }
+
+ @Test public void testSummariesInliningAndStaticLink() throws Exception {
+ Path base = Path.of("build/objs/selective");
+ Files.createDirectories(base);
+ Path root = Files.createTempDirectory(base,"library-");
+ Path src = Files.createDirectory(root.resolve("src"));
+ Path lib = Files.createDirectory(root.resolve("lib"));
+ Path app = Files.createDirectory(root.resolve("app"));
+ write(src,"Lib","val api=Api; val work=Work; val unused=Unused;");
+ write(src,"Lib/Api","""
+ val p={int n -> Work.write_noInline(n);};
+ val p_noInline={int n -> Work.write_noInline(n);};
+ """);
+ write(src,"Lib/Work","val write_noInline={int n -> n+1;};");
+ write(src,"Lib/Unused","val f={int n -> n*2;};");
+ new CodeGen(src.toString(),lib.toString(),null,"Lib",null,123,true,TypeInteger.BOT)
+ .driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,false,false);
+ for( String unit : new String[]{"Lib","Lib/Api","Lib/Work","Lib/Unused"} )
+ assertTrue(unit,Files.isRegularFile(lib.resolve(unit+".o")));
+
+ Ary<String> paths = new Ary<>(new String[]{lib.toString()});
+ CodeGen code = new CodeGen(null,app.toString(),paths,"Main",
+ "if(arg==0) return -1; return Lib.api.p_noInline(arg);",123,true,TypeInteger.BOT).driver(CodeGen.Phase.TypeCheck);
+ assertNotNull(function(code,"Lib.Api.p_noInline").summary());
+ assertNotNull(function(code,"Lib.Work.write_noInline").summary());
+ assertNotNull(function(code,"Lib.Unused.f").summary());
+ assertEquals("-1",Eval2.eval(code,0));
+ assertNotNull("An untaken branch does not load its target",function(code,"Lib.Api.p_noInline").summary());
+ assertEquals("42",Eval2.eval(code,41));
+ assertNull(function(code,"Lib.Work.write_noInline").summary());
+ assertNotNull("Evaluation leaves unrelated objects unloaded",function(code,"Lib.Unused.f").summary());
+ assertEquals("6",Eval2.eval(code,5));
+
+ code=new CodeGen(null,app.toString(),paths,"Main",
+ "val run={int n -> Lib.api.p(n);}; return run(arg);",123,true,TypeInteger.BOT)
+ .driver(CodeGen.Phase.TypeCheck);
+ assertNull("p requested Api's implementation",function(code,"Lib.Api.p").summary());
+ assertNotNull("The remaining call needs a signature, not a body",function(code,"Lib.Work.write_noInline").summary());
+ assertNotNull("A namespace reexport does not request IR",function(code,"Lib.Unused.f").summary());
+ code.driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,false,false);
+ Path driver = app.resolve("driver.c");
+ Files.writeString(driver,"""
+ __attribute__((CALL_CONV)) extern long long run(long long);
+ int main(void) { return run(41)==42 ? 0 : 1; }
+ """);
+ String exe = app.resolve(TestC.OS.startsWith("Windows") ? "test.exe" : "test").toString();
+ assertTrue("The compiler already resolved this transitive object",
+ TestC.linkObjs(code).contains(lib.resolve("Lib/Work.o").toRealPath().toString()));
+ assertEquals("",TestC.gcc(code,
+ TestC.CALL_CONVENTION.equals("win64") ? "ms_abi" : "sysv_abi",driver.toString(),null,exe));
+
+ Files.delete(lib.resolve("Lib/Work.o"));
+ // Even an untaken branch requires a complete dependency closure for a
+ // fresh program.
+ try {
+ Eval2.eval(new CodeGen(null,app.toString(),paths,"Missing",
+ "if(arg==0) return -1; return Lib.api.p_noInline(arg);",123,true,TypeInteger.BOT),0);
+ fail("Missing transitive code must fail before evaluation");
+ } catch( RuntimeException ex ) { assertTrue(ex.getMessage(),ex.getMessage().contains("Lib/Work")); }
+ }
+
+ private static FunNode function(CodeGen code, String name) {
+ for( FunNode fun : code._linker ) if( fun!=null && name.equals(fun._name) ) return fun;
+ throw new AssertionError("Missing function "+name);
+ }
+
+ private static void write(Path src, String unit, String text) throws IOException {
+ Path file = src.resolve(unit+".smp");
+ Files.createDirectories(file.getParent());
+ Files.writeString(file,text);
+ }
+}
diff --git a/chapter28b/src/test/java/com/seaofnodes/simple/SeparateCompilationTest.java b/chapter28b/src/test/java/com/seaofnodes/simple/SeparateCompilationTest.java
index 4576bc94..4e8a0417 100644
--- a/chapter28b/src/test/java/com/seaofnodes/simple/SeparateCompilationTest.java
+++ b/chapter28b/src/test/java/com/seaofnodes/simple/SeparateCompilationTest.java
@@ -30,6 +30,7 @@ public class SeparateCompilationTest {
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);
+ code.loadAllIR(); // This assertion inspects a body, not just its saved summary.
assertNull(code._compunits.get("Callbacks")._src);
ParmNode base=code._start.walk(n -> n instanceof ParmNode p && p._label.equals("base") ? p : null);
assertNotNull(base);
diff --git a/chapter28b/src/test/java/com/seaofnodes/simple/Simple.java b/chapter28b/src/test/java/com/seaofnodes/simple/Simple.java
index 7cfdf57d..1f1e8ab0 100644
--- a/chapter28b/src/test/java/com/seaofnodes/simple/Simple.java
+++ b/chapter28b/src/test/java/com/seaofnodes/simple/Simple.java
@@ -257,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(externPaths), exe);
+ String result = TestC.gcc(code, null, null, "", exe);
System.out.print(result);
}
}
diff --git a/chapter28b/src/test/java/com/seaofnodes/simple/TestC.java b/chapter28b/src/test/java/com/seaofnodes/simple/TestC.java
index b08be824..54adf3af 100644
--- a/chapter28b/src/test/java/com/seaofnodes/simple/TestC.java
+++ b/chapter28b/src/test/java/com/seaofnodes/simple/TestC.java
@@ -2,6 +2,8 @@ package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.RegAllocTestSupport.CheckedCodeGen;
import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.CompUnit;
+import com.seaofnodes.simple.codegen.ElfReader;
import com.seaofnodes.simple.type.TypeInteger;
import com.seaofnodes.simple.util.Ary;
import com.seaofnodes.simple.util.Utils;
@@ -10,6 +12,7 @@ import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.HashSet;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.*;
@@ -117,13 +120,12 @@ public abstract class TestC {
// build/objs/foo.exe - linked executable Windows
// build/objs/foo - linked executable Linux
String pathBase = "build/objs/"+base;
- String obj = pathBase+".o";
String exe = pathBase+(OS.startsWith("Windows") ? ".exe" : "");
// Compile simple, emit ELF
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);
+ linkExe(linkObjs(code), c_conv, cfile, exe);
// Allocation quality not degraded
SpillStats.checkSpills(spills,code._regAlloc._spillScaled);
@@ -137,7 +139,7 @@ public abstract class TestC {
public static String gcc( String main, double ignore, String... objs ) throws IOException {
String BLDDIR = "build/objs/test0/";
String exe = BLDDIR+main+(OS.startsWith("Windows") ? ".exe" : "");
- linkExe(objs[0],null,null,null,exe);
+ linkExe(new Ary<>(objs),null,null,exe);
// Module tests intentionally use the program exit code as their result.
ExecResult result = execute(null,0,exe);
return result.exit==0 ? result.out : "exec exit code: "+result.exit;
@@ -160,7 +162,21 @@ public abstract class TestC {
return execStdin(stdin,0,args);
}
- public static void linkExe( String obj, String c_conv, String cfile, Ary<String> linkObjs, String exe ) throws IOException {
+ // Immediate compile/link: the compiler already resolved every Simple object.
+ public static String gcc(CodeGen code, String c_conv, String cfile, String stdin, String... args) throws IOException {
+ linkExe(linkObjs(code),c_conv,cfile,args[0]);
+ return execStdin(stdin,0,args);
+ }
+
+ // Object-only tests supply every link input explicitly.
+ public static void linkExe( String entry, String c_conv, String cfile, Ary<String> libraries, String exe ) throws IOException {
+ Ary<String> objects = new Ary<>(String.class);
+ objects.add(entry);
+ if( libraries!=null ) objects.addAll(libraries);
+ linkExe(objects,c_conv,cfile,exe);
+ }
+
+ private static void linkExe(Ary<String> objects, String c_conv, String cfile, String exe) throws IOException {
// Compile the C program. Compiling code and constants in the low
// 2Gig. Pointers are 64b BUT since always in the low 2G all the
// high bits are zero - and Simple code can be emitted treating
@@ -169,18 +185,16 @@ public abstract class TestC {
params.add("gcc");
params.add(OS.startsWith("Windows") ? "-Wl,--image-base,0x400000" : "-no-pie");
if( cfile!=null ) params.add(cfile); // Associated C driver, usually has a `main`
- params.add(obj);
+ params.addAll(objects);
// Standalone Simple programs export `simple_main`; the small C
// runtime owns the native main symbol and converts argc/argv into a
// normal Simple array before making the ABI-qualified call.
if( cfile==null ) params.add(runtimeObject());
- if( linkObjs != null )
- params.addAll(linkObjs.asAry());
params.addAll(new String[] {
"-lm", // Picks up 'sqrt' for newtonFloat tests to compare
- "-g",
- "-o",
- exe,
+ "-g", // debug symbols
+ "-o", // name the output file
+ exe, // name the output file
});
// Calling convention for C calls, if any
if( cfile!=null ) {
@@ -254,20 +268,15 @@ public abstract class TestC {
}
}
- static Ary<String> linkObjs(Ary<String> externPaths) {
- if( externPaths == null )
- return null;
- Ary<String> objs = new Ary<>(String.class);
- for( String path : externPaths ) {
- File file = new File(path);
- if( file.isFile() && file.getName().endsWith(".o") )
- objs.add(file.toString());
- else if( file.isDirectory() ) {
- File sys = new File(file,"sys.o");
- if( sys.isFile() )
- objs.add(sys.toString());
- }
+ // Gather all CompUnit files and all externally named symbols
+ static Ary<String> linkObjs(CodeGen code) throws IOException {
+ Ary<String> objNames = new Ary<>(String.class);
+ for( CompUnit cu : code._compunits.values() ) {
+ File obj = cu._obj;
+ if( obj==null && cu._src!=null ) // Source supplied as a string.
+ obj = new File(code._buildDir+"/"+(code._srcName==null ? "Test" : code._srcName)+".o");
+ if( obj!=null ) objNames.add(obj.getCanonicalPath());
}
- return objs.isEmpty() ? null : objs;
+ return objNames;
}
}