diff options
Diffstat (limited to 'chapter25/src')
13 files changed, 391 insertions, 104 deletions
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 86e83372..6fc66e4f 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java @@ -733,6 +733,8 @@ public class CodeGen { // make a machine node for "this". private Node _instSelect( Node n, IdentityHashMap<Node,Node> map ) { if( n==null ) return null; + if( n instanceof StoreNode st && StaticData.preinitialized(this,st) ) + return _instSelect(st.mem(),map); Node x = map.get(n); if( x !=null ) return x; // Been there, done that diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java index 2681066e..d1326cee 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java @@ -30,9 +30,6 @@ public class CompUnit { //BAOS _serial; // Serialized IR for this ELF file Ary<CompUnit> _deps; // CompUnits that this CompUnit depends on - //// List of symbols exported by this compilation unit, and their Node - //// definitions. - //public HashMap<String,Node> _exported; // Per-Compilation-Unit Start/StopNodes, keeping alive all exported Nodes. // Null means no code loaded (yet). public StartCUNode _start; 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 7423edbd..1b6281b8 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java @@ -133,16 +133,22 @@ public class ElfWriter { // The relocations; 3 words per relocation public class ReloSection extends DataSection { ReloSection( int info ) { - super(".rela.text", SHT_RELA, SHF_INFO_LINK, 24/*entsize*/); + this(".rela.text",info); + } + ReloSection(String name,int info) { + super(name, SHT_RELA, SHF_INFO_LINK, 24/*entsize*/); _link = 2; _info = info; } void writeReloOffPlus(int offset, int symidx, byte relo) { + writeRelo(offset,symidx,relo,-4); + } + void writeRelo(int offset,int symidx,byte relo,long addend) { assert symidx > 0; write8(offset); write8(((long)symidx << 32L) | relo); - write8(-4); + write8(addend); } } @@ -287,7 +293,7 @@ public class ElfWriter { _strtab = new StringSection(); // place all the symbols - int nlocals = 1/*null*/+7/*number of sections*/+1/*Simple section*/; + int nlocals = 1/*null*/+9/*number of sections*/+1/*Simple section*/; SymbolSection symbols = new SymbolSection(nlocals); // we've already constructed these entire sections in the encoding phase @@ -324,12 +330,35 @@ public class ElfWriter { relocations.writeReloOffPlus(offset, symidx, (byte)2/*PC32*/); } - // Write relocations for the constant pool - String cpoolPrefix = _code.entryClinitName().replaceAll("[^A-Za-z0-9_$]", "_") + "$CPOOL$"; + // Stable function identities are also addressable from data, including + // anonymous functions which have no source-level exported name. + HashMap<String,Integer> dataSymbols=new HashMap<>(); + for( FunNode fun : _code._linker ) + if( fun!=null && !fun.isDead() && _code.owns(fun) && enc.opStart(fun)>=0 ) { + String name=_code._fidxs.symbol(fun.sig().fidx()); + dataSymbols.put(name,symbols.symbol(name,text._index,SYM_BIND_GLOBAL,SYM_TYPE_FUNC,enc.opStart(fun),0)); + } + for( StaticData.ObjectData obj : enc._data.objects.values() ) { + int section=obj.external ? 0 : obj.readOnly ? rodata._index : rwdata._index; + dataSymbols.put(obj.symbol,symbols.symbol(obj.symbol,section,SYM_BIND_GLOBAL,SYM_TYPE_OBJECT, + obj.external ? 0 : obj.offset,obj.external ? 0 : obj.size)); + } for( Encoding.Relo relo : enc._bigCons.values() ) { - DataSection data = relo.readOnly() ? rodata : rwdata; - int symidx = symbols.symbol(cpoolPrefix+symbols.gidx(), data._index, SYM_BIND_GLOBAL, SYM_TYPE_FUNC, relo._target, relo._t.size()); - relocations.writeReloOffPlus(relo._opStart+relo._off, symidx, relo._elf ); + StaticData.ObjectData obj=enc._data.object(relo._t); + relocations.writeReloOffPlus(relo._opStart+relo._off,dataSymbols.get(obj.symbol),relo._elf); + } + ReloSection roRelos=new ReloSection(".rela.rodata",rodata._index); + 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); + if( sym==null ) { + sym=symbols.symbol(name,0,SYM_BIND_GLOBAL,SYM_TYPE_NOTYPE); + dataSymbols.put(name,sym); + } + // AMD64 absolute 32/64-bit addresses; linker rejects overflow. + byte kind=(byte)(relo.logSize()==2 ? 10 : 1); + (relo.object().readOnly ? roRelos : rwRelos).writeRelo(relo.object().offset+relo.offset(),sym,kind,0); } // Create a "Simple" section for Simple types and ir 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 7cb92884..8e9b99db 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Encoding.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Encoding.java @@ -41,6 +41,8 @@ public class Encoding implements com.seaofnodes.isa.CodeSink { public final BAOS _cpool = new BAOS(); // Constant r/o pool public final BAOS _sdata = new BAOS(); // Static r/w pool public final HashMap<Node,Relo> _bigCons = new HashMap<>(); + public final StaticData _data; + public final HashMap<String,Integer> _externalAddresses = new HashMap<>(); // Big Constant relocation info. public static class Relo { @@ -70,6 +72,7 @@ public class Encoding implements com.seaofnodes.isa.CodeSink { Encoding( CodeGen code ) { _code = code; + _data = new StaticData(this); } // Shortcut to the defining register @@ -217,10 +220,8 @@ public class Encoding implements com.seaofnodes.isa.CodeSink { // encodings and compact the code, changing all the offsets. compactShortForm(); - // Write the constant pool - writeConstantPool(_cpool,true ); - // Write the static memory - writeConstantPool(_sdata,false); + // Place pool objects and record relocations within their contents. + _data.write(); // Patch RIP-relative and local encodings now. patchLocalRelocations(); @@ -517,52 +518,6 @@ public class Encoding implements com.seaofnodes.isa.CodeSink { } } - // -------------------------------------------------- - // Write the constant pool into the BAOS and optionally patch locally - void writeConstantPool( BAOS bits, boolean ro ) { - padN(16,bits); - - // radix sort the big constants by alignment - Ary<Relo>[] raligns = new Ary[5]; - for( Node op : _bigCons.keySet() ) { - Relo relo = _bigCons.get(op); - if( relo.readOnly() != ro ) - continue; - int align = relo._t.alignment(); - Ary<Relo> relos = raligns[align]==null ? (raligns[align]=new Ary<>(Relo.class)) : raligns[align]; - relos.add(relo); - } - - - // Types can be used more than once; collapse the dups - HashMap<Type,Integer> targets = new HashMap<>(); - - // By alignment - for( int align = raligns.length-1; align >= 0; align-- ) { - Ary<Relo> relos = raligns[align]; - if( relos == null ) continue; - for( Relo relo : relos ) { - // Map from relo to constant start and patch - Integer target = targets.get(relo._t); - if( target==null ) { - targets.put(relo._t,target = bits.size()); - // Write constant into constant pool - switch( relo._t ) { - case TypeTuple tt -> throw Utils.TODO("no tuples here, use structs instead"); - case TypeStruct ts -> addStruct(bits,ts); - // Simple primitive (e.g. larger int, float, function ptr) - default -> addN(align,relo._t,bits); - } - } - // Record target address and opcode start. - // Target is relative to the cpool/sdata start. - relo._target = target; - relo._opStart= opStart(relo._op); - } - } - - } - // Emit a single scalar as bits static void addN( int log, Type t, BAOS bits ) { long x = switch( t ) { @@ -577,36 +532,22 @@ public class Encoding implements com.seaofnodes.isa.CodeSink { addN(log,x,bits); } - // Structs use internal field layout - private void addStruct( BAOS bits, TypeStruct ts ) { - // Field order by offset - int[] layout = ts.layout(); - 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++; }; - // Constant array fields are special - if( f._fname=="[]" ) { // Must be a constant array - ((TypeConAry)f._t).write(bits); - off += ((TypeConAry)f._t).len(); - } else { - int log = f._t.log_size(); - addN(log,f._t.isConstant() ? f._t : f._t.makeZero(),bits); - off += 1<<log; - } - } - } - - // A series of libc/external calls that Simple can link against in a JIT. // Since no runtime in the JVM process, using magic numbers for the CPU // emulators to pick up on. public static int SENTINEL_CALLOC = -4; public static int SENTINEL_WRITE = -8; + int externalAddress(String name) { + Integer address=_externalAddresses.get(name); + if( address!=null ) return address; + return switch(name) { + case "calloc" -> SENTINEL_CALLOC; + case "write" -> SENTINEL_WRITE; + default -> throw new IllegalArgumentException("Unresolved external function '"+name+"'"); + }; + } + void patchGlobalRelocations() { for( Node src : _externData.keySet() ) { String name = _externData.get(src); @@ -618,11 +559,7 @@ public class Encoding implements com.seaofnodes.isa.CodeSink { for( Node src : _externals.keySet() ) { int start = opStart(src); String dst = _externals.get(src); - int target = switch( dst ) { - case "calloc" -> SENTINEL_CALLOC; - case "write" -> SENTINEL_WRITE ; - default -> throw Utils.TODO(); - }; + int target = externalAddress(dst); ((RIPRelSize)src).patch(this, start, opLen(src), target - start); } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalBits.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalBits.java index b0060024..bc9c9d39 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalBits.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalBits.java @@ -129,6 +129,12 @@ public class GlobalBits { return next(clz,order); } + // Stable native symbol identity, independent of this compilation's dense IDs. + String owner(int local) { return _local2Clz.at(local); } + String symbol(int local) { + return _local2Clz.at(local)+"$F$"+_local2Order.at(local); + } + // Write out to BAOS enough bits to unwind the local back to global index void packed( BAOS baos, HashMap<String,Integer> strs ) { // Unwind a local index to the clz. diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java index d249b80c..252b5bf0 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java @@ -23,10 +23,11 @@ public class LinkMem { // Patch local ops, e.g. loading float constants from the constant pool for( Node op : enc._bigCons.keySet() ) { Encoding.Relo relo = enc._bigCons.get(op); - int target = relo._target+(relo.readOnly() ? cpool : sdata); + int target = enc._data.address(enc._data.object(relo._t),cpool,sdata); ((RIPRelSize)relo._op).patch(enc, relo._opStart, enc.opLen(relo._op), target - relo._opStart); } + enc._data.link(cpool,sdata); return _code; } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/StaticData.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/StaticData.java new file mode 100644 index 00000000..f9f16008 --- /dev/null +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/StaticData.java @@ -0,0 +1,195 @@ +package com.seaofnodes.simple.codegen; + +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.BAOS; +import java.util.*; + +// Pool objects have identity independently of their initial field values. +// References in their contents use the same symbols in ELF and emulator images. +public final class StaticData { + public static final class ObjectData { + public final Type type; + public final String symbol; + public final boolean readOnly, external; + public final int alignment, size; + public int offset; + ObjectData(Type t, String name, boolean ro, boolean ext) { + type=t; symbol=name; readOnly=ro; external=ext; + alignment=t.alignment(); + int align=1<<alignment; + size=t instanceof TypeStruct ts ? (Math.max(1,ts.size())+align-1)&-align : align; + } + } + public record Relocation(ObjectData object, int offset, int logSize, Type value) {} + + final Encoding enc; + final LinkedHashMap<Object,ObjectData> objects = new LinkedHashMap<>(); + final ArrayList<Relocation> relocations = new ArrayList<>(); + final String prefix; + + public ArrayList<ObjectData> entries() { + ArrayList<ObjectData> entries=new ArrayList<>(objects.values()); + entries.sort(Comparator.comparingInt((ObjectData obj) -> obj.alignment).reversed()); + return entries; + } + + StaticData(Encoding enc) { + this.enc=enc; + prefix=enc._code.entryClinitName()+"$DATA$"; + } + + static boolean isClass(Type t) { + return t instanceof TypeStruct ts && Parser.startsClzPrefix(ts._name); + } + + static CompUnit owner(CodeGen code,TypeStruct ts) { + return owner(code,ts._name.substring(Parser.CLZ.length())); + } + static CompUnit owner(CodeGen code,String name) { + if(name==null) return null; + CompUnit owner=null; + for( CompUnit cu : code._compunits.values() ) + if( cu._cname!=null && (name.equals(cu._cname) || name.startsWith(cu._cname+".")) && + (owner==null || cu._cname.length()>owner._cname.length()) ) owner=cu; + return owner; + } + + // A final field whose declaration already denotes this exact constant is + // available as that value to every ideal Load. Materialize it in the object + // image instead of emitting the redundant initializer Store. This lowering + // leaves serialized ideal IR intact, so imports retain their memory edges. + static boolean preinitialized(CodeGen code,StoreNode st) { + if( !(st.ptr()._type instanceof TypeMemPtr ptr) || !ptr._one || !isClass(ptr._obj) ) return false; + CompUnit owner=owner(code,ptr._obj); + if( owner==null || owner._src==null ) return false; + if( !(Parser.TYPES.get(ptr._obj._name) instanceof TypeStruct ts) ) return false; + Field fld=ts.field(st._name); + if( fld==null || !fld._final || !fld._t.isConstant() || !st.val()._type.isConstant() ) return false; + Type value=st.val()._type, declared=fld._t; + if( declared instanceof TypeFunPtr fp && value instanceof TypeFunPtr vp ) return fp.fidx()==vp.fidx(); + if( declared instanceof TypeMemPtr ptr0 && value instanceof TypeMemPtr ptr1 ) + return pointerConstant(ptr0) && ptr0._obj==ptr1._obj; + return declared==value; + } + + ObjectData object(Type t) { + Object key=isClass(t) ? ((TypeStruct)t)._name + : t instanceof TypeFunPtr fp ? functionSymbol(fp) : t; + ObjectData old=objects.get(key); + if( old!=null ) return old; + boolean clz=isClass(t); + if( clz && Parser.TYPES.get(((TypeStruct)t)._name) instanceof TypeStruct canonical ) t=canonical; + CompUnit owner=clz ? owner(enc._code,(TypeStruct)t) + : t instanceof TypeFunPtr fp ? owner(enc._code,enc._code._fidxs.owner(fp.fidx())) : null; + String symbol=clz ? ((TypeStruct)t)._name.substring(Parser.CLZ.length())+".$class" + : t instanceof TypeFunPtr fp ? functionSymbol(fp) : prefix+objects.size(); + ObjectData obj=new ObjectData(t,symbol,!clz && (!(t instanceof TypeStruct ts) || ts.isConstant()), + owner!=null && owner._src==null); + objects.put(key,obj); + // Register before following fields: class objects may refer to each other. + if( !obj.external && t instanceof TypeStruct ts ) + for( Field f : ts._fields ) + if( !f._extern && f._t.isConstant() ) target(f._t); + return obj; + } + + private static boolean pointerConstant(TypeMemPtr ptr) { + return ptr.notNull() && (isClass(ptr._obj) || (ptr._obj.isConstant() && (!ptr._obj.isAry() || ptr._obj.isConAry()))); + } + + private void target(Type t) { + if( t instanceof TypeMemPtr ptr && pointerConstant(ptr) ) object(ptr._obj); + if( t instanceof TypeFunPtr fp && fp.notNull() && + enc._code.link(fp)==null && !enc._code._externFunc.containsKey(fp.fidx()) ) + // A dead function still needs a unique non-null identity for comparisons. + object(fp); + } + + void write() { + ArrayList<Encoding.Relo> refs=new ArrayList<>(enc._bigCons.values()); + refs.sort(Comparator.comparingInt(r -> r._op._nid)); + for( Encoding.Relo ref : refs ) object(ref._t); + // Definitions belong to their compilation unit, even if every local use folded. + ArrayList<CompUnit> units=new ArrayList<>(enc._code._compunits.values()); + units.sort(Comparator.comparing(cu -> cu._cname==null ? "" : cu._cname)); + for( CompUnit cu : units ) if( cu._src!=null && cu._clz!=null ) object(cu._clz); + for( ObjectData obj : entries() ) { + if( obj.external ) continue; + BAOS bits=obj.readOnly ? enc._cpool : enc._sdata; + Encoding.padN(1<<obj.alignment,bits); + obj.offset=bits.size(); + if( obj.type instanceof TypeStruct ts ) writeStruct(obj,ts,bits); + else Encoding.addN(obj.alignment,obj.type,bits); + while( bits.size()<obj.offset+obj.size ) bits.write(0); + } + for( Encoding.Relo ref : refs ) { + ObjectData obj=object(ref._t); + ref._target=obj.offset; + ref._opStart=enc.opStart(ref._op); + } + } + + private void writeStruct(ObjectData obj, TypeStruct ts, BAOS bits) { + int[] layout=ts.layout(); + for( int idx : layout ) { + if( idx==ts._fields.length ) continue; + Field f=ts._fields[idx]; + if( f._extern ) continue; + int off=ts.offset(idx); + while( bits.size()<obj.offset+off ) bits.write(0); + if( f._fname.equals("[]") ) ((TypeConAry)f._t).write(bits); + else { + Type value=f._t.isConstant() ? f._t : f._t.makeZero(); + int log=f._t.log_size(); + Encoding.addN(log,value,bits); + if( (value instanceof TypeFunPtr fp && fp.notNull()) || + (value instanceof TypeMemPtr ptr && pointerConstant(ptr)) ) + relocations.add(new Relocation(obj,off,log,value)); + } + } + while( bits.size()<obj.offset+ts.size() ) bits.write(0); + } + + String functionSymbol(TypeFunPtr fp) { + String ext=enc._code._externFunc.get(fp.fidx()); + return ext!=null ? ext : enc._code._fidxs.symbol(fp.fidx()); + } + + String targetSymbol(Type value) { + if( value instanceof TypeMemPtr ptr ) return object(ptr._obj).symbol; + TypeFunPtr fp=(TypeFunPtr)value; + return enc._code.link(fp)==null && !enc._code._externFunc.containsKey(fp.fidx()) + ? object(fp).symbol : functionSymbol(fp); + } + + void link(int cpool, int sdata) { + for( Relocation rel : relocations ) { + long address; + if( rel.value instanceof TypeMemPtr ptr ) address=address(object(ptr._obj),cpool,sdata); + else { + TypeFunPtr fp=(TypeFunPtr)rel.value; + String ext=enc._code._externFunc.get(fp.fidx()); + FunNode fun=enc._code.link(fp); + if( ext!=null ) address=enc.externalAddress(ext); + else if( fun==null ) address=address(object(fp),cpool,sdata); + else { + if( !enc._code.owns(fun) ) throw new IllegalArgumentException("Unlinked function "+functionSymbol(fp)); + address=enc.opStart(fun); + } + } + // Emulator native-call sentinels are signed 32-bit addresses. + if( rel.logSize==2 && (address<Integer.MIN_VALUE || address>0xffff_ffffL) ) + throw new IllegalArgumentException("Function address outside 32 bits: "+address); + BAOS bits=rel.object.readOnly ? enc._cpool : enc._sdata; + int off=rel.object.offset+rel.offset; + for( int i=0; i<(1<<rel.logSize); i++ ) bits.buf()[off+i]=(byte)(address>>>(8*i)); + } + } + + int address(ObjectData obj,int cpool,int sdata) { + if( obj.external ) throw new IllegalArgumentException("Unlinked class object "+obj.symbol); + return obj.offset+(obj.readOnly ? cpool : sdata); + } +} diff --git a/chapter25/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java b/chapter25/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java index 994065d8..e8218afd 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java @@ -79,12 +79,19 @@ public final class ASMPrinter extends AssemblyAdapter<Node> { if( !encoded() ) return pools; int base=(codeEnd+15)&-16; if( _enc._cpool.size()>0 ) - pools.add(new Pool("Constant Pool",_enc._cpool.buf(),0,base,entries(_enc._bigCons.values(),true))); + pools.add(new Pool("Constant Pool",_enc._cpool.buf(),0,base,objects(true))); base=(base+_enc._cpool.size()+15)&-16; if( _enc._sdata.size()>0 ) - pools.add(new Pool("Static Data",_enc._sdata.buf(),0,base,entries(_enc._bigCons.values(),false))); + pools.add(new Pool("Static Data",_enc._sdata.buf(),0,base,objects(false))); return pools; } + private ArrayList<Data> objects(boolean ro) { + var data=new ArrayList<Data>(); + for( var obj : _enc._data.entries() ) + if( !obj.external && obj.readOnly==ro ) + data.add(new Data(obj,obj.symbol+": "+obj.type.str(),obj.alignment,obj.size)); + return data; + } // Retain this diagnostic entry point for checking that pool printing never lays out types. static int printConstantPool(int address, SB sb, BAOS bits, HashMap<Node,Encoding.Relo> relos, boolean ro, String name) { diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter20Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter20Test.java index 40e9172e..b46a54b2 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter20Test.java +++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter20Test.java @@ -258,7 +258,7 @@ val _hashCodeString = { !String self -> }; """; testCPU(src,"x86_64_v2", "SystemV",21,null); - testCPU(src,"riscv" , "SystemV",10,null); + testCPU(src,"riscv" , "SystemV",8,null); testCPU(src,"arm" , "SystemV",8,null); } diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java index 8010e245..0cfdb577 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java +++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java @@ -384,7 +384,7 @@ val _hashCodeString = { !String self -> }; """; testCPU(src,"x86_64_v2", "SystemV",21,null); - testCPU(src,"riscv" , "SystemV",10,null); + testCPU(src,"riscv" , "SystemV",8,null); testCPU(src,"arm" , "SystemV",8,null); } diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter22Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter22Test.java index e8c080fd..7484a6ae 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter22Test.java +++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter22Test.java @@ -146,9 +146,9 @@ struct _Person { i32 age;}; p.age = (arg<<17)>>17; return 0; """; - assertEquals(82, testCPUSize(src, "x86_64_v2","win64", 3 )); - assertEquals(96, testCPUSize(src, "riscv", "SystemV", 4 )); - assertEquals(96, testCPUSize(src, "arm" , "SystemV", 4 )); + assertEquals(65, testCPUSize(src, "x86_64_v2","win64", 3 )); + assertEquals(76, testCPUSize(src, "riscv", "SystemV", 4 )); + assertEquals(76, testCPUSize(src, "arm" , "SystemV", 4 )); // do assertEquals here EvalRisc5 R5 = TestRisc5.build( src, "sext_str_not_fold_away", 0, 4, false); @@ -178,9 +178,9 @@ return 0; int trap_arm = A5.step(100); assertEquals(0,trap_arm); - assertEquals(82, testCPUSize(src, "x86_64_v2","win64",3 )); - assertEquals(96, testCPUSize(src, "riscv", "SystemV",4 )); - assertEquals(96, testCPUSize(src, "arm", "SystemV",4 )); + assertEquals(65, testCPUSize(src, "x86_64_v2","win64",3 )); + assertEquals(76, testCPUSize(src, "riscv", "SystemV",4 )); + assertEquals(76, testCPUSize(src, "arm", "SystemV",4 )); } @@ -202,9 +202,9 @@ return 0; int trap_arm = A5.step(100); assertEquals(0,trap_arm); - assertEquals(72, testCPUSize(src, "x86_64_v2","win64",3 )); - assertEquals(92, testCPUSize(src, "riscv", "SystemV",5 )); - assertEquals(92, testCPUSize(src, "arm", "SystemV",5)); + assertEquals(55, testCPUSize(src, "x86_64_v2","win64",3 )); + assertEquals(72, testCPUSize(src, "riscv", "SystemV",5 )); + assertEquals(72, testCPUSize(src, "arm", "SystemV",5)); // do assertEquals here } diff --git a/chapter25/src/test/java/com/seaofnodes/simple/StaticDataTest.java b/chapter25/src/test/java/com/seaofnodes/simple/StaticDataTest.java new file mode 100644 index 00000000..070b7a9e --- /dev/null +++ b/chapter25/src/test/java/com/seaofnodes/simple/StaticDataTest.java @@ -0,0 +1,112 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.StaticData; +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) { + return code._encoding._data.entries().stream() + .filter(obj -> obj.symbol.endsWith("Test.$class")).findFirst().orElseThrow(); + } + + // 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(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)); + } + + // 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)); + } +} diff --git a/chapter25/src/test/java/com/seaofnodes/simple/TestC.java b/chapter25/src/test/java/com/seaofnodes/simple/TestC.java index 64518b0d..b08be824 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/TestC.java +++ b/chapter25/src/test/java/com/seaofnodes/simple/TestC.java @@ -167,6 +167,7 @@ public abstract class TestC { // pointers as 4bytes. var params = new Ary<>(String.class); params.add("gcc"); + params.add(OS.startsWith("Windows") ? "-Wl,--image-base,0x400000" : "-no-pie"); if( cfile!=null ) params.add(cfile); // Associated C driver, usually has a `main` params.add(obj); // Standalone Simple programs export `simple_main`; the small C |
