From 586c8f65f2d0c0e1f73aadd0aa8d850aa653bddd Mon Sep 17 00:00:00 2001 From: Cliff Click Date: Mon, 28 Sep 2026 13:30:22 -0700 Subject: Support extern C ver direct updates FIx minor RISC encodings. --- .../seaofnodes/simple/node/cpus/riscv/TMPRISC.java | 6 +- .../java/com/seaofnodes/simple/Chapter22Test.java | 32 ++++++++ .../test/java/com/seaofnodes/simple/EvalRisc5.java | 2 +- .../seaofnodes/simple/node/cpus/riscv/TMPRISC.java | 6 +- .../java/com/seaofnodes/simple/Chapter22Test.java | 32 ++++++++ .../test/java/com/seaofnodes/simple/EvalRisc5.java | 2 +- .../seaofnodes/simple/node/cpus/riscv/TMPRISC.java | 6 +- .../java/com/seaofnodes/simple/Chapter22Test.java | 32 ++++++++ .../test/java/com/seaofnodes/simple/EvalRisc5.java | 2 +- chapter25/README.md | 24 ++++++ chapter25/docs/examples/FileIO.smp | 2 +- chapter25/src/main/c/simple_crt.c | 4 + .../main/java/com/seaofnodes/simple/Parser.java | 59 +++++++++++---- .../src/main/java/com/seaofnodes/simple/Var.java | 1 + .../com/seaofnodes/simple/codegen/CodeGen.java | 13 ++++ .../com/seaofnodes/simple/codegen/ElfWriter.java | 8 ++ .../com/seaofnodes/simple/codegen/Encoding.java | 11 +++ .../com/seaofnodes/simple/node/ConFldOffNode.java | 3 + .../seaofnodes/simple/node/ExternOffsetNode.java | 19 +++++ .../java/com/seaofnodes/simple/node/LoadNode.java | 10 ++- .../java/com/seaofnodes/simple/node/MemOpNode.java | 2 +- .../main/java/com/seaofnodes/simple/node/Node.java | 3 +- .../seaofnodes/simple/node/cpus/arm/TMPARM.java | 4 +- .../seaofnodes/simple/node/cpus/riscv/TMPRISC.java | 9 +-- .../simple/node/cpus/x86_64_v2/TMPX86.java | 5 +- .../java/com/seaofnodes/simple/type/Field.java | 46 ++++++----- .../com/seaofnodes/simple/type/TypeStruct.java | 9 ++- chapter25/src/main/smp/sys/io.smp | 2 +- chapter25/src/main/smp/sys/libc.smp | 5 +- .../java/com/seaofnodes/simple/Chapter25Test.java | 88 ++++++++++++++++++++++ .../test/java/com/seaofnodes/simple/EvalRisc5.java | 2 +- .../java/com/seaofnodes/simple/progs/extern_data.c | 22 ++++++ .../seaofnodes/simple/progs/extern_data_import.c | 14 ++++ .../java/com/seaofnodes/simple/spill/README.md | 10 +-- docs/chapter-backports.md | 64 +++++++++++----- 35 files changed, 469 insertions(+), 90 deletions(-) create mode 100644 chapter25/src/main/java/com/seaofnodes/simple/node/ExternOffsetNode.java create mode 100644 chapter25/src/test/java/com/seaofnodes/simple/progs/extern_data.c create mode 100644 chapter25/src/test/java/com/seaofnodes/simple/progs/extern_data_import.c diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java index 860e5eaf..2f7ffe0d 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java @@ -28,9 +28,9 @@ public class TMPRISC extends ConstantNode implements MachNode, RIPRelSize { // Delta is from opcode start @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { short dst = enc.reg(this); - // AUIPC dst,#hi20_constant_pool - enc.patch4(opStart , riscv.u_type(riscv.OP_AUIPC, dst, delta>>12)); - // Load dst,[dst+#low12_constant_pool] + // Round the high part because ADDI's low 12 bits are signed. + enc.patch4(opStart , riscv.u_type(riscv.OP_AUIPC, dst, (delta+0x800)>>12)); + // ADDI dst,dst,#low12_constant_pool enc.patch4(opStart+4, riscv.i_type(riscv.OP_IMM, dst, 0, dst, delta & 0xFFF)); } diff --git a/chapter22/src/test/java/com/seaofnodes/simple/Chapter22Test.java b/chapter22/src/test/java/com/seaofnodes/simple/Chapter22Test.java index 8d0f4c65..60917773 100644 --- a/chapter22/src/test/java/com/seaofnodes/simple/Chapter22Test.java +++ b/chapter22/src/test/java/com/seaofnodes/simple/Chapter22Test.java @@ -13,6 +13,38 @@ import org.junit.Test; import static org.junit.Assert.*; public class Chapter22Test { + + @Test public void testRiscvRightShifts() { + // SRAI, SRLI, SRA, SRL: a0 = -32 shifted by 4. + int[] ops = {0x40455513,0x00455513,0x40b55533,0x00b55533}; + for( int i=0; i (rs1 < rs2) ? 1 : 0; case 3 -> (rs1 < rs2) ? 1 : 0; case 4 -> rs1 ^ rs2; - case 5 -> (ir & 0x40000000) != 0 ? (rs1 >>> (rs2 & 0x3F)) : (rs1 >> (rs2 & 0x3F)); + case 5 -> (ir & 0x40000000) != 0 ? (rs1 >> (rs2 & 0x3F)) : (rs1 >>> (rs2 & 0x3F)); case 6 -> rs1 | rs2; case 7 -> rs1 & rs2; default -> rval; diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java index 44e73d1d..94f0da0e 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java @@ -28,9 +28,9 @@ public class TMPRISC extends ConstantNode implements MachNode, RIPRelSize { // Delta is from opcode start @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { short dst = enc.reg(this); - // AUIPC dst,#hi20_constant_pool - enc.patch4(opStart , riscv.u_type(riscv.OP_AUIPC, dst, delta>>12)); - // Load dst,[dst+#low12_constant_pool] + // Round the high part because ADDI's low 12 bits are signed. + enc.patch4(opStart , riscv.u_type(riscv.OP_AUIPC, dst, (delta+0x800)>>12)); + // ADDI dst,dst,#low12_constant_pool enc.patch4(opStart+4, riscv.i_type(riscv.OP_IMM, dst, 0, dst, delta & 0xFFF)); } diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Chapter22Test.java b/chapter23/src/test/java/com/seaofnodes/simple/Chapter22Test.java index bca06f0c..6f488c85 100644 --- a/chapter23/src/test/java/com/seaofnodes/simple/Chapter22Test.java +++ b/chapter23/src/test/java/com/seaofnodes/simple/Chapter22Test.java @@ -13,6 +13,38 @@ import org.junit.Test; import static org.junit.Assert.*; public class Chapter22Test { + + @Test public void testRiscvRightShifts() { + // SRAI, SRLI, SRA, SRL: a0 = -32 shifted by 4. + int[] ops = {0x40455513,0x00455513,0x40b55533,0x00b55533}; + for( int i=0; i (rs1 < rs2) ? 1 : 0; case 3 -> (rs1 < rs2) ? 1 : 0; case 4 -> rs1 ^ rs2; - case 5 -> (ir & 0x40000000) != 0 ? (rs1 >>> (rs2 & 0x3F)) : (rs1 >> (rs2 & 0x3F)); + case 5 -> (ir & 0x40000000) != 0 ? (rs1 >> (rs2 & 0x3F)) : (rs1 >>> (rs2 & 0x3F)); case 6 -> rs1 | rs2; case 7 -> rs1 & rs2; default -> rval; diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java index 44e73d1d..94f0da0e 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java @@ -28,9 +28,9 @@ public class TMPRISC extends ConstantNode implements MachNode, RIPRelSize { // Delta is from opcode start @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { short dst = enc.reg(this); - // AUIPC dst,#hi20_constant_pool - enc.patch4(opStart , riscv.u_type(riscv.OP_AUIPC, dst, delta>>12)); - // Load dst,[dst+#low12_constant_pool] + // Round the high part because ADDI's low 12 bits are signed. + enc.patch4(opStart , riscv.u_type(riscv.OP_AUIPC, dst, (delta+0x800)>>12)); + // ADDI dst,dst,#low12_constant_pool enc.patch4(opStart+4, riscv.i_type(riscv.OP_IMM, dst, 0, dst, delta & 0xFFF)); } diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Chapter22Test.java b/chapter24/src/test/java/com/seaofnodes/simple/Chapter22Test.java index e4eb03c0..cf92f830 100644 --- a/chapter24/src/test/java/com/seaofnodes/simple/Chapter22Test.java +++ b/chapter24/src/test/java/com/seaofnodes/simple/Chapter22Test.java @@ -13,6 +13,38 @@ import org.junit.Test; import static org.junit.Assert.*; public class Chapter22Test { + + @Test public void testRiscvRightShifts() { + // SRAI, SRLI, SRA, SRL: a0 = -32 shifted by 4. + int[] ops = {0x40455513,0x00455513,0x40b55533,0x00b55533}; + for( int i=0; i (rs1 < rs2) ? 1 : 0; case 3 -> (rs1 < rs2) ? 1 : 0; case 4 -> rs1 ^ rs2; - case 5 -> (ir & 0x40000000) != 0 ? (rs1 >>> (rs2 & 0x3F)) : (rs1 >> (rs2 & 0x3F)); + case 5 -> (ir & 0x40000000) != 0 ? (rs1 >> (rs2 & 0x3F)) : (rs1 >>> (rs2 & 0x3F)); case 6 -> rs1 | rs2; case 7 -> rs1 & rs2; default -> rval; diff --git a/chapter25/README.md b/chapter25/README.md index b6a7ded5..6d1d5e99 100644 --- a/chapter25/README.md +++ b/chapter25/README.md @@ -29,6 +29,26 @@ external Simple object search paths, including an object file or a directory. The compiler also resolves external C symbols for native linking. Names beginning with `_` control privacy; public reachability depends on enclosing names too. +Explicit numeric C declarations bind directly to mutable native storage: + +```java +i32 counter = "C"; +counter += 1; +return counter; +``` + +The native linker resolves `counter`. Reads and writes access that C variable; +the declaration neither copies nor initializes it. Repeated declarations and +module imports share the same storage alias. Integer and floating-point data +bindings are supported; pointer and aggregate data bindings are not yet supported. +External fields carry a storage flag and occupy no bytes in their declaring +class. Layout resolves their field offsets to `ExternOffset` symbols; instruction +selection replaces class-base-plus-offset with the native symbol address. +Native object output uses the existing AMD64 ELF writer; ARM and RISC-V support +these addresses through the in-memory linker. Thread-local variables and C macros +need accessor functions: the system library exposes `libc.errno()` through a small +native runtime accessor. + ## Object files contain ideal IR The ELF writer stores native code and a `.simple` section containing canonical @@ -47,6 +67,10 @@ expensive serialization bijection check is currently disabled, and the larger incremental-rebuild test `testModule0` is ignored. Neither is an active validation guarantee. +The external-field flag and symbolic offsets change the serialized layout. +The magic remains `C0DE`; there is no separate format version yet. Rebuild all +Simple object files before importing them with this compiler. + ## Constructing SSA before types are complete Simple still parses directly to SSA without an AST. Forward references mean an diff --git a/chapter25/docs/examples/FileIO.smp b/chapter25/docs/examples/FileIO.smp index 6966755a..e48d435b 100644 --- a/chapter25/docs/examples/FileIO.smp +++ b/chapter25/docs/examples/FileIO.smp @@ -47,4 +47,4 @@ val _error2 = { i32 errno -> sys.libc._exit(errno); }; -val _error = { -> _error2(sys.libc.errno); }; +val _error = { -> _error2(sys.libc.errno()); }; diff --git a/chapter25/src/main/c/simple_crt.c b/chapter25/src/main/c/simple_crt.c index b79e6f88..8656a3cc 100644 --- a/chapter25/src/main/c/simple_crt.c +++ b/chapter25/src/main/c/simple_crt.c @@ -1,6 +1,10 @@ #include #include #include +#include + +// errno may be thread-local or a macro calling a platform accessor. +int32_t simple_errno(void) { return errno; } // Native process arguments converted to ordinary Simple array layouts. // The outer array is mutable; every contained byte array is published as diff --git a/chapter25/src/main/java/com/seaofnodes/simple/Parser.java b/chapter25/src/main/java/com/seaofnodes/simple/Parser.java index af5af514..19894357 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/Parser.java @@ -346,9 +346,11 @@ public class Parser { Var v = _scope.var(lex + nvar++); // Field with sharper type Field old = tself.field(v._name); - Field fld = old == null + Field fld = v._extern!=0 + ? Field.make(v._name,v.type(),v._extern,false,true) + : old == null ? Field.make(v._name,v.type(),_code.alias(_ref._cname),v._final) - : Field.make(v._name,v.type(), old._alias, old._final); + : old.makeFrom(v.type()); tself = tself.addOrUpdate(fld); } TYPES.put(typeName,tself); @@ -426,7 +428,7 @@ public class Parser { if( vidx == -1 ) continue; Var v = _scope.var(vidx); - if( v._fref ) continue; // Forward refs not declared here + if( v._fref || v._extern!=0 ) continue; // C storage has no Simple initializer // NewNode's TOP field is the zero-code poison for a required // field. Leave it alone unless the declaration body supplied a // value; otherwise a user constructor must initialize it. @@ -970,6 +972,9 @@ public class Parser { if( def==null ) throw error("Undefined name '" + name + "'"); + if( def._extern!=0 ) // Parse the C binding as a memory lvalue + { pos(old); return parseExpression(); } + // TOP fields are for late-initialized fields; these have never // been written to, and this must be the final write. Other writes // outside the constructor need to check the final bit. @@ -1101,6 +1106,14 @@ public class Parser { Node expr; if( match("=") ) { if( isExternDecl() ) { + if( !(t instanceof TypeFunPtr) ) { + Node ptr = externData(name,t); + // The lexical binding is a fixed address; its contents are mutable. + if( !_scope.define(name,t,true,ptr,loc) ) + throw error("Redefining name '"+name+"'",loc); + _scope.lookup(name)._extern = _code.externAlias(name); + return _code.ZERO; + } expr = externDecl(name,t); t = expr._type; // Upgrade declared type to the exact extern decl type } else if( isConstructorDecl(name) ) { @@ -1280,7 +1293,7 @@ public class Parser { priv.keep(); for( Field field : ts._fields ) { Type storage = field._t.makeStorage(); - Field escaped = field._final ? field : Field.make(field._fname,storage,field._alias,true); + Field escaped = field._final ? field : field.makeFrom(storage).makeFrom(true); pub.keep(); Node esc = new EscapeNode(escaped,self,priv,pub).peephole(); MemMergeNode merge = new MemMergeNode(null,pub); @@ -1879,6 +1892,8 @@ public class Parser { if( name!=null ) { Var n = _scope.lookup(name); if( n != null && !(n.type() instanceof TypeMemPtr) ) { + if( n._extern!=0 ) + return parsePostfixName(_scope.in(n),name,delta); if( n._final ) throw error("Cannot reassign final '"+n._name+"'"); Node expr = zsMask(peep(new AddNode(_scope.in(n),con(delta))),n.type()); @@ -1963,6 +1978,8 @@ public class Parser { // Load local value Node rvalue = _scope.in(var); + if( var._extern!=0 ) + return parsePostfixName(rvalue,id,0); if( var._fref ) return parsePostfix(rvalue); // Required constructor fields begin as their declared pointer type @@ -1980,7 +1997,7 @@ public class Parser { Kind fk = _scope._kinds.at(fx); // Access instance field from 'self' if( kx+1 == fx && kk instanceof Kind.Func inst && FunNode.isInstance(inst._name) && fk instanceof Kind.Func method ) - return parsePostfixName(_scope.in(method._lexSize),id); + return parsePostfixName(_scope.in(method._lexSize),id,0); // Check for a function-escaping variable; these require true // closures. Final constants are OK; final vars require a hidden var @@ -1998,7 +2015,7 @@ public class Parser { // Get the most recent sharpen global TypeStruct clz = (TypeStruct) TYPES.get( ((TypeMemPtr) clzptr.type())._obj._name ); TypeMemPtr clztmp = TypeMemPtr.make( (byte) 2, clz, true ); - return parsePostfixName( con( clztmp ), id ); + return parsePostfixName( con( clztmp ), id,0 ); //return parsePostfixName(_scope.in(2),id); } } @@ -2206,7 +2223,7 @@ public class Parser { else if( match("(") ) return parsePostfix(require(functionCall(expr,defaultSelf()),")")); else return expr; // No postfix - return parsePostfixName(expr,name); + return parsePostfixName(expr,name,0); } private Node defaultSelf() { @@ -2225,7 +2242,8 @@ public class Parser { * */ - private Node parsePostfixName(Node expr, String name) { + // pre is +/-1 for a prefix update, otherwise parse a postfix operation. + private Node parsePostfixName(Node expr, String name, int pre) { // Keep expr across possible updates expr.keep(); @@ -2278,7 +2296,7 @@ public class Parser { : fldoff(expr,name)).keep(); // Disambiguate "obj.fld==x" boolean test from "obj.fld=x" field assignment - if( matchOpx('=','=') ) { + if( pre==0 && matchOpx('=','=') ) { // Field assignment Node val = parseAsgn().keep(); // Lift value for store @@ -2306,20 +2324,20 @@ public class Parser { Node load = peep(new LoadNode(loc(),name, alias, ctrl(), mem, expr, off)); // Check for assign-update, "ptr.fld += expr" or "ary[idx]++" - char ch = _lexer.matchOperAssign(); + char ch = pre==0 ? _lexer.matchOperAssign() : (char)pre; if( ch!=0 ) { if( fld!=null && decl==Type.BOTTOM ) throw error( "'" + ts._name + "' is not fully initialized, field '" + fld._fname + "' needs to be set in a constructor" ); - Node op = opAssign(ch,load, decl ); + Node op = opAssign(ch,load, decl ).keep(); mem.keep(); Node st = new StoreNode(loc(), name, alias, decl, ctrl(), mem, expr.unkeep(), off.unkeep(), op, false).peephole().keep(); storeMem(st,alias,mem); st.unkeep(); mem.unkeep(); - load = postfix(ch) ? load.unkeep() : op; - // And use the original loaded value as the result - return load; + op.unkeep(); // A narrow Store may have folded away the expression's conversion. + if( postfix(ch) ) load.unkeep(); + return pre==0 && postfix(ch) ? load : op; } off.unkill(); // Might be a method call, so pass expr as 'self' @@ -2487,6 +2505,19 @@ public class Parser { return (ExternNode)(new ExternNode(t,ex).peephole()); } + // A lexical C binding has a singleton namespace with one external field. + // The namespace occupies no storage; its field offset supplies the address. + private Node externData(String name, Type t) { + if( !(t instanceof TypeInteger) && !(t instanceof TypeFloat) ) + throw error("C data binding requires an integer or float type"); + String cname = ("C:"+name).intern(); + TypeStruct ts = TypeStruct.make(cname,false,Field.make(name,t,_code.externAlias(name),false,true)); + Type old = TYPES.putIfAbsent(cname,ts); + if( old!=null && old!=ts ) + throw error("Conflicting types for C data '"+name+"'"); + return con(TypeMemPtr.make((byte)2,ts,true)); + } + /** * Parse integer literal * diff --git a/chapter25/src/main/java/com/seaofnodes/simple/Var.java b/chapter25/src/main/java/com/seaofnodes/simple/Var.java index 1906ce15..af371d21 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/Var.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/Var.java @@ -19,6 +19,7 @@ public class Var { // Parser-only definite-initialization marker. The current binding's // nullable type carries the path-sensitive state through SSA merges. public boolean _uninit; + public int _extern; // C storage alias; zero for ordinary variables public Parser.Lexer _loc; // Source location public Var(int idx, String name, Type type, boolean xfinal, Parser.Lexer loc ) { diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java index 0f38d3ff..86e83372 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java @@ -321,6 +321,12 @@ public class CodeGen { public final GlobalBits _aliases = new GlobalBits(); public int alias(String clz) { return _aliases.next(clz); } + // C storage has one global alias per symbol, including across object files. + public int externAlias(String name) { return _aliases.next("C:"+name,GlobalBits.RESERVED); } + + // External data addresses supplied by an in-memory linker (image offsets). + public final HashMap _externDataAddresses = new HashMap<>(); + // Compute local function index (FIDX) from global function info. This is // called *in order* during parsing, and that order is part of the global // unique mapping @@ -740,6 +746,13 @@ public class CodeGen { } // Produce a machine node from n; map it to flag as done so stops cycles. + if( n instanceof MemOpNode mem && mem.off() instanceof ExternOffsetNode off ) { + // External fields replace base+offset with symbol+0. Keep the ideal + // declaring type on the address for machine load/store width selection. + TypeMemPtr ptr = (TypeMemPtr)mem.ptr()._type; + n._inputs.set(2,new ExternNode(TypeMemPtr.make((byte)2,ptr._obj,true),off._extern)); + n._inputs.set(3,ConstantNode.raw(TypeInteger.ZERO)); + } map.put(n, x=_mach.instSelect(n) ); // Carry loop-tree and pre-order info across the ideal->mach transition if( n instanceof CFGNode ncfg && x instanceof CFGNode xcfg ) { 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 a74303e6..7423edbd 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java @@ -316,6 +316,14 @@ public class ElfWriter { relocations.writeReloOffPlus(offset, symidx, (byte)4/*PLT32*/); } + // Native ELF output is AMD64. Data addresses use PC32, not PLT calls. + for( Node n : enc._externData.keySet() ) { + String extern = enc._externData.get(n); + int symidx = symbols.symbol(extern, 0, SYM_BIND_GLOBAL, SYM_TYPE_OBJECT); + int offset = enc.opStart(n) + enc.opLen(n) - 4; + relocations.writeReloOffPlus(offset, symidx, (byte)2/*PC32*/); + } + // Write relocations for the constant pool String cpoolPrefix = _code.entryClinitName().replaceAll("[^A-Za-z0-9_$]", "_") + "$CPOOL$"; for( Encoding.Relo relo : enc._bigCons.values() ) { 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 618ec689..215fd694 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Encoding.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Encoding.java @@ -187,6 +187,9 @@ public class Encoding { return this; } + public final HashMap _externData = new HashMap<>(); + public void externalData(Node n, String name) { _externData.put(n,name); } + // Store t as a 32/64 bit constant in the code space; generate RIP-relative // addressing to load it. Type is stored in either the .rodata or .data. public void largeConstant( Node relo, Type t, int off, int elf ) { @@ -581,6 +584,7 @@ public class Encoding { int off=0; // offset in the struct for( int fn=0; fn strs, HashMap types, IdentityHashMap nodes) { + baos.packed2(strs.get(_extern)); + } +} diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java index bd80a74b..193fbe17 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -92,8 +92,7 @@ public class LoadNode extends MemOpNode { // Loads into structs do not need a ctrl edge, as null-ptr checking is // baked into the type system. Loads into arrays DO need the ctrl // edge, at least until proper range-checking is in place. - if( in(0)!=null && ptr._type instanceof TypeMemPtr tmp && - !tmp._obj.isAry() ) { + if( in(0)!=null && ptr._type instanceof TypeMemPtr tmp && !tmp._obj.isAry() ) { setDef(0,null); return this; } @@ -113,6 +112,10 @@ public class LoadNode extends MemOpNode { if( _alias == 1 ) return null; + // External storage is shared even through different class namespaces. + boolean external = ptr._type instanceof TypeMemPtr tmp && + (fld=tmp._obj.field(_name)) != null && fld._extern; + // Simple Load-after-Store on same address. if( mem instanceof StoreNode st && ptr == st.nnptr() && @@ -154,7 +157,7 @@ public class LoadNode extends MemOpNode { return extend(castRO(st.val())); // Proved equal // Can we prove unequal? Offsets do not overlap? if( !off()._type.join(st.off()._type).isHigh() && // Offsets overlap - !neverAlias(ptr,st.ptr()) ) { // And might alias + (external || !neverAlias(ptr,st.ptr())) ) { // And might alias addDep( off()); // Offsets can fold, proving unequal addDep(st.off()); // Offsets can fold, proving unequal break outer; // Cannot tell, stop trying @@ -175,6 +178,7 @@ public class LoadNode extends MemOpNode { case ProjNode mproj: // Memory projection switch( mproj.in(0) ) { case NewNode nnn1: + if( external ) break outer; // Class allocation does not initialize C storage // Direct load from fresh zero/default-filled allocation. Type decl = declaredType(); assert decl!=Type.BOTTOM; diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MemOpNode.java index 083889c5..441edc3e 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/MemOpNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/MemOpNode.java @@ -99,7 +99,7 @@ public abstract class MemOpNode extends TypeNode { @Override public StringBuilder _print1( StringBuilder sb, BitSet visited ) { return _printMach(sb,visited); } public StringBuilder _printMach( StringBuilder sb, BitSet visited ) { throw Utils.TODO(); } - //public int log_size() { return _con.log_size(); } + @Override public boolean eq(Node n) { diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter25/src/main/java/com/seaofnodes/simple/node/Node.java index e77b0c0a..8ca4662b 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/Node.java @@ -84,7 +84,7 @@ public abstract class Node implements Cloneable { New,Never,Not,Or,Parm,Phi,Proj, ReadOnly,Return,Region,RoundF32, Sar,Shl,Shr,Start,Stop,Store,Sub,ToFloat, - XCtrl,Xor,StartCU,StopCU,Guard,MemPhi,BulkMemPhi,PtrToInt; + XCtrl,Xor,StartCU,StopCU,Guard,MemPhi,BulkMemPhi,PtrToInt,ExternOffset; public static final Tag[] VALS = values(); public Node make( BAOS bais, String[] strs, Type[] types, GlobalBits fileAliases, GlobalBits aliases ) { return switch(this) { @@ -124,6 +124,7 @@ public abstract class Node implements Cloneable { case Con -> ConstantNode.make(bais ,types); case Escape-> EscapeNode.make(bais ,types); case Extern-> ExternNode.make(bais,strs,types); + case ExternOffset-> new ExternOffsetNode(strs[bais.packed2()]); case FunPtr-> FunPtrNode.make(bais, types); case Fun -> FunNode.make(bais,strs,types); case Load -> new LoadNode(bais,strs,types,fileAliases,aliases); diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java index f86d85dc..8209eb4b 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java @@ -17,8 +17,8 @@ public class TMPARM extends ConstantNode implements MachNode, RIPRelSize { @Override public boolean isClone() { return true; } @Override public TMPARM copy() { return new TMPARM(this,_ext); } @Override public void encoding( Encoding enc ) { - if( _ext!=null ) throw Utils.TODO(); - enc.largeConstant(this,((TypeMemPtr)_con)._obj,0,-1); + if( _ext!=null ) enc.externalData(this,_ext); + else enc.largeConstant(this,((TypeMemPtr)_con)._obj,0,-1); short dst = enc.reg(this); // adrp x0, 0 enc.add4(arm.adrp(1,0, arm.OP_ADRP, 0,dst)); diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java index aa001e1e..45c3eed2 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java @@ -6,7 +6,6 @@ import com.seaofnodes.simple.node.MachNode; import com.seaofnodes.simple.node.Node; import com.seaofnodes.simple.type.TypeMemPtr; import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; public class TMPRISC extends ConstantNode implements MachNode, RIPRelSize { final String _ext; @@ -17,8 +16,8 @@ public class TMPRISC extends ConstantNode implements MachNode, RIPRelSize { @Override public boolean isClone() { return true; } @Override public TMPRISC copy() { return new TMPRISC(this,_ext); } @Override public void encoding( Encoding enc ) { - if( _ext!=null ) throw Utils.TODO(); - enc.largeConstant(this,((TypeMemPtr)_con)._obj,0,-1); + if( _ext!=null ) enc.externalData(this,_ext); + else enc.largeConstant(this,((TypeMemPtr)_con)._obj,0,-1); short dst = enc.reg(this); // AUIPC dst,#hi20_constant_pool enc.add4(riscv.u_type(riscv.OP_AUIPC, dst, 0)); @@ -32,8 +31,8 @@ public class TMPRISC extends ConstantNode implements MachNode, RIPRelSize { @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { short dst = enc.reg(this); // AUIPC dst,#hi20_constant_pool - enc.patch4(opStart , riscv.u_type(riscv.OP_AUIPC, dst, delta>>12)); - // Load dst,[dst+#low12_constant_pool] + enc.patch4(opStart , riscv.u_type(riscv.OP_AUIPC, dst, (delta+0x800)>>12)); + // ADDI's low 12 bits are signed; round the AUIPC page accordingly. enc.patch4(opStart+4, riscv.i_type(riscv.OP_IMM, dst, 0, dst, delta & 0xFFF)); } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java index cc8741b3..fbd79dd7 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java @@ -4,7 +4,6 @@ import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeMemPtr; import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; public class TMPX86 extends ConstantNode implements MachNode, RIPRelSize{ private byte _opLen; @@ -17,12 +16,12 @@ public class TMPX86 extends ConstantNode implements MachNode, RIPRelSize{ @Override public Node copy() { return new TMPX86(this,_ext); } @Override public void encoding( Encoding enc ) { - if( _ext!=null ) throw Utils.TODO(); short dst = enc.reg(this); // 0 or 1 for REX depending on the dst. _opLen = (byte)(6+ x86_64_v2.rexF(dst, 0, 0, true, enc)); enc.add1(0x8D).add1(x86_64_v2.modrm(x86_64_v2.MOD.INDIRECT, dst, 0b101)).add4(0); - enc.largeConstant(this,((TypeMemPtr)_con)._obj, _opLen-4, 2/*ELF encoding PC32*/); + if( _ext!=null ) enc.externalData(this,_ext); + else enc.largeConstant(this,((TypeMemPtr)_con)._obj, _opLen-4, 2/*ELF encoding PC32*/); } // Delta is from opcode start diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/Field.java b/chapter25/src/main/java/com/seaofnodes/simple/type/Field.java index ed505b19..beaa719c 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/type/Field.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/type/Field.java @@ -1,7 +1,6 @@ package com.seaofnodes.simple.type; import com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.util.AryInt; import com.seaofnodes.simple.util.BAOS; import com.seaofnodes.simple.util.SB; import java.util.*; @@ -22,23 +21,26 @@ public class Field extends Type { public int _alias; // Field is only written in the constructor public boolean _final; + // Native C storage, outside the containing object's layout. + public boolean _extern; private static final Ary FREE = new Ary<>(Field.class); - private Field(String fname, Type type, int alias, boolean xfinal ) { super(TFLD); init(fname,type,alias,xfinal); } - private Field init(String fname, Type type, int alias, boolean xfinal ) { + private Field(String fname, Type type, int alias, boolean xfinal, boolean external ) { super(TFLD); init(fname,type,alias,xfinal,external); } + private Field init(String fname, Type type, int alias, boolean xfinal, boolean external ) { _fname = fname; _t = type; _alias = alias; _final = xfinal; + _extern = external; return this; } // Return a filled-in Field; either from free list or alloc new. - private static Field malloc(String fname, Type type, int alias, boolean xfinal ) { - return FREE.isEmpty() ? new Field(fname,type,alias,xfinal) : FREE.pop().init(fname,type,alias,xfinal); + private static Field malloc(String fname, Type type, int alias, boolean xfinal, boolean external ) { + return FREE.isEmpty() ? new Field(fname,type,alias,xfinal,external) : FREE.pop().init(fname,type,alias,xfinal,external); } // Malloc-from - public Field malloc( ) { return malloc(_fname,null,_alias,_final); } + public Field malloc( ) { return malloc(_fname,null,_alias,_final,_extern); } @Override Field free(Type t) { Field f = (Field)t; assert !f.isFree() && !f._terned; @@ -52,16 +54,19 @@ public class Field extends Type { // Make and intern with listed fields public static Field make( String fname, Type type, int alias, boolean xfinal ) { - Field f = malloc(fname,type,alias,xfinal); + return make(fname,type,alias,xfinal,false); + } + public static Field make( String fname, Type type, int alias, boolean xfinal, boolean external ) { + Field f = malloc(fname,type,alias,xfinal,external); Field f2 = f.intern(); if( f2==f ) return f; return VISIT.isEmpty() ? f2.free(f) : f2.delayFree(f); } public Field makeFrom( Type type ) { - return type == _t ? this : make(_fname,type,_alias,_final); + return type == _t ? this : make(_fname,type,_alias,_final,_extern); } public Field makeFrom( boolean xfinal ) { - return xfinal == _final ? this : make(_fname,_t,_alias,xfinal); + return xfinal == _final ? this : make(_fname,_t,_alias,xfinal,_extern); } // Cycle-making-breaking @@ -71,7 +76,8 @@ public class Field extends Type { public static final Field TEST = make("test",Type.NIL,-2,false); public static final Field TEST2 = make("test",Type.NIL,-2,true); public static final Field FLT = make("flt ",TypeFloat.F32,-2,false); - public static void gather(ArrayList ts) { ts.add(TEST); ts.add(TEST2); ts.add(FLT); } + public static final Field EXT = make("test",TypeInteger.I32,-2,false,true); + public static void gather(ArrayList ts) { ts.add(TEST); ts.add(TEST2); ts.add(FLT); ts.add(EXT); } @Override Field xmeet( Type that ) { Field fld = (Field)that; // Invariant @@ -80,16 +86,17 @@ public class Field extends Type { if( fld ==BOT.dual() ) return this; if( _fname!=fld._fname ) { assert !_fname.equals(fld._fname); return BOT; } assert _alias==fld._alias; - return make(_fname, _t.meet(fld._t ), _alias, _final | fld._final); + if( _extern!=fld._extern ) return BOT; + return make(_fname, _t.meet(fld._t ), _alias, _final | fld._final,_extern); } @Override - Field xdual() { return malloc(_fname, _t.dual(),_alias,!_final); } + Field xdual() { return malloc(_fname, _t.dual(),_alias,!_final,_extern); } @Override Field rdual() { if( _dual!=null ) return dual(); assert !_terned; - Field d = malloc(_fname,null,_alias,!_final); + Field d = malloc(_fname,null,_alias,!_final,_extern); (_dual = d)._dual = this; // Cross link duals d._t = _t._terned ? _t.dual() : _t.rdual(); return d; @@ -97,7 +104,7 @@ public class Field extends Type { @Override boolean _isConstant() { return _t._isConstant(); } @Override boolean _isFinal() { return _final && _t._isFinal(); } - @Override Field _makeRO() { return _final ? this : make(_fname, _t._makeRO(),_alias,true); } + @Override Field _makeRO() { return _final ? this : make(_fname, _t._makeRO(),_alias,true,_extern); } @Override Field _close( String name, HashMap TYPES ) { return makeFrom( _t._close(name, TYPES )); } @Override Type _upgradeType(HashMap TYPES) { @@ -105,10 +112,10 @@ public class Field extends Type { } // Override in subclasses - int hash() { return _fname.hashCode() ^ _t.hashCode() ^ _alias ^ (_final ? 1024 : 0); } + int hash() { return _fname.hashCode() ^ _t.hashCode() ^ _alias ^ (_final ? 1024 : 0) ^ (_extern ? 2048 : 0); } private boolean static_eq( Field f ) { - return _fname.equals(f._fname) && _alias==f._alias && _final==f._final && _t !=null && f._t !=null; + return _fname.equals(f._fname) && _alias==f._alias && _final==f._final && _extern==f._extern && _t !=null && f._t !=null; } @Override boolean eq( Type t ) { Field f = (Field)t; @@ -123,22 +130,23 @@ public class Field extends Type { @Override public int nkids() { return 1; } @Override public Type at( int idx ) { return _t; } @Override public void set( int idx, Type t ) { _t = t; } - // Tags: final/!final; +alias+name + // Tags: final/!final; +alias+name+external-storage flag @Override int TAGOFF() { return 2; } @Override public void packed( BAOS baos, HashMap strs ) { baos.write(TAGOFFS[_type] + (_final ? 1 : 0)); baos.packed2(_alias); baos.packed2(strs.get(_fname)); + baos.write(_extern ? 1 : 0); } static Field packed( int tag, BAOS bais, String[] strs ) { int alias = bais.packed2(); String fname = strs[bais.packed2()]; - return malloc(fname,null,alias,(tag&1)==1); + return malloc(fname,null,alias,(tag&1)==1,bais.read()!=0); } @Override public SB _print( SB sb, BitSet visit, boolean html ) { - sb.p(_final?"":"!").p(_fname).p(":").p(_alias).p(" : "); + sb.p(_extern ? "extern C " : "").p(_final?"":"!").p(_fname).p(":").p(_alias).p(" : "); return _t ==null ? sb.p("---") : _t.print(sb,visit,html); } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeStruct.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeStruct.java index 0d6700b9..82c37a12 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeStruct.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeStruct.java @@ -154,6 +154,7 @@ public class TypeStruct extends Type { public TypeStruct makeInit() { Field[] fs = new Field[_fields.length]; for( int i=0; i<_fields.length; i++ ) { + if( _fields[i]._extern ) { fs[i] = _fields[i]; continue; } Type zero = _fields[i]._t.makeZero(); fs[i] = _fields[i].makeFrom(_fields[i]._t != Type.BOTTOM && zero.isa(_fields[i]._t) ? zero : Type.TOP); } @@ -336,7 +337,7 @@ public class TypeStruct extends Type { int len = Math.min(_fields.length,that._fields.length); for( int i=0; i