summaryrefslogtreecommitdiff
path: root/chapter29/src/test/java/com/seaofnodes/simple/StaticDataTest.java
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-11-01 10:33:55 -0800
committerCliff Click <cliffc@acm.org>2026-11-01 10:33:55 -0800
commitf4bd723d929e53475f7990374dca7b7dfcbbdc42 (patch)
tree7bb080119e7b88ad4ac9b30af019fe6a5e4ba9dd /chapter29/src/test/java/com/seaofnodes/simple/StaticDataTest.java
parent0ac0c9e10287972ac3af5a4ee43d26f0d5b318fe (diff)
downloadSimple-main.tar.gz
Simple-main.zip
Add Chapter 29 for tech debt cleanup with the parser splitmain
Keep Chapter 28b intact and carry the grammar-oriented parser package, Var move, IterPeeps move, and parser regression tests into Chapter 29. Defer privacy and exclude its experimental tests and fixture changes. Register the chapter in the indexes, IntelliJ modules and run configurations, Maven reactor, roadmap, and linear-history publisher. Windows and Linux each pass 573 tests, release builds, 199 allocation replays, and 29 native checks.
Diffstat (limited to 'chapter29/src/test/java/com/seaofnodes/simple/StaticDataTest.java')
-rw-r--r--chapter29/src/test/java/com/seaofnodes/simple/StaticDataTest.java173
1 files changed, 173 insertions, 0 deletions
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/StaticDataTest.java b/chapter29/src/test/java/com/seaofnodes/simple/StaticDataTest.java
new file mode 100644
index 00000000..88d0246d
--- /dev/null
+++ b/chapter29/src/test/java/com/seaofnodes/simple/StaticDataTest.java
@@ -0,0 +1,173 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.StaticData;
+import com.seaofnodes.simple.node.ConstantNode;
+import com.seaofnodes.simple.node.StoreNode;
+import com.seaofnodes.simple.type.TypeMem;
+import com.seaofnodes.simple.type.TypeStruct;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.isa.eval.EvalArm64;
+import com.seaofnodes.isa.eval.EvalRisc5;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class StaticDataTest {
+ private static final String SRC="val n=42; val f={int x -> x+n;}; return 0;";
+
+ private static StaticData.ObjectData root(CodeGen code) {
+ for( var obj : code._encoding._data.entries() )
+ if( obj.symbol.endsWith("Test.$class") ) return obj;
+ throw new AssertionError("Missing Test class object");
+ }
+
+ // Read the image before <clinit>, then call the function through its data slot.
+ @Test public void testEmulatorImage() {
+ for( String cpu : new String[]{"arm","riscv"} ) {
+ CodeGen code=new CodeGen(SRC).driver(CodeGen.Phase.TypeCheck);
+ assertNull("Constant fields fold before instruction selection",
+ code._stop.walk(n -> n instanceof StoreNode st &&
+ (st._name.equals("n") || st._name.equals("f")) ? n : null));
+ assertNotNull(code._stop.walk(n -> n instanceof ConstantNode &&
+ n._type instanceof TypeMem mem && mem.isConstant() &&
+ mem._t==TypeInteger.constant(42) ? n : null));
+ assertNotNull("The runtime initialization flag still needs its Store",
+ code._stop.walk(n -> n instanceof StoreNode st &&
+ st._name.equals("$init") ? n : null));
+ code.driver(cpu,"SystemV",true,false);
+ var e=code._encoding;
+ var obj=root(code);
+ TypeStruct ts=(TypeStruct)obj.type;
+ int cp=(e._bits.size()+15)&-16, sd=(cp+e._cpool.size()+15)&-16;
+ byte[] image=new byte[1<<20];
+ System.arraycopy(e._bits.buf(),0,image,0,e._bits.size());
+ System.arraycopy(e._cpool.buf(),0,image,cp,e._cpool.size());
+ System.arraycopy(e._sdata.buf(),0,image,sd,e._sdata.size());
+ int n=sd+obj.offset+ts.offset(ts.find("n"));
+ int f=sd+obj.offset+ts.offset(ts.find("f"));
+ if( cpu.equals("arm") ) {
+ var arm=new EvalArm64(image,1<<16);
+ assertEquals(42,arm.ld4s(n));
+ arm._pc=arm.ld4s(f);
+ arm.regs[0]=7;
+ assertEquals(0,arm.step(1000));
+ assertEquals(49,arm.regs[0]);
+ } else {
+ var r5=new EvalRisc5(image,1<<16);
+ assertEquals(42,r5.ld4s(n));
+ r5._pc=r5.ld4s(f);
+ r5.regs[10]=7;
+ assertEquals(0,r5.step(1000));
+ assertEquals(49,r5.regs[10]);
+ }
+ }
+ }
+
+ @Test public void testNativeImage() throws Exception {
+ Path dir=Path.of("build/objs/static-data");
+ Files.createDirectories(dir);
+ 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;
+ Path driver=dir.resolve("driver.c");
+ Files.writeString(driver,"""
+ #include <stdint.h>
+ #include <string.h>
+ extern unsigned char data[] __asm__("Test.$class");
+ typedef int64_t (__attribute__((CALL_CONV)) *fun)(int64_t);
+ int main(void) {
+ int64_t n; uint32_t f;
+ memcpy(&n,data+%d,sizeof(n));
+ memcpy(&f,data+%d,sizeof(f));
+ return n!=42 || ((fun)(uintptr_t)f)(7)!=49;
+ }
+ """.formatted(ts.offset(ts.find("n")),ts.offset(ts.find("f"))));
+ String exe=dir.resolve(TestC.OS.startsWith("Windows")?"test.exe":"test").toString();
+ TestC.linkExe(dir.resolve("Test.o").toString(),
+ TestC.CALL_CONVENTION.equals("win64")?"ms_abi":"sysv_abi",
+ driver.toString(),null,exe);
+ assertEquals("",TestC.exec(TestC.TEST_TIMEOUT_SECONDS,exe));
+ }
+
+ // Native entries must agree too, both in static fields (before <clinit>)
+ // and when code materializes the address. The native body may live above 4G.
+ @Test public void testNativeFunctionIdentity() throws Exception {
+ Path dir=Path.of("build/objs/static-native-functions");
+ Files.createDirectories(dir);
+ int[] offsets=new int[2];
+ 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(),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));
+ code.driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,false,false);
+ for( var obj : code._encoding._data.entries() )
+ if( obj.symbol.equals(name+".$class") ) {
+ TypeStruct ts=(TypeStruct)obj.type;
+ offsets[i]=ts.offset(ts.find("abs"));
+ }
+ }
+ Path driver=dir.resolve("driver.c");
+ Files.writeString(driver,"""
+ #include <stdint.h>
+ #include <string.h>
+ extern unsigned char a[] __asm__("A.$class"), b[] __asm__("B.$class");
+ typedef int32_t (__attribute__((CALL_CONV)) *fun)(int32_t);
+ __attribute__((CALL_CONV)) extern fun getA(void), getB(void);
+ int main(void) {
+ uint32_t x,y;
+ memcpy(&x,a+%d,sizeof(x));
+ memcpy(&y,b+%d,sizeof(y));
+ fun f=(fun)(uintptr_t)x;
+ if(!x || x!=y || f!=getA() || f!=getB()) return 1;
+ return f(-7)!=7;
+ }
+ """.formatted(offsets[0],offsets[1]));
+ String exe=dir.resolve(TestC.OS.startsWith("Windows")?"test.exe":"test").toString();
+ String conv=TestC.CALL_CONVENTION.equals("win64")?"ms_abi":"sysv_abi";
+ // Either object's weak definition can win; every reference must agree.
+ for( String first : new String[]{"A","B"} ) {
+ String second=first.equals("A")?"B":"A";
+ TestC.linkExe(dir.resolve(first+".o").toString(),conv,driver.toString(),
+ new Ary<>(new String[]{dir.resolve(second+".o").toString()}),exe);
+ assertEquals("",TestC.exec(TestC.TEST_TIMEOUT_SECONDS,exe));
+ }
+ }
+
+ // 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);
+ 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++ )
+ 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>
+ __attribute__((CALL_CONV)) extern int64_t *a(void),*b(void);
+ int main(void) {
+ int64_t *x=a(),*y=b();
+ if(x!=y) return 1;
+ *x=37;
+ return *y!=37;
+ }
+ """);
+ String exe=dir.resolve(TestC.OS.startsWith("Windows")?"test.exe":"test").toString();
+ TestC.linkExe(lib.resolve("Globals.o").toString(),
+ TestC.CALL_CONVENTION.equals("win64")?"ms_abi":"sysv_abi",
+ driver.toString(),new Ary<>(new String[]{dir+"/A.o",dir+"/B.o"}),exe);
+ assertEquals("",TestC.exec(TestC.TEST_TIMEOUT_SECONDS,exe));
+ }
+}