diff options
Diffstat (limited to 'chapter25/src')
23 files changed, 289 insertions, 60 deletions
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 <stdint.h> #include <stdlib.h> #include <string.h> +#include <errno.h> + +// 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 { * </pre> */ - 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<String,Integer> _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<Node,String> _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<ts._fields.length; fn++ ) { Field f = ts._fields[layout[fn]]; + if( f._extern ) continue; int foff = ts. offset(layout[fn]); // Pad up to field while( off < foff ) { bits.write(0); off++; }; @@ -604,6 +608,13 @@ public class Encoding { public static int SENTINEL_WRITE = -8; void patchGlobalRelocations() { + for( Node src : _externData.keySet() ) { + String name = _externData.get(src); + Integer target = _code._externDataAddresses.get(name); + if( target==null ) throw new IllegalArgumentException("Unresolved C data symbol '"+name+"'"); + int start = opStart(src); + ((RIPRelSize)src).patch(this,start,opLen(src),target-start); + } for( Node src : _externals.keySet() ) { int start = opStart(src); String dst = _externals.get(src); diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java index c7ee1359..468721a4 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java @@ -52,6 +52,9 @@ public class ConFldOffNode extends ConstantNode { public Node asOffset() { int fldx = _fname==" len" ? _ts._fields.length : _ts.find(_fname); if( fldx == -1 ) return null; + // " len" selects the terminal size slot, not a Field. + if( fldx < _ts._fields.length && _ts._fields[fldx]._extern ) + return new ExternOffsetNode(_fname).peephole(); return ConstantNode.make(TypeInteger.constant(_ts.offset(fldx))).peephole(); } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ExternOffsetNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ExternOffsetNode.java new file mode 100644 index 00000000..409ce5d1 --- /dev/null +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ExternOffsetNode.java @@ -0,0 +1,19 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.util.BAOS; +import java.util.HashMap; +import java.util.IdentityHashMap; + +// A field address supplied by the C linker, not a displacement from the base. +// The class base remains available for ideal type/alias analysis. Instruction +// selection replaces it with this symbol's address and a zero displacement. +public class ExternOffsetNode extends ExternNode { + public ExternOffsetNode(String name) { super(TypeInteger.BOT,name); } + @Override public Tag serialTag() { return Tag.ExternOffset; } + @Override public Node copy() { return new ExternOffsetNode(_extern); } + @Override public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node,Integer> 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<Field> 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<Type> 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<Type> 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<String, Type> TYPES ) { return makeFrom( _t._close(name, TYPES )); } @Override Type _upgradeType(HashMap<String,Type> 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<String,Integer> 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<len; i++ ) { Field f0 = _fields[i], f1 = that._fields[i]; - if( !f0._fname.equals(f1._fname) || f0._alias != f1._alias ) + if( !f0._fname.equals(f1._fname) || f0._alias != f1._alias || f0._extern != f1._extern ) return BOT; } @@ -465,7 +466,7 @@ public class TypeStruct extends Type { @Override public int alignment() { int align = 0; for( Field f : _fields ) - align = Math.max(align, f._t.alignment()); + if( !f._extern ) align = Math.max(align, f._t.alignment()); return align; } @@ -614,7 +615,8 @@ public class TypeStruct extends Type { int[] cnts = new int[5]; // Count of fields at log field size int flen = _fields.length; for( int i=0; i<flen; i++ ) - cnts[_fields[i]._t.log_size()]++; // Log size is 0(byte), 1(i16/u16), 2(i32/f32), 3(i64/dbl) + if( !_fields[i]._extern ) + cnts[_fields[i]._t.log_size()]++; // Log size is 0(byte), 1(i16/u16), 2(i32/f32), 3(i64/dbl) int off = 0, idx = 0; // Base common struct fields go here, e.g. Mark/Klass // Compute offsets to the start of each power-of-2 aligned fields. int[] offs = new int[4]; @@ -625,6 +627,7 @@ public class TypeStruct extends Type { // Assign offsets to all fields. // Really a hidden radix sort. for( int i=0; i<flen; i++ ) { + if( _fields[i]._extern ) { _offs[idx++] = -1; continue; } int log = _fields[i]._t.log_size(); _offs[idx++] = offs[log]; // Field offset offs[log] += 1<<log; // Next field offset at same alignment diff --git a/chapter25/src/main/smp/sys/io.smp b/chapter25/src/main/smp/sys/io.smp index 33fbdb5e..d6263ffe 100644 --- a/chapter25/src/main/smp/sys/io.smp +++ b/chapter25/src/main/smp/sys/io.smp @@ -12,7 +12,7 @@ val read = { i32 fd -> int len = 0; while( true ) { i32 rez = libc.read(fd,ptr,buf#); - if( rez > buf# ) libc._exit(libc.errno); + if( rez > buf# ) libc._exit(libc.errno()); len += rez; if( rez < buf# ) break; diff --git a/chapter25/src/main/smp/sys/libc.smp b/chapter25/src/main/smp/sys/libc.smp index 133b5bf7..17ee65cb 100644 --- a/chapter25/src/main/smp/sys/libc.smp +++ b/chapter25/src/main/smp/sys/libc.smp @@ -30,8 +30,9 @@ i32 STDERR = 2; // status { i32 -> i32 } _exit = "C"; -// Error return from many native calls -i32 errno = "C"; +// errno is not an ordinary C data symbol; read it through the native runtime. +{ i32 } simple_errno = "C"; +val errno = { -> simple_errno(); }; // addr len prot flags fd off -> void* { i64 i64 i32 i32 i32 i32 -> i64 } mmap = "C"; // mmap flags diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter25Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter25Test.java index 41225097..bde25ab7 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter25Test.java +++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter25Test.java @@ -21,6 +21,87 @@ import org.junit.Test; import static org.junit.Assert.*; public class Chapter25Test { + @Test + public void testExternData() throws IOException { + String src = """ + i32 counter="C"; + i8 small="C"; + f64 fraction="C"; + {int} bump="C"; + val test_global={ -> + val before=counter; + counter=before; + counter=++counter+1; + counter+=1; + val mid=bump(); + small++; + fraction=fraction+0.5; + return before*10000+mid*100+counter; + }; + """; + TestC.runC(src,"extern_data","",-1); + } + + @Test + public void testExternDataEncoding() { + for( String cpu : new String[]{"arm","riscv"} ) { + CodeGen code = new CheckedCodeGen("i32 counter=\"C\"; counter=counter+3; return counter;"); + // Exercise signed low-12 relocation arithmetic, past the first page. + int addr = 0x3800; + code._externDataAddresses.put("counter",addr); + code.driver(cpu,"SystemV",true,false); + byte[] image = new byte[1<<20]; + var bits = code._encoding._bits; + System.arraycopy(bits.buf(),0,image,0,bits.size()); + if( cpu.equals("arm") ) { + EvalArm64 arm = new EvalArm64(image,1<<16); + arm.st4(addr,-7); + assertEquals(0,arm.step(1000)); + assertEquals(-4,arm.regs[0]); + assertEquals(-4,arm.ld4s(addr)); + } else { + EvalRisc5 r5 = new EvalRisc5(image,1<<16); + r5.st4(addr,-7); + assertEquals(0,r5.step(1000)); + assertEquals(-4,r5.regs[10]); + assertEquals(-4,r5.ld4s(addr)); + } + } + } + + @Test + public void testExternDataImports() throws IOException { + Path dir = Files.createTempDirectory(Path.of("build/objs"),"extern_data_"); + String globals = "i32 counter=\"C\"; val read={ -> counter; };"; + CodeGen globalCode = new CodeGen(null,dir.toString(),null,"Globals",globals,126,true,TypeInteger.BOT) + .driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,false,false); + var clz = globalCode.compunit()._clz; + int counter = clz.find("counter"); + assertTrue(clz._fields[counter]._extern); + assertEquals(-1,clz.offset(counter)); + assertEquals(clz.remove(counter).size(),clz.size()); + // Load the serialized declaration and ideal graph from Globals.o. + String src = """ + i32 counter="C"; + val test_global={ -> + val before=Globals.counter; + Globals.counter=before+1; + counter+=2; + return before*100+Globals.read(); + }; + """; + Ary<String> paths = new Ary<>(new String[]{dir.toString()}); + for( boolean imported : new boolean[]{true,false} ) { + Path root = dir.resolve(imported ? "binary" : "source"); + Files.createDirectories(root.resolve("Main")); + if( !imported ) Files.writeString(root.resolve("Main/Globals.smp"),globals); + 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+"/main"+(TestC.OS.startsWith("Windows") ? ".exe" : ""))); + } + } + @Test public void testPrintingConstantPool() throws Exception { com.seaofnodes.simple.codegen.PrintRegTestSupport.checkConstantPool(); } @@ -312,6 +393,13 @@ return rez < buf# ? 0 : sys.libc._exit(-2); } @Test + public void testErrnoAccessor() throws IOException { + String src = "sys.libc.close(-1); return sys.libc.errno()==0;"; + TestC.run(src,"errnoAccessor",new Ary<>(new String[]{SYS_BLDDIR}), + TestC.CALL_CONVENTION,null,null,"",-1); + } + + @Test public void testBubbles() throws IOException { String src = Files.readString( Path.of("docs/examples/BubbleSort.smp")); String exe = TestC.compile(src,"BubbleSort",new Ary<>(new String[]{SYS_BLDDIR}), diff --git a/chapter25/src/test/java/com/seaofnodes/simple/EvalRisc5.java b/chapter25/src/test/java/com/seaofnodes/simple/EvalRisc5.java index a4cc7044..633e931e 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/EvalRisc5.java +++ b/chapter25/src/test/java/com/seaofnodes/simple/EvalRisc5.java @@ -252,7 +252,7 @@ public class EvalRisc5 { case 2 -> (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/src/test/java/com/seaofnodes/simple/progs/extern_data.c b/chapter25/src/test/java/com/seaofnodes/simple/progs/extern_data.c new file mode 100644 index 00000000..9325f65e --- /dev/null +++ b/chapter25/src/test/java/com/seaofnodes/simple/progs/extern_data.c @@ -0,0 +1,22 @@ +#include <stdint.h> +#include <stdio.h> + +int32_t counter=7; +int8_t small=-3; +double fraction=1.25; + +int64_t bump(void) { counter+=5; return counter; } +__attribute__((CALL_CONV)) extern int64_t test_global(void); + +int main(void) { + int64_t result=test_global(); + if(result!=71515 || counter!=15 || small!=-2 || fraction!=1.75) { + printf("result=%lld counter=%d small=%d fraction=%g\n", + (long long)result,counter,small,fraction); + return 1; + } + // A second call must observe C's new values, not a Simple initialization copy. + counter=-2; + result=test_global(); + return result!=-19394 || counter!=6 || small!=-1 || fraction!=2.25; +} diff --git a/chapter25/src/test/java/com/seaofnodes/simple/progs/extern_data_import.c b/chapter25/src/test/java/com/seaofnodes/simple/progs/extern_data_import.c new file mode 100644 index 00000000..f65e5c84 --- /dev/null +++ b/chapter25/src/test/java/com/seaofnodes/simple/progs/extern_data_import.c @@ -0,0 +1,14 @@ +#include <stdint.h> +#include <stdio.h> + +int32_t counter=7; +__attribute__((CALL_CONV)) extern int64_t test_global(void); + +int main(void) { + int64_t result=test_global(); + if(result!=710 || counter!=10) { + printf("result=%lld counter=%d\n",(long long)result,counter); + return 1; + } + return 0; +} diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/README.md b/chapter25/src/test/java/com/seaofnodes/simple/spill/README.md index fae44126..6c78d46f 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/spill/README.md +++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/README.md @@ -27,11 +27,11 @@ Chapter 25 cannot parse all earlier sources unchanged. Adaptations are explicit: All entries replay **through RegAlloc**, checking scheduled register constraints. The historical phase column is provenance, not a promise to run those old native -harnesses in Chapter 25. In particular, the adapted Bubble Sort still exposes a -pre-existing ARM extern-data encoding limitation (`i32 errno="C"; return 0;` is -sufficient to reproduce it). Allocation succeeds; ARM encoding does not. That -separate failure remains in the backport queue. Do not count this corpus as -historical runtime coverage. +harnesses in Chapter 25. The adapted Bubble Sort retains its historical +`i32 errno="C"` declaration for allocation comparability. Direct data bindings +now encode on all three targets, but this declaration cannot link to a libc +whose errno is thread-local or accessor-based. Current system-library code uses +`libc.errno()` instead. Do not count this corpus as historical runtime coverage. `SpillStats` then compiles the current system library through encoding at seed 456, and runs Chapter25Test with its existing seeds and native checks. Together |
