summaryrefslogtreecommitdiff
path: root/chapter25
diff options
context:
space:
mode:
Diffstat (limited to 'chapter25')
-rw-r--r--chapter25/README.md18
-rw-r--r--chapter25/ROADMAP.md20
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java46
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/Encoding.java4
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/StaticDataTest.java47
5 files changed, 129 insertions, 6 deletions
diff --git a/chapter25/README.md b/chapter25/README.md
index 8e51c557..bbe0c50e 100644
--- a/chapter25/README.md
+++ b/chapter25/README.md
@@ -52,6 +52,24 @@ these addresses through the in-memory linker. Thread-local variables and C macro
need accessor functions: the system library exposes `libc.errno()` through a small
native runtime accessor.
+Native function pointers still occupy four bytes. A native library can live
+above that address range, so the AMD64 ELF writer gives each address-taken
+native function a small entry in Simple's low-address code. The entry tail-jumps
+through the native symbol's PLT entry; arguments and the return address pass
+through unchanged. Direct calls still use the native symbol directly.
+
+Static class fields and addresses computed in code both refer to that entry.
+Its stable weak symbol, `__simple_native$<name>`, lets separately compiled
+objects agree on one pointer when linked into an executable. The class image
+uses a four-byte relocation to this defined entry, instead of a four-byte
+relocation to a dynamic native symbol in writable data, which GNU ld rejects.
+Constant field Stores can still fold away. The entry is a Simple function
+identity; it need not equal the raw address obtained by native code.
+
+`StaticDataTest.testNativeFunctionIdentity` reads two separately compiled class
+images before their initializers run, compares their stored and computed
+addresses, and calls the native function. It checks both object-link orders.
+
## Object files contain ideal IR
The ELF writer stores native code and a `.simple` section containing canonical
diff --git a/chapter25/ROADMAP.md b/chapter25/ROADMAP.md
index 966bf8e9..b5647587 100644
--- a/chapter25/ROADMAP.md
+++ b/chapter25/ROADMAP.md
@@ -487,6 +487,26 @@ Mutable fields may have static initial contents without becoming constants.
Choose read-only storage only when no runtime writes remain. Static reference
cycles can use relocations without requiring a dynamic initialization cycle.
+For a wholly immutable, pre-initialized class, eliminate the mutable `$init`
+state as well as the checks so the class image can reside in ROM. The native
+function thunks preserve static initialization of function fields.
+
+### Compact code and data arenas
+
+Keep four-byte Simple references into bounded code and data arenas; the arenas
+need not overlap or live near zero. Choose absolute addresses versus offsets
+from an arena base when implementing placement and indirect calls. Native
+function thunks can occupy the code arena while native libraries live elsewhere.
+The current thunk implementation uses the existing low-address executable;
+ordinary data pointers still occupy eight bytes.
+
+Give full-width native pointers separate types and foreign lifetime contracts.
+Opaque device addresses may need to be passed through without CPU dereference.
+Simple object lifetimes should be managed automatically without a GC;
+ownership, borrowing, cycles, and native retention still need design. Compact
+storage alone does not establish those rules. Preserve static class images and
+explicit native boundary conversions when choosing the arena representation.
+
### CFG frequency estimates
After lowering and loop-tree construction, estimate block frequencies for GCM
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java
index b3eac583..5a280723 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java
@@ -1,6 +1,7 @@
package com.seaofnodes.simple.codegen;
import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeFunPtr;
import com.seaofnodes.simple.type.TypeStruct;
import com.seaofnodes.simple.util.Ary;
import com.seaofnodes.simple.util.BAOS;
@@ -171,7 +172,7 @@ public class ElfWriter {
public int symbol( String name, int shndx, int bind, int type, long value, long size ) {
int gidx = gidx(); // Global index before I write to _globals
- BAOS baos = bind==SYM_BIND_GLOBAL ? _globals : _contents;
+ BAOS baos = bind==SYM_BIND_LOCAL ? _contents : _globals;
write4(baos,_strtab.writeCString(name));
int info = (bind << 4) | (type & 0xF);
baos.write(info);
@@ -179,7 +180,7 @@ public class ElfWriter {
write2(baos,shndx); // section index
write8(baos,value);
write8(baos,size);
- return bind==SYM_BIND_GLOBAL ? gidx : -99;
+ return bind==SYM_BIND_LOCAL ? -99 : gidx;
}
public int symbol( String name, int shndx, int bind, int type ) {
@@ -265,6 +266,7 @@ public class ElfWriter {
public static final int SYM_BIND_LOCAL = 0;
public static final int SYM_BIND_GLOBAL = 1;
+ public static final int SYM_BIND_WEAK = 2;
public static final int SYM_TYPE_NOTYPE = 0;
public static final int SYM_TYPE_OBJECT = 1;
@@ -281,6 +283,26 @@ public class ElfWriter {
// ------------------------------------------------------------------------
+ // Native code can live above 4G, outside our four-byte function pointers.
+ // Give address-taken natives a low entry which tail-jumps through the PLT.
+ // A weak, stable name makes references from separate objects agree on one
+ // entry. Do not patch references straight to the local copy: that would
+ // bypass the linker's choice and break pointer equality across objects.
+ private int nativeEntry(String name, HashMap<String,Integer> entries,
+ DataSection text, SymbolSection symbols, ReloSection relos) {
+ Integer old = entries.get(name);
+ if( old!=null ) return old;
+ int offset = text.size();
+ text._contents.write(0xE9); // jmp rel32; preserve arguments and return address
+ text.write4(0);
+ int target = symbols.symbol(name,0,SYM_BIND_GLOBAL,SYM_TYPE_FUNC);
+ relos.writeReloOffPlus(offset+1,target,(byte)4/*PLT32*/);
+ int entry = symbols.symbol("__simple_native$"+name,text._index,
+ SYM_BIND_WEAK,SYM_TYPE_FUNC,offset,5);
+ entries.put(name,entry);
+ return entry;
+ }
+
public ElfWriter export(boolean emitEntrySymbol) {
if( _code._serial==null )
throw new IllegalStateException("ELF export needs ideal IR: request Export from the driver, or Serialize before Lower");
@@ -300,7 +322,10 @@ public class ElfWriter {
// we've already constructed these entire sections in the encoding phase
Encoding enc = _code._encoding;
- DataSection text = new DataSection(".text" , Section.SHT_PROGBITS, enc._bits , DataSection.SHF_ALLOC | DataSection.SHF_WRITE | DataSection.SHF_EXECINSTR );
+ // Append ELF-only thunks to a copy. Export must not change instruction
+ // offsets or the image used by the in-memory linker and emulators.
+ BAOS textBits = new BAOS(enc._bits.toByteArray(),enc._bits.size());
+ DataSection text = new DataSection(".text" , Section.SHT_PROGBITS, textBits, DataSection.SHF_ALLOC | DataSection.SHF_WRITE | DataSection.SHF_EXECINSTR );
// Constant pool
DataSection rodata = new DataSection(".rodata", Section.SHT_PROGBITS, enc._cpool, DataSection.SHF_ALLOC );
// Static/global pool
@@ -316,12 +341,19 @@ public class ElfWriter {
// create external .text relocations
ReloSection relocations = new ReloSection(text._index);
+ HashMap<String,Integer> nativeEntries = new HashMap<>();
for( Node n : enc._externals.keySet()) {
String extern = enc._externals.get(n);
- int symidx = symbols.symbol(extern, 0, SYM_BIND_GLOBAL, SYM_TYPE_NOTYPE);
+ boolean nativeAddress = n instanceof FunPtrNode &&
+ _code.externFunc(((TypeFunPtr)n._type).fidx())!=null;
+ int symidx = nativeAddress
+ ? nativeEntry(extern,nativeEntries,text,symbols,relocations)
+ : symbols.symbol(extern,0,SYM_BIND_GLOBAL,SYM_TYPE_FUNC);
int offset = enc.opStart(n) + enc.opLen(n) - 4;
- relocations.writeReloOffPlus(offset, symidx, (byte)4/*PLT32*/);
+ // Address materialization and static fields name the same thunk;
+ // direct calls still go straight to the native symbol's PLT entry.
+ relocations.writeReloOffPlus(offset,symidx,(byte)(nativeAddress ? 2/*PC32*/ : 4/*PLT32*/));
}
// Native ELF output is AMD64. Data addresses use PC32, not PLT calls.
@@ -353,7 +385,9 @@ public class ElfWriter {
ReloSection rwRelos=new ReloSection(".rela.data",rwdata._index);
for( StaticData.Relocation relo : enc._data.relocations ) {
String name=enc._data.targetSymbol(relo.value());
- Integer sym=dataSymbols.get(name);
+ String nativeName=relo.value() instanceof TypeFunPtr fp ? _code.externFunc(fp.fidx()) : null;
+ Integer sym=nativeName==null ? dataSymbols.get(name)
+ : nativeEntry(nativeName,nativeEntries,text,symbols,relocations);
if( sym==null ) {
sym=symbols.symbol(name,0,SYM_BIND_GLOBAL,SYM_TYPE_NOTYPE);
dataSymbols.put(name,sym);
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Encoding.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Encoding.java
index 8e9b99db..06bf14f5 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Encoding.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Encoding.java
@@ -171,6 +171,10 @@ public class Encoding implements com.seaofnodes.isa.CodeSink {
// Code address as a constant
public Encoding relo( FunPtrNode fptr ) {
TypeFunPtr tfp = (TypeFunPtr)fptr._type;
+ // Native functions have no local FunNode, but are still callable.
+ // Do not treat their addresses as identities of deleted functions.
+ String nativeName = _code.externFunc(tfp.fidx());
+ if( nativeName!=null ) return external(fptr,nativeName);
FunNode fun = _code.link(tfp);
if( fun != null && !_code.owns(fun) )
_externals.put(fptr,fun._name);
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/StaticDataTest.java b/chapter25/src/test/java/com/seaofnodes/simple/StaticDataTest.java
index 03df05a7..88d0246d 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/StaticDataTest.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/StaticDataTest.java
@@ -93,6 +93,53 @@ public class StaticDataTest {
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");