diff options
Diffstat (limited to 'chapter25/src/main')
185 files changed, 4598 insertions, 11494 deletions
diff --git a/chapter25/src/main/c/simple_crt.c b/chapter25/src/main/c/simple_crt.c deleted file mode 100644 index 8656a3cc..00000000 --- a/chapter25/src/main/c/simple_crt.c +++ /dev/null @@ -1,61 +0,0 @@ -#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 -// immutable and non-null to Simple code. -typedef struct SimpleBytes { - uint32_t len; - unsigned char bytes[]; -} SimpleBytes; - -typedef struct SimpleArgs { - uint32_t len; - uint32_t pad; - SimpleBytes *args[]; -} SimpleArgs; - -__attribute__((CALL_CONV)) -extern int64_t simple_main(SimpleArgs *args); - -static SimpleArgs *simple_args(int argc, char **argv) { - if (argc < 0 || (uint64_t)argc > UINT32_MAX) - return NULL; - - SimpleArgs *args = calloc(1, sizeof(*args) + (size_t)argc * sizeof(args->args[0])); - if (args == NULL) - return NULL; - args->len = (uint32_t)argc; - - for (int i = 0; i < argc; i++) { - size_t len = strlen(argv[i]); - if (len > UINT32_MAX) { - free(args); - return NULL; - } - SimpleBytes *str = malloc(sizeof(*str) + len); - if (str == NULL) { - for (int j = 0; j < i; j++) free(args->args[j]); - free(args); - return NULL; - } - str->len = (uint32_t)len; - memcpy(str->bytes, argv[i], len); - args->args[i] = str; - } - return args; -} - -int main(int argc, char **argv) { - SimpleArgs *args = simple_args(argc, argv); - if (args == NULL) - return 1; - // The language-level hidden receiver is not part of the emitted ABI for - // free functions; source argument zero occupies native argument zero. - return (int)simple_main(args); -} diff --git a/chapter25/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter25/src/main/java/com/seaofnodes/simple/IterPeeps.java index e65ef6d9..2fe4e172 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/IterPeeps.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/IterPeeps.java @@ -1,14 +1,12 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.Opto; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; import com.seaofnodes.simple.util.Ary; import com.seaofnodes.simple.node.*; import java.util.Arrays; import java.util.BitSet; -import java.util.IdentityHashMap; import java.util.Random; /** @@ -40,7 +38,7 @@ import java.util.Random; * <li>Our strong invariant is that for all Nodes, either they are on the worklist * OR no peephole applies. This invariant is easy to check, although expensive. * Basically the normal "iterate peepholes to a fixed point" is linear, and this - * check is linear at each peephole step... so quadratic overall. It's a useful + * check is linear at each peephole step... so quadratic overall. Its a useful * assert, but one we can disable once the overall algorithm is stable - and * then turn it back on again when some new set of peepholes is misbehaving. * The code for this is turned on in `IterPeeps.iterate` as `assert @@ -50,11 +48,11 @@ import java.util.Random; public class IterPeeps { public final WorkList<Node> _work; - final WorkList<CallEndNode> _workInline; - private int _cnt; + + private final WorkList<CallEndNode> _workInline; public IterPeeps( long seed ) { - _work = new WorkList<>(seed); + _work = new WorkList<>(seed); _workInline = new WorkList<>(seed); } @@ -64,53 +62,36 @@ public class IterPeeps { public void addAll( Ary<Node> ary ) { _work.addAll(ary); } /** - * Iterate peepholes and inlining to a fixed point + * Iterate peepholes to a fixed point */ public void iterate( CodeGen code ) { - boolean didInline = false; - Ary<Node> defer = new Ary<>(Node.class); while( true ) { - // Clean up everything that does not grow the code iteratePeeps(code); - - // Pick an inline candidate, no real heuristic, first come, first served - boolean inlined = false; CallEndNode cend; - while( (cend=_workInline.pop()) != null) { - if( cend.isDead() ) continue; - byte inline = cend.maybeInline(); - if( inline == -1 ) defer.add(cend); - if( inline > 0 ) { - inlined = true; - break; - } - } - // Inlined, run peeps until clean again - if( inlined ) { didInline = true; continue; } - // No more candidates, check the defer list - if( !didInline ) break; // No more progress, so all the "maybe inline after cleanup" do not progress - // Some inlining happened, retry all the "try again after cleanup" calls - didInline = false; - code.addAll(defer); - defer.clear(); + while( (cend=_workInline.pop())!=null ) + if( cend.inlineSmall() ) break; + if( cend==null ) return; } } - // Run all the code-reduction and type-lifting peeps as possible + // Only try growing inlines once the shrinking peepholes reach a fixed point. private void iteratePeeps( CodeGen code ) { - assert !CodeGen.expensiveAssert(0) || (progressOnList(code, _work) && schedulableUses(code)); + assert progressOnList(code, _work, true); + int cnt=0; Node n; while( (n=_work.pop()) != null ) { if( n.isDead() ) continue; - _cnt++; // Useful for debugging, searching which peephole broke things - var obs = code._obs; // Collect peephole neighbors for graph display + if( n instanceof CallEndNode cend ) _workInline.push(cend); + cnt++; // Useful for debugging, searching which peephole broke things + var obs = CodeGen.CODE._obs; if( obs != null ) obs.before(n); Node x = n.peepholeOpt(); - if( n instanceof CallEndNode cend ) - _workInline.push(cend); if( x != null ) { - assert !x.isDead(); // Peepholes return alive answers + if( x.isDead() ) { + if( obs != null ) obs.after(n, x, true); + continue; + } // peepholeOpt can return brand-new nodes, needing an initial type set if( x._type==null ) x.setType(x.compute()); // Changes require neighbors onto the worklist @@ -120,31 +101,22 @@ public class IterPeeps { // Everybody gets a free "go again" in case they didn't get // made in their final form. _work.push(x); - // A self-returning peephole can have rewritten its input - // edges. The new defs gained a user and may have - // backwards, user-sensitive peepholes of their own. - for( Node z : x._inputs ) _work.push(z); // If the result is not self, revisit all inputs (because // there's a new user), and replace in the graph. if( x != n ) { - //for( Node z : n. _inputs ) _work.push(z); + for( Node z : n. _inputs ) _work.push(z); for( Node z : x._outputs ) _work.push(z); n.subsume(x); } } // If there are distant neighbors, move to worklist n.moveDepsToWorklist(); - // Very expensive assert. - assert !CodeGen.expensiveAssert(_cnt) || (progressOnList(code, _work) && schedulableUses(code)); + assert progressOnList(code, _work, true); // Very expensive assert } - boolean unused = n.isUnused(); - if( unused ) { - assert !(n instanceof StopNode); // StopNodes can die if all code in the compunit dies + if( n.isUnused() && !(n instanceof StopNode) ) n.kill(); // Just plain dead - } - if( obs != null ) obs.after(n, x!=null ? x : unused ? n : null, true); + if( obs != null ) obs.after(n, x, true); } - assert !CodeGen.expensiveAssert(0) || schedulableUses(code); } @@ -161,112 +133,27 @@ public class IterPeeps { // {@link #Node.addDep} which is a side effect in an assert. The {@link // #midAssert} is used to stop this side effect. // Pessimistic solver assert - public static boolean progressOnList(CodeGen code, WorkList<Node> list ) { + public static boolean progressOnList(CodeGen code, WorkList<Node> list, boolean dir ) { code._midAssert = true; Node changed = code._stop.walk( n -> { - // Ignore most in-progress things - if( n.iskeep() ) return null; - // Types must be forwards, even if on the worklist. + Node m = n; Type nval = n.compute(); - assert nval.isa(n._type) : "Non-monotonic peep: "+n+"#"+n._nid+" old="+n._type+" new="+nval+" inputs="+inputTypes(n)+" peep="+n; - if( list.on(n) ) - return null; // On worklist is ok! - if( n instanceof CallEndNode cend ) { - if( code._iter._workInline.on(cend) ) - return null; // On inline worklist is ok! - assert cend.maybeInline() <= 0 : "Inline fired and not on worklist, CallEndNode#"+cend._nid; + if( (!n.iskeep() || n._nid<=8) && // Types must be forwards, even if on worklist + ( dir + ? nval.isa(n._type) // Pesi: new value lifts over old + : n._type.isa(nval) // Opto: new value falls over old + ) ) { + if( list.on(n) ) return null; + m = n.peepholeOpt(); + if( m==null ) return null; } - // Off list: must be live, in GVN table, and no peeps. - assert n.nOuts() > 0 : "Unused live node: "+n; - assert code._gvn.get(n)==n : "Not in GVN table: "+n; - Node m = n.peepholeOpt(); - assert m==null : "Peep fired and not on worklist, "+n.getClass().getSimpleName()+"#"+n._nid+" -> "+m; + System.err.println("BREAK HERE FOR BUG"); return m; }); code._midAssert = false; return changed==null; } - private static String inputTypes(Node n) { - StringBuilder sb = new StringBuilder("["); - for( Node in : n._inputs ) - sb.append(in==null ? "null" : in.getClass().getSimpleName()+"#"+in._nid+"="+in+":"+in._type+":keep="+in.iskeep()).append(','); - return sb.append(']').toString(); - } - - // GCM assumes every movable node can be placed no earlier than its inputs - // and no later than the LCA of its uses. Catch graphs where a use escapes - // above an input-defined branch before the later scheduling pass walks off - // the top of the idom tree. - private static final IdentityHashMap<Node,CFGNode> earlyCache = new IdentityHashMap<>(); - private static final BitSet VISIT = new BitSet(); - public static boolean schedulableUses(CodeGen code) { - earlyCache.clear(); - Node bad = code._stop.walk( n -> { - if( n.iskeep() || n.isDead() || n.isConst() || - n instanceof CFGNode || n instanceof PhiNode || n instanceof ProjNode ) - return null; - assert VISIT.isEmpty(); - CFGNode early = CFGNode.earlyCFG(n,earlyCache,VISIT); - VISIT.clear(); - if( early == null ) return null; - CFGNode lca = null; - for( Node use : n._outputs ) { - CFGNode ublk = useBlock(n,use); - if( ublk != null ) - lca = ublk.domLCA(lca,null); - } - if( lca == null ) return null; - if( !early.sameFun(lca) ) return null; - assert early.dominates(lca,null) : badSchedule(n,early,lca); - return null; - }); - return bad == null; - } - - private static CFGNode useBlock(Node n, Node use) { - if( use == null ) - return null; - if( use instanceof PhiNode phi ) { - CFGNode found = null; - for( int i=1; i<phi.nIns(); i++ ) - if( phi.in(i)==n ) { - if( i >= phi.region().nIns() ) - return null; - found = phi.region().cfg(i).domLCA(found,null); - } - return found; - } - return CFGNode.safeCFG(use); - } - - private static String badSchedule(Node n, CFGNode early, CFGNode lca) { - StringBuilder sb = new StringBuilder("Unschedulable data node ") - .append(n.getClass().getSimpleName()).append('#').append(n._nid) - .append(" early=").append(cfg(early)) - .append(" use-lca=").append(cfg(lca)) - .append(" node=").append(n).append("\ninputs:"); - for( int i=0; i<n.nIns(); i++ ) { - Node in = n.in(i); - sb.append("\n in").append(i).append(": ").append(node(in)) - .append(" cfg=").append(cfg(CFGNode.safeCFG(in))); - } - sb.append("\noutputs:"); - for( Node use : n._outputs ) - if( use != null ) - sb.append("\n use ").append(node(use)) - .append(" block=").append(cfg(useBlock(n,use))); - return sb.toString(); - } - - private static String node(Node n) { - return n==null ? "null" : n.getClass().getSimpleName()+"#"+n._nid+" "+n; - } - - private static String cfg(CFGNode cfg) { - return cfg==null ? "null" : cfg.getClass().getSimpleName()+"#"+cfg._nid+" "+cfg; - } - /** * Classic WorkList, with a fast add/remove, dup removal, random pull. * The Node's nid is used to check membership in the worklist. @@ -314,16 +201,12 @@ public class IterPeeps { for( E n : ary ) push(n); } - public void addAll( E[] es ) { - for( E n : es ) - push(n); - } - /** * True if Node is on the WorkList */ public boolean on( E x ) { return _on.get(x._nid); } + public boolean isEmpty() { return _len==0; } /** * Removes a random Node from the WorkList; null if WorkList is empty @@ -337,13 +220,11 @@ public class IterPeeps { return x; } - public boolean isEmpty() { return _len==0; } - //Node[] asAry() { return Arrays.copyOf(_es,_len); } - //public void clear() { - // _len = 0; - // _on.clear(); - // _R.setSeed(_seed); - // _totalWork = 0; - //} + public void clear() { + _len = 0; + _on.clear(); + _R.setSeed(_seed); + _totalWork = 0; + } } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/Parser.java b/chapter25/src/main/java/com/seaofnodes/simple/Parser.java index 5ceda6a9..56233250 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/Parser.java @@ -1,14 +1,11 @@ package com.seaofnodes.simple; -import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; import com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.util.BAOS; +import com.seaofnodes.simple.util.Utils; import com.seaofnodes.simple.node.ScopeNode.Kind; - -import static com.seaofnodes.simple.util.Utils.TODO; - import java.util.*; /** @@ -20,32 +17,16 @@ import java.util.*; */ public class Parser { + public static ConstantNode ZERO; // Very common node, cached here + public static ConstantNode NIL; // Very common node, cached here + public static XCtrlNode XCTRL; // Very common node, cached here + // Compile driver public final CodeGen _code; // The Lexer. Thin wrapper over a byte[] buffer with a cursor. private Lexer _lexer; - // Class prefix string; TODO: use something short: "%" - public static final String CLZ = "class:"; - private static final String clzPrefix = CLZ; - public static String addClzPrefix( String x ) { - assert !x.startsWith(clzPrefix); - return (clzPrefix+x).intern(); - } - public static boolean startsClzPrefix( String s ) { - return s.startsWith(clzPrefix); - } - public static boolean isPublicClassName( String name ) { - if( startsClzPrefix(name) ) name = name.substring(clzPrefix.length()); - return !name.isEmpty() && name.charAt(0)!='_' && name.indexOf("._")==-1; - } - - // Source file for compilation - public CompUnit _ref; - - // Current classname being parsed - private String _nestedType; /** * Current ScopeNode - ScopeNodes change as we parse code, but at any point of time * there is one current ScopeNode. The reason the current ScopeNode can change is to do with how @@ -97,20 +78,34 @@ public class Parser { */ public final Stack<ScopeNode> _xScopes = new Stack<>(); - ScopeNode _breakScope; // Merge all the while-breaks here - ScopeNode _continueScope; // Merge all the while-continues here - ScopeNode _returnScope; // Merge all the function exits here - Ary<FRefNode> _frefs; // Forward refs created during this parse - TypeStruct _ctorOpenStruct; // Open struct being initialized by an allocation constructor block. - // Constructor exit types survive scope teardown until all fields are known. - // A null entry denotes a constructor with no returning path. - private ArrayList<IdentityHashMap<Var,Type>> _ctorExits; + ScopeNode _continueScope; + ScopeNode _breakScope; // Merge all the while-breaks here + FunNode _fun; // Current function being parsed // Mapping from a type name to a Type. The string name matches // `type.str()` call. - public static final HashMap<String, Type> TYPES = new HashMap<>(); - public static final HashMap<String, Type> INIT_TYPES = - new HashMap<>() {{ + public static HashMap<String, Type> TYPES = defaultTypes(); + + // Mapping from a type name to the constructor for a Type. + public final HashMap<String, StructNode> INITS; + + + public Parser(CodeGen code, TypeInteger arg) { + _code = code; + _scope = new ScopeNode(); + _continueScope = _breakScope = null; + ZERO = con(TypeInteger.ZERO).keep(); + NIL = con(Type.NIL).keep(); + XCTRL= new XCtrlNode().peephole().keep(); + TYPES = defaultTypes(); + INITS = new HashMap<>(); + } + + @Override + public String toString() { return _lexer.toString(); } + + public static HashMap<String, Type> defaultTypes() { + return new HashMap<>() {{ put("bool",TypeInteger.U1 ); put("byte",TypeInteger.U8 ); put("f32" ,TypeFloat .F32); @@ -128,420 +123,172 @@ public class Parser { put("val" ,Type.TOP); // Marker type, indicates type inference put("var" ,Type.BOTTOM); // Marker type, indicates type inference }}; - - - public Parser(CodeGen code ) { - _code = code; } - @Override - public String toString() { return _lexer==null ? "" : _lexer.toString(); } - // Debugging utility to find a Node by index public Node f(int nid) { return _code.f(nid); } - // Read and set CTRL private Node ctrl() { return _scope.ctrl(); } private <N extends Node> N ctrl(N n) { return _scope.ctrl(n); } - // Read and set Memory - private Node mem() { return _scope.mem(1); } - private void mem(Node mem) { _scope.mem(mem); } - // Structural label for precise memory Phis. The '$' prefix cannot be - // referenced in Simple source. - public static String memName(int alias) { return ("$"+alias).intern(); } - - // Ordinary parsing carries one whole-memory value. Alias partitioning is - // graph optimization, not parser state. - private void storeMem( Node st, int alias, Node prior ) { - mem(alias==1 ? st : mergeAlias(prior,alias,st)); - } - - // A semantic graph partition representing the complete memory after a - // precise alias update. ScopeNode treats this as one opaque value. - private Node mergeAlias(Node bulk, int alias, Node precise) { - assert bulk != null : "Missing bulk memory for alias #"+alias; - MemMergeNode merge = new MemMergeNode(null,bulk); - merge.alias(alias,precise); - return peep(merge); - } - - - public Ary<FRefNode> parse( CompUnit ref ) { - assert _scope == null && _breakScope == null && _continueScope == null && _returnScope == null; - assert _frefs == null; - _lexer = new Lexer(ref); - _frefs = new Ary<>(FRefNode.class); - // Starting Scope has control, memory, initial arguments - _scope = new ScopeNode(); + + public void parse() { + // Graph callbacks during scope setup need a source position. + _lexer = new Lexer(sys.SYS); + _scope.define(ScopeNode.CTRL, Type.CONTROL , false, null, _lexer); _scope.define(ScopeNode.MEM0, TypeMem.BOT , false, null, _lexer); - // Track active scopes for Graph display - _xScopes.push(_scope); - ctrl(_code.XCTRL); - mem(con(TypeMem.BOT)); + _scope.define(ScopeNode.ARG0, TypeInteger.BOT, false, null, _lexer); - // File-level struct declaration - _ref = ref; - _nestedType = ref._cname; - String typeName = addClzPrefix(ref._cname); + ctrl(XCTRL); + _scope.mem(con(TypeMem.BOT)); - ReturnNode clzret = parseStruct( true, typeName ); + // Parse the sys import + while( !_lexer.isEOF() ) { + parseStatement(); + _lexer.skipWhiteSpace(); + } - if( !_lexer.isEOF() ) throw _errorSyntax("unexpected"); + // Reset lexer for program text + _lexer = new Lexer(_code._src); + _xScopes.push(_scope); + + // Parse whole program, as-if function header "{ int arg -> body }" + parseFunctionBody( _code._main,loc(),"arg"); + + // Kill an empty default main. Keep only if it was explicitly defined + // (programmer asked for a "main") or it has stuff (i.e. beginner + // default main). + FunNode main = _code.link(_code._main); + StopNode stop = _code._stop; + // Gather an explicit main + FunNode xmain = null; + for( Node n : stop._inputs ) + if( n instanceof ReturnNode ret && "main".equals(ret.fun()._name) ) + if( xmain != null ) throw Utils.TODO(); + else xmain = ret.fun(); + + if( main.ret().expr()._type==Type.TOP && main.uctrl()==null ) { + // Kill an empty default main; so it does not attempt to put a + // "main" in any final ELF file + main.setDef(1,XCTRL); // Delete default start input + stop.delDef(stop._inputs.find(main.ret())); + if( xmain != null ) + _code.setMain(xmain); - // All still-unresolved forward refs - // should immediately resolve to a file-based name which is a Simple - // class type. - Ary<FRefNode> frefs = new Ary<>(FRefNode.class); - for( FRefNode fref : _frefs ) { - if( fref.nIns() != 1 && fref._con == FRefNode.FREF_TYPE ) continue; - CompUnit cu = ParseAll.findCompUnitOrThrow(_code,_ref,fref._name); - fref._con = TypeMemPtr.make((byte)2,TypeStruct.make(addClzPrefix(cu._cname),true),true); - frefs.push(fref); + } else { + // We have a non-empty default main. + if( xmain != null ) + // Found an explicit "main" AND we have a default "main" + throw error("Cannot define both an explicit main and a default main"); + main._name = "main"; } - // The class being published - ref._clz = (TypeStruct)TYPES.get(typeName); + if( !_lexer.isEOF() ) throw _errorSyntax("unexpected"); // Clean up and reset _xScopes.pop(); _scope.kill(); - _scope = null; - _frefs = null; - - return frefs; + for( StructNode init : INITS.values() ) + init.unkeep().kill(); + INITS.clear(); + stop.peephole(); } - - /** * Parses a function body, assuming the header is parsed. */ - private ReturnNode parseFunctionBody( TypeFunPtr sig, Lexer loc, String[] ids) { + private ReturnNode parseFunctionBody( TypeFunPtr sig, Lexer loc, String... ids) { return parseFunctionBody(sig, loc, null, ids); } - private ReturnNode parseFunctionBody( TypeFunPtr sig, Lexer loc, java.util.BitSet finals, String[] ids) { - // Record & restore the existing scope vars. The function parse might - // update fields, so when the parse is done reset back to the pre-parse - // state. - Node[] preParse = _scope.save(); - - // Parse function body normally - ReturnNode ret = _parseFunctionBody(null, sig,loc,finals,ids); - - // Reset all fields to pre-parse days - _scope.restore(preParse); - return ret; - } - - - // Parse a function body - - // Stack the {break,continue,return} scopes and a new lexical scope. - // Build a FunNode and parameters, extending scope by parm names. - // Parse the function body - // - while( not end-of-function ) - // - - parseStatement - // - - If isInit, also incrementally extend the self-type. - // If isInit - // - upgrade and close the self-type - // - Gather fields and store values - // - <init> returns self, not last - // Unstack the {break,continue,return} scopes. - - private ReturnNode _parseFunctionBody( String funName, TypeFunPtr sig, Lexer loc, String[] ids) { - return _parseFunctionBody(funName, sig, loc, null, ids); - } - - private ReturnNode _parseFunctionBody( String funName, TypeFunPtr sig, Lexer loc, java.util.BitSet finals, String[] ids) { - boolean isInit = FunNode.isInit(funName); - boolean isCtor = isInit || isExplicitConstructorName(funName); - String typeName = isCtor ? ((TypeMemPtr)sig._sig[0])._obj._name : null; + private ReturnNode parseFunctionBody( TypeFunPtr sig, Lexer loc, java.util.BitSet finals, String... ids) { + // If this is a method, record & restore the existing struct fields, + // which are also the struct initializers. The method might update + // mutable fields, so when the function parse is done reset back to + // pre-function-parse values. + Node[] preParse = new Node[_scope.nIns()]; + for( int i=0; i<_scope.nIns(); i++ ) + preParse[i] = _scope.in(i)==null ? null : _scope.in(i).keep(); // Stack parser state on the local Java stack, and unstack it later - _scope.push(new Kind.Func(funName)); - ScopeNode breakScope = _breakScope; _breakScope = null; + FunNode oldfun = _fun; + ScopeNode breakScope = _breakScope; _breakScope = null; ScopeNode continueScope = _continueScope; _continueScope = null; - ScopeNode returnScope = _returnScope; _returnScope = null; - int oldUID = _code.UID(); // Used to approximate function size - FunNode fun = (FunNode)peep(new FunNode(loc(),sig, funName, _ref, null, _ref._start)); + FunNode fun = _fun = (FunNode)peep(new FunNode(loc(),sig,_nestedType,null,_code._start)); // Once the function header is available, install in linker table - // allowing recursive functions. Linker matches on declared args and // exact fidx, and ignores the return (because the fidx will only match // the exact single function). _code.link(fun); - Node rpc = new ParmNode("$rpc",0,TypeRPC.BOT,fun,ConstantNode.seed(TypeRPC.BOT).peephole()).peephole(); + Node rpc = new ParmNode("$rpc",0,TypeRPC.BOT,fun,con(TypeRPC.BOT)).peephole(); + + // Build a multi-exit return point for all function returns + RegionNode r = new RegionNode( null,null,null).init(); + assert r.inProgress(); + PhiNode rmem = new BulkMemPhiNode(ScopeNode.MEM0,r,null).init(); + PhiNode rrez = new PhiNode(ScopeNode.ARG0,r,null).init(); + ReturnNode ret = new ReturnNode(r, rmem, rrez, rpc, fun).init(); + fun.setRet(ret); + assert ret.inProgress(); + _code._stop.addDef(ret); + // Pre-call the function from Start, with worse-case arguments. This // represents all the future, yet-to-be-parsed functions calls and // external calls. - _scope.ctrl(fun); // Scope control from function - // Private mem alias tracking per function - Node defaultMem = new ProjNode(_ref._start,1,ScopeNode.MEM0).peephole(); - Node privMem = new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,defaultMem).peephole(); - mem(privMem); + _scope.push(new Kind.Func()); + ctrl(fun); // Scope control from function + // Unknown callers supply whole memory; field aliases are discovered lazily. + _scope.mem(new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,con(TypeMem.BOT)).peephole()); // All args, "as-if" called externally for( int i=0; i<ids.length; i++ ) { Type t = sig.arg(i); - // Args take a default input if called from Start/external-world - _scope.define(ids[i], t, (i==0 && ids[0]=="self") || (finals!=null && finals.get(i)), new ParmNode(ids[i],i+2,t,fun,ConstantNode.seed(t).peephole()).peephole(), loc); + _scope.define(ids[i], t, finals==null ? t instanceof TypeNil : finals.get(i), new ParmNode(ids[i],i+2,t,fun,con(t)).peephole(), loc); } - // The initializer owns its run-once flag store. Parent initialization - // is a structural requirement, executed before this body by CLInit lowering. - if( fun.isClz() ) { - Node self = fun.parm(2); - Field flag = ((TypeMemPtr)self._type)._obj.field("$init"); - Node prior = mem().keep(); - Node st = peep(new StoreNode(loc,"$init",flag._alias,flag._t,ctrl(),prior,self, - fldoff(self,"$init"),con(1),false)); - storeMem(st,flag._alias,prior); - prior.unkeep(); - } - - // Parse the function body. - Node last=_code.ZERO; // Last statement as the default result - - int nvar = 0; - while (!peek('}') && !_lexer.isEOF()) { - // Parse a statement; record last statement as default return - last = _code.add(parseStatement()); - // For <init> and <clinit>, local vars are really struct fields and - // get exposed as discovered. This allows late local functions to - // use early declared fields. - if( isInit ) - nvar = updateSelfAsFieldsDiscovered( typeName, nvar ); - } - - ParmNode self = fun.parm(2); - // Last expression is the return - if( ctrl()._type==Type.CONTROL ) { - // Constructors get special return types - if( fun.isClz() ) { - assert last!=null; - } else if( isCtor ) { - last = fun.parm(3); - } - addReturn(last); - // exit path is now dead (nothing live after a "return") - } - if( isCtor ) - upgradeSelfTypeAndStoreFields( typeName, fun.isClz(), self, fun.parm(3), isInit ); - - // Build a return from the _returnScope. - // Can be no returns for never-exit functions - Node rctl = _returnScope==null ? _code.XCTRL : _returnScope.ctrl().peephole(); - Node rmem = rctl==_code.XCTRL ? con(TypeMem.TOP) : _returnScope.mem(); - Node expr = rctl==_code.XCTRL ? con(Type.TOP) : _returnScope._inputs.last().peephole(); - ReturnNode ret = (ReturnNode)peep(new ReturnNode(rctl, rmem, expr, rpc, fun)); - fun.setRet(ret); - // Approximate function size, for inlining heuristics - fun._approxUIDs = _code.UID() - oldUID; - for( Node cend : ret.outs() ) - if( cend instanceof CallEndNode ) - throw TODO(); //_code.add(cend); // have call ends ALREADY? just barely made the Return - - // Export (and keep-alive) functions until prove dead - _ref.addFun(_code,fun); - - // Unstack parser state - if( _returnScope != null ) _returnScope.kill(); - _returnScope = returnScope; - _continueScope = continueScope; - _breakScope = breakScope; - _scope.pop(); - - return ret; - } + // Parse the body + Node last=ZERO; + while (!peek('}') && !_lexer.isEOF()) + last = parseStatement(); - private int updateSelfAsFieldsDiscovered( String typeName, int nvar ) { - int lex = _scope.klast()._lexSize + 2/*Skip self and selfMem*/; - // Load self type in and out of the TYPES hashtable. - TypeStruct tself = (TypeStruct)TYPES.get(typeName); - // Start looking for newly declared vars at the lexical scope start, - // skipping two ("self" and "selfmem") and fields seen already. - while( lex + nvar < _scope.nIns() ) { - Var v = _scope.var(lex + nvar++); - // Field with sharper type - Field old = tself.field(v._name); - 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) - : old.makeFrom(v.type()); - tself = tself.addOrUpdate(fld); - } - TYPES.put(typeName,tself); - return nvar; - } - - - void upgradeSelfTypeAndStoreFields( String typeName, boolean isClz, ParmNode self, ParmNode smem, boolean upgrade ) { - int lex = _scope.klast()._lexSize; - assert _scope.var(lex)._name=="self"; - TypeStruct tself = (TypeStruct)TYPES.get(typeName); - int base = lex+2; // Skip ctrl, mem - if( upgrade ) { - // Default <init>/<clinit> defines the struct and can sharpen fields. - // A named constructor only initializes the already-closed struct. - for( int i=0; i<tself.nkids(); i++ ) { - Field tfld = tself._fields[i]; - int vidx = fieldVarIdx(base,tfld._fname); - if( vidx == -1 ) - continue; - Var v = _scope.var(vidx); - // Forward-ref fields where placed in tself but will get promoted - // to an outer scope - and should not be in tself. - if( v._fref ) - tself = tself.remove(i--); - else if( v.type() != tfld._t ) { - assert v.type().isa(tfld._t); - tself = tself.replace( tfld.makeFrom( v.type() ) ); - } + // Last expression is the return except for the top-level main + if( ctrl()._type==Type.CONTROL ) + if( fun.sig() != _code._main ) + fun.addReturn(ctrl(), _scope.mem(), last); + else { + fun.setDef(1,XCTRL); // Kill default main + _code.addAll(fun._outputs); } - // Close Struct type after parsing struct body. - TYPES.put( tself._name, tself = tself.close() ); - // Improve self, selfMem types in scope. - // Class self-type is a singleton pointer. Instance <init> receives the - // broad allocation pointer, while private memory tracks initialized fields. - self._declaredType = self._type = isClz ? TypeMemPtr.make((byte)2,tself,true) : TypeMemPtr.make(constructorRecv(tself)); - FunNode fun = self.fun(); - fun.setSig(fun.sig().makeFrom(self._type,0)); - if( !isClz ) // <init> returns upgraded private memory - smem._declaredType = smem._type = TypeMem.makePrivate(tself); - } - - if( !upgrade ) { - IdentityHashMap<Var,Type> exit = null; - if( _returnScope!=null && !_returnScope.ctrl()._type.isHigh() ) { - exit = new IdentityHashMap<>(); - int first = _scope._kinds.at(_scope.depth()-2)._lexSize+2; - for( int i=first; i<lex; i++ ) - exit.put(_scope.var(i),_returnScope.in(i)._type); - } - _ctorExits.add(exit); - } else if( !isClz ) { - checkInitFields(tself,base); - } - - // When can _returnScope be null here? A never-exit constructor will - // not have any returns, and thus no need to gather values and store - // them into the (never) constructed object - if( _returnScope==null ) - return; - - // A MemMerge to gather field updates as stores. Classes update the - // normal public memory. <init> updates the private memory that got - // passed in - is treated like a normal argument and not like memory. - Node mmm = isClz ? _returnScope.mem(1) : smem; - - - // Store constructor results into fields - for( int i=0; i<tself.nkids(); i++ ) { - Field fld = tself._fields[i]; - int vidx = upgrade - ? fieldVarIdx(base,fld._fname) - : fieldVarIdxBefore(lex,fld._fname); - if( vidx == -1 ) - continue; - Var v = _scope.var(vidx); - 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. - Node val = _returnScope.in(vidx); - if( upgrade && v._uninit && - !initialized(val._type) ) continue; - // Store value into extended struct - Node prior = mmm.keep(); - Node st = peep(new StoreNode(null, fld._fname, fld._alias, fld._t, null, prior, self, off(tself,fld._fname), val, fld._final)); - mmm = mergeAlias(prior,fld._alias,st); - prior.unkeep(); - } - mmm = mmm.peephole(); - - if( !isClz ) - // Stuff private mem into normal expression return value. - _returnScope.setDef(_returnScope.nIns()-1,mmm); - else - // Parser memory is one opaque whole-memory edge. Publish the - // completed class-field aggregate as the <clinit> return memory. - _returnScope.mem(mmm); - //_code.add(mmm); - } - - // Validate each user constructor, or an implicit empty one, before the - // declaration scope goes away. Later fields use their declaration defaults. - private static boolean initialized(Type t) { - return t!=Type.BOTTOM && !t.isHigh() && (!(t instanceof TypeNil tn) || tn.notNull()); - } - - private void checkInitFields(TypeStruct ts, int base) { - if( _returnScope==null || _returnScope.ctrl()._type.isHigh() ) return; - boolean implicit = _ctorExits.isEmpty(); - if( implicit ) _ctorExits.add(new IdentityHashMap<>()); - for( IdentityHashMap<Var,Type> exit : _ctorExits ) { - if( exit==null ) continue; - for( Field fld : ts._fields ) { - int idx = fieldVarIdx(base,fld._fname); - if( idx == -1 ) continue; - Var v = _scope.var(idx); - if( !v._uninit || v._fref ) continue; - Type t = exit.getOrDefault(v,_returnScope.in(idx)._type); - if( !initialized(t) ) - throw error("'"+ts._name+"' is not fully initialized, field '"+fld._fname+ - (implicit ? "' needs to be set in a constructor" : - "' is only partially set in the constructor"),v._loc); - } - } - } - - private int fieldVarIdx( int base, String name ) { - for( int i=base; i<_scope.nIns(); i++ ) - if( _scope.var(i)._name == name ) - return i; - return -1; - } - - // Named constructors update field Vars from the enclosing initializer - // scope. Constructor arguments and locals begin at limit and must not - // shadow the field state gathered into private memory. - private int fieldVarIdxBefore( int limit, String name ) { - for( int i=limit-1; i>=0; i-- ) - if( _scope.var(i)._name == name ) - return i; - return -1; - } - - private boolean isConstructorDecl(String name) { - TypeStruct self = _scope.constructorSelf(); - return self != null && name == initFieldName(self._name); - } - - private boolean inExplicitConstructor() { - Kind.Func func = (Kind.Func)_scope._kinds.at(_scope.enclosingFuncOrDecl()); - return isExplicitConstructorName(func._name); - } + // Pop off the inProgress node on the multi-exit Region merge + assert r.inProgress(); + r ._inputs.pop(); + rmem._inputs.pop(); + rrez._inputs.pop(); + r._loc = loc(); // Final position + assert !r.inProgress(); + + // Force peeps, which have been avoided due to inProgress + ret.setDef(1,rmem.peephole()); + ret.setDef(2,rrez.peephole()); + ret.setDef(0,r.peephole()); + ret = (ReturnNode)ret.peephole(); + + // Function scope ends + _scope.pop(); + _fun = oldfun; + _breakScope = breakScope; + _continueScope = continueScope; - private boolean isExplicitConstructorName(String funName) { - TypeStruct self = _scope.constructorSelf(); - return self != null && funName == (self._name+"."+initFieldName(self._name)).intern(); - } + // Reset all fields to pre-parse days + for( int i=0; i<preParse.length; i++ ) + _scope.setDef(i,preParse[i]==null ? null : preParse[i].unkeep()); - private static TypeStruct constructorRecv(TypeStruct self) { - Field[] fs = new Field[self._fields.length]; - for( int i=0; i<fs.length; i++ ) { - Field fld = self._fields[i]; - fs[i] = fld.makeFrom(fld._t.makeStorage()); - } - return TypeStruct.make(self._name,self._open,fs); + return ret; } - /** * Parses a block * @@ -554,11 +301,9 @@ public class Parser { private Node parseBlock(Kind kind) { // Enter a new scope _scope.push(kind); - Node last = _code.ZERO; - while (!peek('}') && !_lexer.isEOF()) { - _code.add(last); + Node last = ZERO; + while (!peek('}') && !_lexer.isEOF()) last = parseStatement(); - } // Exit scope last.keep(); _scope.pop(); @@ -581,8 +326,7 @@ public class Parser { else if (matchx("break") ) return parseBreak(); else if (matchx("continue")) return parseContinue(); else if (matchx("struct") ) return parseStruct(); - else if (inStructDeclaration() && matchx("new")) return parseConstructorDeclaration(); - else if (matchx(";") ) return _code.ZERO; // Empty statement + else if (matchx(";") ) return null; // Empty statement // Break ambiguity around leading function types and starting a block else if (peek('{') && !isTypeFun() ) { match("{"); @@ -592,31 +336,6 @@ public class Parser { else return parseDeclarationStatement(); } - private boolean inStructDeclaration() { - return _scope.klast() instanceof Kind.Func fun && FunNode.isInstance(fun._name); - } - - // Constructor declaration: new TypeName = { args -> body }; - private Node parseConstructorDeclaration() { - TypeStruct self = _scope.constructorSelf(); - assert self != null; - String name = requireId(); - String expect = initFieldName(self._name); - if( name != expect ) - throw error("Constructor name '" + name + "' must match type name '" + expect + "'"); - require("="); - Node ctor = constructorFunc(name); - TypeFunPtr sig = (TypeFunPtr)ctor._type; - String clzName = addClzPrefix(self._name); - TypeStruct clz = (TypeStruct)TYPES.get(clzName); - Field old = clz==null ? null : clz.field("<ctor>"); - if( old != null ) - throw error("Redefining constructor '" + name + "'"); - Field fld = Field.make("<ctor>",sig,_code.alias(_ref._cname),true); - TYPES.put(clzName,clz==null ? TypeStruct.make(clzName,true,fld) : clz.add(fld)); - return require(ctor,";"); - } - /** * Parses a while statement * @@ -667,7 +386,7 @@ public class Parser { // used as an indicator to switch off peepholes of the region and // associated phis; see {@code inProgress()}. - ctrl(new LoopNode(loc(),null,ctrl()).peephole()); // Note we set back edge to null here + ctrl(new LoopNode(loc(),ctrl()).peephole()); // Note we set back edge to null here // At loop head, we clone the current Scope (this includes all // names in every nesting level within the Scope). @@ -686,8 +405,8 @@ public class Parser { // IfNode takes current control and predicate Node ifNode = new IfNode(ctrl(), pred.keep()).peephole(); // Setup projection nodes - CFGNode ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); - CFGNode ifF = (CFGNode)new CProjNode(ifNode.unkeep(), 1, "False").peephole(); + Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); + Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole(); // for( ;;next ) body int nextPos = -1, nextEnd = -1; @@ -740,8 +459,6 @@ public class Parser { // it sets the second input to the corresponding input from the back // edge. If the phi is redundant, it is replaced by its sole input. var exit = _breakScope; - head.balanceLoopFRefs(_scope); - exit.balanceLoopFRefs(_scope); head.endLoop(_scope, exit); head.unkeep().kill(); @@ -759,12 +476,12 @@ public class Parser { _xScopes.push( exit ); } _scope = exit; - return _code.ZERO; + return ZERO; } private ScopeNode jumpTo(ScopeNode toScope) { ScopeNode cur = _scope.dup(); - ctrl(_code.XCTRL); // Kill current scope + ctrl(XCTRL); // Kill current scope // Prune nested lexical scopes that have depth > than the loop head // We use _breakScope as a proxy for the loop head scope to obtain the depth while( cur.depth() > _breakScope.depth() ) @@ -782,7 +499,7 @@ public class Parser { private void checkLoopActive() { if (_breakScope == null) throw error("No active loop for a break or continue"); } - private Node parseContinue() { checkLoopActive(); _continueScope = require(jumpTo( _continueScope ),";"); return _code.ZERO; } + private Node parseContinue() { checkLoopActive(); _continueScope = require(jumpTo( _continueScope ),";"); return ZERO; } private Node parseBreak () { checkLoopActive(); // At the time of the break, and loop-exit conditions are only valid if @@ -790,8 +507,8 @@ public class Parser { // conditions here, not the union. _breakScope.removeGuards(_breakScope.ctrl()); _breakScope = require(jumpTo(_breakScope ),";"); - _breakScope.addGuards((CFGNode)_breakScope.ctrl(), null, false); - return _code.ZERO; + _breakScope.addGuards(_breakScope.ctrl(), null, false); + return ZERO; } // Look for an unbalanced `)`, skipping balanced @@ -801,7 +518,7 @@ public class Parser { // Next X char handles skipping complex comments switch( _lexer.nextXChar() ) { case Character.MAX_VALUE: - throw TODO(); + throw Utils.TODO(); case ')': if( --paren<0 ) { posT( pos() - 1 ); @@ -835,8 +552,8 @@ public class Parser { pred.keep(); Node ifNode = new IfNode(ctrl(), pred).peephole(); // Setup projection nodes - CFGNode ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); - CFGNode ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole().keep(); + Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); + Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole().keep(); // In if true branch, the ifT proj node becomes the ctrl // But first clone the scope and set it as current ScopeNode fScope = _scope.dup(); // Duplicate current scope @@ -852,7 +569,7 @@ public class Parser { case ":" -> parseAsgn(); // pred ? asgn; case "&&" -> parseLogical(); // pred && expr case "||" -> pred; // pred || expr_is_ignored - default -> throw TODO(); + default -> throw Utils.TODO(); }; lhs.keep(); _scope.removeGuards(ifT); @@ -875,14 +592,12 @@ public class Parser { Node rhs = switch( fside ) { case "else" -> (doRHS=match(fside)) ? parseStatement() : con(lhs._type.makeZero()); case ":" -> (doRHS=match(fside)) ? parseAsgn() : con(lhs._type.makeZero()); - case "&&" -> con(lhs._type.makeZero()); // pred && expr_is_ignored - case "||" -> parseLogical(); // pred || expr - default -> throw TODO(); + case "&&" -> rhs = pred; + case "||" -> rhs = parseLogical(); + default -> throw Utils.TODO(); }; rhs.keep(); _scope.removeGuards(ifF); - // The RHS of `||` is parsed on the false side, so retain its updated - // control, memory and locals just as an explicit else arm does. if( doRHS || fside.equals("||") ) fScope = _scope; pred.unkeep(); @@ -905,10 +620,9 @@ public class Parser { Node ret = peep(new PhiNode("",r,lhs.unkeep(),rhs.unkeep())); // Immediately fail e.g. `arg ? 7 : ptr` ParseException err; - if( !fside.equals("else") && (err=ret.err()) !=null ) throw err; - ret.keep(); + if( !fside.equals("else") && (err=ret.err()) !=null ) throw err; r.peephole(); - return ret.unkeep(); + return ret; } /** @@ -922,69 +636,20 @@ public class Parser { */ private Node parseReturn() { var expr = require(parseAsgn(), ";"); - return addReturn(expr); + // Force the lazy whole-memory Phi before closing this exit. + _fun.addReturn(ctrl(), _scope.mem(), expr); + ctrl(XCTRL); // Kill control + return expr; } - private Node addReturn(Node expr) { - expr.keep(); - // Need default memory, since it can be lazy, need to force - // a non-lazy Phi - mem(); // Force a pending lazy bulk-memory Phi - // For <init> and <clinit> - all fields in scope will be live-on-exit - // and stored into `self` and need to have lazy-phi's inserted. - int lexN = _scope.enclosingFuncOrDecl(); - Kind.Func k = (Kind.Func)_scope._kinds.at(lexN); - if( FunNode.isInit(k._name) ) { - int nestedLexSize = lexN+1 < _scope.depth() ? _scope._kinds.at(lexN+1)._lexSize : _scope.nIns(); - for( int i=k._lexSize; i<nestedLexSize; i++ ) - _scope.update(_scope.var(i),null); - if( _returnScope != null ) { - Var vexpr = _returnScope._vars.pop(); - Node oldX = _returnScope.removeLast(); - int rlen = _returnScope.nIns(); - while( rlen < nestedLexSize ) { - _returnScope._vars.add(_scope.var(rlen)); - _returnScope.addDef(con(_scope.var(rlen++).type().makeZero())); - } - _returnScope._vars.add(vexpr); - _returnScope.addDef(oldX); - } - } else if( isExplicitConstructorName(k._name) ) { - // Field bindings live in the enclosing declaration, not among the - // constructor's arguments/locals. Force lazy Phis on every exit. - int first = _scope._kinds.at(lexN-1)._lexSize+2; - for( int i=first; i<k._lexSize; i++ ) - _scope.update(_scope.var(i),null); - } - // No prior merge point? Just clone and hang on to it - if( _returnScope == null ) { - _returnScope = _scope.dup(); - while( lexN+1 < _returnScope.depth() ) - _returnScope._pop(); // Pop a nested block scope until we hit the function scope - _returnScope.define("$expr", inferredReturnType(expr._type), true, expr, null); - } else { - // For <init> and <clinit> - ALL FIELDS IN LAST SCOPE are live - // and need to have lazy-phi's inserted. - - // And fields might not match.... will need matching Var/define with default values - int rlen = _returnScope.nIns()-1; - RegionNode r = ctrl(new RegionNode(null, null,_returnScope.ctrl(), _scope.ctrl()).init().keep()); - _returnScope._merge(_scope, r, rlen); - Node oldExpr = _returnScope.in(rlen); - _returnScope.setDef(rlen,new PhiNode("$expr", r, oldExpr, expr).peephole()); - _code.add(r); - _returnScope.ctrl(r.unkeep()); - } - ctrl(_code.XCTRL); // Kill control - return expr.unkeep(); - } /** Parse: [name '='] expr */ private Node parseAsgn() { int old = pos(); String name = _lexer.matchId(); + int old_pre_equals = pos(); // Just a plain expression, no assignment. // Distinguish `var==expr` from `var=expr` if( name==null || KEYWORDS.contains(name) || !matchOpx('=','=') ) @@ -993,12 +658,16 @@ public class Parser { // Find variable to update Var def = _scope.lookup(name); if( def==null ) - def = defineOpenConstructorField(name); - if( def==null ) throw error("Undefined name '" + name + "'"); - if( def._extern!=0 ) // Parse the C binding as a memory lvalue - { pos(old); return parseExpression(); } + // If a method var, allow nested blocks and methods, but not fcns or other structs. + // Else safety check for accessing fields out of function scope. + if( instance(def) ) { + pos(old_pre_equals); // setup for `self.name = expr` + Var selfVar = _scope.lookup("self"); + Node self = liftExpr(_scope.in(selfVar),selfVar.type(),true); + return parsePostfixName(self,name); + } // TOP fields are for late-initialized fields; these have never // been written to, and this must be the final write. Other writes @@ -1006,93 +675,34 @@ public class Parser { if( _scope.in(def._idx)._type!=Type.TOP && def._final && // Inside an allocation, final assign is OK, outside nope. // The alloc() call added the allocation scope - !(_scope.inConstructor() && def._idx >= _scope.klast()._lexSize) && - !inExplicitConstructor() ) + !_scope.canInit(def) ) throw error("Cannot reassign final '"+name+"'"); // Parse assignment expression Node expr = parseAsgn(); // Lift expression, based on type - Type decl = def.type(); // Declared type - Node lift = convertExpr(expr.keep(), decl).keep(); + Node lift = liftExpr(expr.keep(), def.type(), true); // Update _scope.update(name,lift); - - // Check for storing into an instance field instead of a local var - int kx = _scope.kindx(def); // Declaration scope - int fx = _scope.enclosingFunction(); // Enclosing function scope - Kind kk = _scope._kinds.at(kx); - 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 && !inExplicitConstructor() ) { - // Named field offset on an unknown struct type - TypeMemPtr self = (TypeMemPtr)_scope.var(fk._lexSize).type(); - TypeStruct sobj = (TypeStruct)TYPES.get(self._obj._name); - Field fld = sobj.field(name); - int alias = fld._alias; - Node ptr = _scope.in(method._lexSize); - Node off = fldoff(ptr,name); - Node prior = mem().keep(); - Node st = new StoreNode(loc(), name, alias, decl, ctrl(), prior, ptr, off, lift, inExplicitConstructor()).peephole().keep(); - storeMem(st,alias,prior); - st.unkeep(); - prior.unkeep(); - } - lift.unkill(); - - // Return un-lifted expr return expr.unkeep(); } - // Widen ints to floats; narrow wide int types. + // Apply declared access permissions; widen floats and narrow integers. // Early error if types do not match variable. - private Node convertExpr( Node expr, Type t ) { - if( !expr._type.accessISA(t) ) - throw error("Type "+expr._type.str()+" is not of declared type "+t.str()); + private Node liftExpr(Node expr, Type t, boolean isLoad) { + if( t==Type.BOTTOM || t==Type.TOP ) return expr; + // Constructor sentinels record missing initialization, not a value + // needing conversion. Keep them visible to the completion check. + if( expr instanceof ConstantNode && !(expr instanceof FRefNode) && + (expr._type==Type.TOP || expr._type==Type.BOTTOM) ) return expr; if( t instanceof TypeMemPtr tmp && tmp.isFinal() ) expr = peep(new ReadOnlyNode(expr)); - // A FunPtr carries the semantic edge to its function's Return. Do not - // hide that edge behind a Convert which can fold to a plain constant. - if( expr instanceof FunPtrNode && t instanceof TypeFunPtr ) - return expr; - // Auto-widen array to i64 (cast ptr to raw int bits) - if( t == TypeInteger.BOT && expr._type instanceof TypeMemPtr tmp && tmp._obj.isAry() ) - return peep(new AddNode(peep(new PtrToIntNode(expr)),off(tmp._obj,"[]"))); - // Widen ints to floats, narrow wide types + if( expr instanceof FunPtrNode && t instanceof TypeFunPtr ) return expr; return peep(new ConvertNode(t,expr)); } - // Inferred `var` declarations describe writable slots, not the exact - // value used to initialize them. Explicitly typed locals retain the - // user's nullability and numeric-width guarantees; `val` retains the - // initializer's precise type. - private static Type mutableInferredType(Type t) { - return switch( t ) { - case TypeInteger ti -> TypeInteger.BOT; - case TypeFloat tf -> TypeFloat.F64; - case TypeMemPtr tmp -> tmp.makeVar(); - case TypeFunPtr tfp -> tfp; - case TypeConAry<?> a -> a; - case TypePtr ptr -> TypePtr.PTR; - case TypeScalar ts -> TypeScalar.BOT; - default -> t; - }; - } - - // Seed an inferred return merge with the declared numeric family instead - // of a transient constant/range. Pointer nullability remains part of the - // inferred function result and is combined by the later Phi. - private static Type inferredReturnType(Type t) { - return switch( t ) { - case TypeInteger ti -> TypeInteger.BOT; - case TypeFloat tf -> TypeFloat.F64; - default -> t; - }; - } - private Node widenInt( Node expr, Type t ) { return (expr._type instanceof TypeInteger || expr._type==Type.NIL) && t instanceof TypeFloat ? peep(new ToFloatNode(expr)) : expr; @@ -1119,6 +729,23 @@ public class Parser { return require(n,";"); } + // Widen a reassignable binding without discarding the initializer's + // permissions. Lattice glb includes readonly memory and is too broad here. + private Type inferredType(Type t) { + if( t instanceof TypeMemPtr tmp ) { + if( tmp._obj.isAry() ) { + Type elem = tmp._obj._fields[1]._t; + if( elem instanceof TypeConAry ) + elem = ((TypeMemPtr)TYPES.get(tmp._obj._name))._obj._fields[1]._t; + return typeAry(elem,tmp._obj._fields[1]._final).withAccess(tmp._ro).makeNullable(); + } + Type base = TYPES.get(tmp._obj._name); + TypeMemPtr ptr = (TypeMemPtr)base; + return ptr.withAccess(tmp._ro).makeNullable(); + } + return t.glb(false); + } + // The binding modifier sits immediately before the name. It never // changes permissions on the referenced object. private boolean bindingFinal(Type t) { @@ -1137,45 +764,27 @@ public class Parser { Lexer loc = loc(); String name = requireId(); // Optional initializing expression follows - boolean fld_final = false; // Field is final, but not deeply final - boolean uninit = false; // Required non-null constructor field + boolean fld_final = false; // Fixed field independent of pointee access + boolean initialized = match("="); 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) ) { - expr = constructorFunc(name); - } else { - expr = parseAsgn(); - } + if( initialized ) { + expr = isExternDecl() + ? externDecl(name,t) + : parseAsgn(); // var/val, then type comes from expression if( inferType ) { if( expr._type==Type.NIL ) throw error("a not-null/non-zero expression"); t = expr._type; - if( !xfinal ) t = mutableInferredType(t); + if( !xfinal ) t = inferredType(t); // Widen if not final } - // expr is a constant function - if( t instanceof TypeFunPtr decl && expr._type instanceof TypeFunPtr tfp && tfp.isConstant() ) { - t = decl.makeFrom(tfp.fidx()); - FunNode fun = _code.link(tfp); - if( fun != null ) - fun.setName(name); // Assign debug name to Simple function + if( t instanceof TypeFunPtr && expr._type instanceof TypeFunPtr tfp && tfp.isConstant() ) { + if( expr instanceof ExternNode ) t = expr._type; // Upgrade declared type to exact function + else _code.link(tfp).setName(name); // Assign debug name to Simple function } } else { - // Since no initializer, not-final but might need an initializer - // Need an expression to infer the type. // Also, if not-null then need an initializing expression. // Allowed in a class def, because type/init will happen in the constructor. @@ -1183,33 +792,30 @@ public class Parser { throw errorSyntax("=expression"); // Initial value for uninitialized struct fields. expr = switch( t ) { - // Required pointers retain their declared shape, but include - // null until every constructor path assigns them. - case TypeNil tn -> tn.nullable() ? con(Type.NIL) : con(t.meet(Type.NIL)); - case TypeInteger ti -> xfinal ? con(Type.BOTTOM) : _code.ZERO; - case TypeFloat tf -> con(xfinal ? Type.BOTTOM : TypeFloat.FZERO); + // Nullable pointers get a NIL; not-null get a TOP which + // signals that they *must* be initialized in the constructor. + case TypeNil tn -> tn.nullable() ? NIL : con(Type.TOP); + case TypeInteger ti -> xfinal ? con(Type.TOP) : ZERO; + case TypeFloat tf -> con(xfinal ? Type.TOP : TypeFloat.FZERO); // Bottom signals type inference: they must be initialized in // the constructor and that's when we'll discover the type. case Type tt -> { assert tt==Type.BOTTOM; yield con(tt); } }; - // Every non-null pointer without a default is constructor-required. - // Without `!` it is also shallow-final; mutability and definite - // initialization are independent properties. - if( (xfinal && !(t instanceof TypeNil)) || (t instanceof TypeNil tn && !tn.nullable()) ) { - uninit = true; - if( xfinal ) fld_final = true; - } + // Nullable fields are set in the constructor, but remain shallow final. + // e.g. final pointer to a r/w array + if( t instanceof TypeNil tn && !tn.nullable() && xfinal ) + fld_final = true; } // Lift expression, based on type - // Do not let an authoritative Convert hide the nullable - // not-yet-initialized state from constructor-local type flow. - Node lift = uninit ? expr : convertExpr(expr, t); + Node lift = liftExpr(expr, t, true); + // Lift type to the declaration. This will report as an error later if + // we cannot lift the type. // Define a new name if( !_scope.define(name,t,xfinal || fld_final,lift, loc) ) throw error("Redefining name '" + name + "'", loc); - _scope.lookup(name)._uninit = uninit; + _scope.lookup(name)._init = initialized; return lift; } @@ -1221,218 +827,59 @@ public class Parser { * * @return zero */ - private Node parseStruct( ) { - // keyword "struct" already parsed, so expect the struct name next. + private String _nestedType; + private Node parseStruct() { + // "struct" already parsed, so expect the struct name next. + String old = _nestedType; String typeName = requireId(); + if( old!=null ) typeName = old+"."+typeName; + _nestedType = typeName; + // New declared or forward-ref type + TypeStruct ts = TYPES.get(typeName) instanceof TypeMemPtr tmp ? tmp._obj : TypeStruct.open(typeName); + TYPES.put(typeName,TypeMemPtr.make(ts)); + + // A Block scope parse, and inspect the scope afterward for fields. + _scope.push(new Kind.Define(TypeMemPtr.make(ts))); require("{"); + int lexlen = _scope._kinds.last()._lexSize, nvar=lexlen; + while (!peek('}') && !_lexer.isEOF()) { + parseStatement().isKill(); + while( nvar < _scope._vars._len ) { + Var v = _scope.var(nvar++); + if( !v.isFRef() ) { + ts = ts.add( Field.make( v._name, v.type(), _code.getALIAS(), v._final, v._final && v._init && _scope.in( v._idx )._type != Type.TOP ) ); + TYPES.put( typeName, TypeMemPtr.make( ts ) ); + } + } + } - // create type class:NESTED.typeName; no fields. + // Count struct declarations + int varlen = _scope._vars._len; + ts = ts.close(); + TYPES.put(typeName,TypeMemPtr.make(ts)); - String old = _nestedType; - String fullName = _scope.depth()==1 && _nestedType.endsWith(typeName) ? old : (old+"."+typeName).intern(); - _nestedType = fullName; // Recursive structs start with this as their basename + // Generate an initializing struct + StructNode s = new StructNode(ts); + for( int i=lexlen; i<varlen; i++ ) + if( !_scope.var(i).isFRef() ) // Promote to outer scope, not defined here + s.addDef(_scope.in(i)); // Keep the program code to generate instance - ReturnNode ret = parseStruct( false, fullName ); + // Save instance default constructor + s.setType(s.compute()); // Do not call peephole, in case the entire struct is a big plain constant + INITS.put(typeName,s.keep()); - _nestedType = old; + // Having finalized the struct, upgrade all the 'self' pointers + for( Node n : s._inputs ) + if( n._type instanceof TypeFunPtr tfp && _code.link(tfp) instanceof FunNode fun ) + _code.addAll( fun._outputs ); - // Insert a field into class:NESTED.typeName of "val typeName = fcn-ptr-to-init" - // Function does malloc internally. - // Future self: means subtyping has to deal with "who does the malloc" - String clzName = addClzPrefix(fullName); - TypeFunPtr sig = ret.fun().sig(); - TypeStruct ts = (TypeStruct)TYPES.get(clzName); - String initName = initFieldName(fullName); - Field oldFld = ts==null ? null : ts.field(initName); - Field fld = oldFld==null - ? Field.make(initName,sig,_code.alias(_ref._cname),true) - : oldFld.makeFrom(sig); - Field ctorFld = ts==null ? null : ts.field("<ctor>"); - // A same-named struct shares its file's class object and flag. - Field flag = ts==null ? null : ts.field("$init"); - ts = ctorFld==null - ? TypeStruct.make(clzName,false,fld) - : TypeStruct.make(clzName,false,fld,ctorFld); - if( flag!=null ) ts=ts.add(flag); - TypeFunPtr alloc = makeAllocator((TypeStruct)TYPES.get(fullName),ret,ctorFld); - ts = ts.add(Field.make("<new>",alloc,_code.alias(_ref._cname),true)); - TYPES.put(clzName,ts); - - // Insert a field in the containing class with the nested class type. - // This is basically sugar for "val CLZNAME = <init>" - // and is a nicer version of updateSelfAsFieldsDiscovered. - _scope.define(typeName, sig, true, new FunPtrNode(sig,_code._start,ret).peephole(), loc()); + // Done with struct/block scope + _nestedType = old; require("}"); - return require(_code.ZERO,";"); - } - - // Build the declaration-side allocation helper. Allocation sites only - // call this function; all layout, initialization and publication choices - // are made here, where the complete struct and constructor are known. - private TypeFunPtr makeAllocator(TypeStruct ts, ReturnNode initRet, Field ctorFld) { - TypeFunPtr ctor = ctorFld==null ? null : (TypeFunPtr)ctorFld._t; - int nargs = ctor==null ? 0 : ctor.nargs()-2; // Hide self and selfMem - Type[] atypes = new Type[nargs]; - for( int i=0; i<nargs; i++ ) atypes[i] = ctor.arg(i+2); - TypeMemPtr ptr = TypeMemPtr.make(ts); - TypeFunPtr sig = TypeFunPtr.make1((byte)2,true,atypes,ptr,_code.fidx(_ref._cname)); - String name = (ts._name+".<new>").intern(); - - int oldUID = _code.UID(); - FunNode fun = (FunNode)new FunNode(loc(),sig,name,_ref,null,_ref._start).peephole(); - _code.link(fun); - Node rpc = new ParmNode("$rpc",0,TypeRPC.BOT,fun,ConstantNode.seed(TypeRPC.BOT).peephole()).peephole(); - Node pub = new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun, - new ProjNode(_ref._start,1,ScopeNode.MEM0).peephole()).peephole(); - Node[] parms = new Node[nargs]; - for( int i=0; i<nargs; i++ ) - parms[i] = new ParmNode("arg"+i,i+2,atypes[i],fun,ConstantNode.seed(atypes[i]).peephole()).peephole(); - - NewNode nnn = new NewNode(ts,fun,off(ts," len")).init(); - Node self = new ProjNode(nnn,0,ts.str()).init(); - Node priv = new ProjNode(nnn,1,"#selfMem").init(); - Node[] initArgs = new Node[]{fun,pub,self,priv, - new FunPtrNode(initRet.fun().sig(),_code._start,initRet).peephole()}; - CallEndNode cend = rawCall(initArgs,initRet.fun()); - Node ctl = new CProjNode(cend,0,ScopeNode.CTRL).peephole(); - pub = new ProjNode(cend,1,ScopeNode.MEM0).peephole(); - priv = new ProjNode(cend,2,"#selfMem").peephole(); - - if( ctor != null ) { - Node[] cargs = new Node[nargs+5]; - cargs[0]=ctl; cargs[1]=pub; cargs[2]=self; cargs[3]=priv; - for( int i=0; i<nargs; i++ ) cargs[i+4]=parms[i]; - FunNode cfun = _code.link(ctor); - cargs[cargs.length-1] = new FunPtrNode(ctor,_code._start,cfun.ret()).peephole(); - cend = rawCall(cargs,cfun); - ctl = new CProjNode(cend,0,ScopeNode.CTRL).peephole(); - pub = new ProjNode(cend,1,ScopeNode.MEM0).peephole(); - priv = new ProjNode(cend,2,"#selfMem").peephole(); - } - - priv.keep(); - for( Field field : ts._fields ) { - Type storage = field._t.makeStorage(); - 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); - merge.alias(field._alias,esc); - Node next = merge.peephole(); - pub.unkeep(); - pub = next; - } - priv.unkill(); - ReturnNode ret = (ReturnNode)new ReturnNode(ctl,pub,self,rpc,fun).peephole(); - fun.setRet(ret); - fun._approxUIDs = _code.UID() - oldUID; - _ref.addFun(_code,fun); - return sig; - } - - private CallEndNode rawCall(Node[] args, FunNode target) { - // Establish this known declaration-side edge immediately, but do not - // peephole or inline it while the surrounding factory is being built. - // IterPeeps therefore starts with the complete initializer caller set. - CallNode call = new CallNode(loc(),args).init(); - CallEndNode cend = new CallEndNode(call,TypeRPC.constant(_code.rpc(_ref._cname))).init(); - call.link(target); - return cend; - } - - // Parse a struct declaration (not an allocation); file-level is a class-init, and scope structs are normal - private ReturnNode parseStruct( boolean isClz, String typeName ) { - var oldExits = _ctorExits; - _ctorExits = new ArrayList<>(); - // Record & restore global state set during parsing the <init> code - Node oldCtrl= _scope.ctrl().keep(); - Node oldMem = _scope.mem ().keep(); - - // Make the future clazz/instance struct. - TypeStruct tself = (TypeStruct)TYPES.get(typeName); - if( tself==null ) - TYPES.put(typeName, tself = TypeStruct.make(typeName,true)); - else { - assert tself._open; - TYPES.put(typeName,tself = tself.defined()); - } - - // Class object keeps a runtime flag to signal the <clinit> has run. - if( isClz ) - TYPES.put(typeName,tself=tself.add(Field.make("$init",TypeInteger.BOOL,_code.alias(_ref._cname),false))); - - // Signature of the default constructor - TypeFunPtr sig = structInitSig(isClz,tself); - // Function and argument names of default constructor - String fname = (typeName + (isClz ? ".<clinit>" : ".<init>")).intern(); - String[] ids = new String[]{"self", isClz ? "arg" : "#selfMem"}; - - // Struct decls look like function bodies. Parse function body normally. - ReturnNode ret = _parseFunctionBody(fname,sig,loc(),ids); - _ctorExits = oldExits; - // Unwind global state. - ctrl(oldCtrl.unkeep()); - mem (oldMem .unkeep()); - return ret; - } - - // <cl/init> signature: { self arg/selfMem -> BOT/selfMem } - // Self-memory is the rare *private* (never aliased) memory for the new object. - private TypeFunPtr structInitSig( boolean isClz, TypeStruct tself ) { - // Private uninitialized memory from new object, no escaped values. - TypeMem privMem = isClz ? null : TypeMem.make(1,tself,true,isClz,false,null,null); - Type targ = isClz ? TypeInteger.BOT : privMem; - // Return for private memory has to name all escaped fields, which are not known yet. - // Escape ALL fields, and sharpen later. - Type tret = isClz ? Type.BOTTOM : TypeMem.makePrivate(tself); - Type[] targs = new Type[]{ TypeMemPtr.make((byte)2,tself,isClz), targ }; - // FIDX for FREFs is in the global namespace, needs the correct clz name. - String clzBare = tself._name; - int order = 3; // 3 is always the <init> order, 2nd in the file just behind the <clinit> - if( isClz ) { - assert startsClzPrefix( clzBare ); - clzBare = clzBare.substring(clzPrefix.length()).intern(); - order = 2; // 2 is always the <clinit> order, first in file and skipping reserved #1 - } - return TypeFunPtr.make1((byte)2, true, targs, tret, _code.fidx(clzBare,order)); - } - - // A class type carries the constructor function as a required final field. - private TypeStruct classStruct( String typeName, TypeFunPtr init, boolean open ) { - return TypeStruct.make(addClzPrefix(typeName),open,Field.make(initFieldName(typeName),init,_code.alias(_ref._cname),true)); - } - - private static String initFieldName( String typeName ) { - if( startsClzPrefix(typeName) ) - typeName = typeName.substring(clzPrefix.length()); - int idx = typeName.lastIndexOf('.'); - return (idx == -1 ? typeName : typeName.substring(idx+1)).intern(); - } - - private TypeStruct latestStruct( TypeStruct ts ) { - Type t = TYPES.get(ts._name); - return t instanceof TypeStruct ts2 ? ts2 : ts; - } - - private Field fieldOrOpen( TypeStruct ts, String name, boolean xfinal ) { - ts = latestStruct(ts); - Field fld = ts.field(name); - if( fld != null ) return fld; - if( !ts._open || !ts._fref ) return null; - fld = Field.make(name,Type.BOTTOM,_code.alias(_ref._cname),xfinal); - TYPES.put(ts._name,ts = ts.add(fld)); - if( _ctorOpenStruct != null && _ctorOpenStruct._name == ts._name ) - _ctorOpenStruct = ts; - return fld; - } - - private Var defineOpenConstructorField( String name ) { - if( _ctorOpenStruct==null ) return null; - Field fld = fieldOrOpen(_ctorOpenStruct,name,true); - if( fld == null ) return null; - _scope.define(name,fld._t,fld._final,con(Type.TOP),loc()); - return _scope.lookup(name); + require(";"); + _scope.pop(); + return ZERO; } @@ -1442,135 +889,38 @@ public class Parser { // ref type position. // t = int|i8|i16|i32|i64|u8|u16|u32|u64|byte|bool | flt|f32|f64 | val | var | struct[?] - - // Structs have nested names, and can be discovered with a partial name - // match. Example, searching for a type name "C.D" in the namespace "A.B". - // This will match "A.B.C.D", then "A.C.D", then "C.D". - - // Will also match external names like "sys.io" or "externModule.subtype.subtype" - private Type type() { + int old1 = pos(); + boolean writable = match("!"); + boolean readonly = !writable && match("~"); // Only type with a leading `{` is a function pointer... if( peek('{') ) return typeFunPtr(); // Otherwise you get a type name - int old1 = pos(); - boolean writable = match("!"); - boolean readonly = !writable && match("~"); String tname = _lexer.matchId(); if( tname==null ) return posT(old1); // Convert the type name to a type. - Type t0 = TYPES.get(tname), t1 = t0; + Type t0 = TYPES.get(tname); // No new types as keywords - if( KEYWORDS.contains(tname) ) { - if( t0 == null ) return posT(old1); - // Something like "int" or "f64" or "var". - // Can be "int[]" so still need to check array-ness - } else { - if( t0 == Type.BOTTOM || t0 == Type.TOP ) return t0; // var/val type inference - - if( t0 != null ) { - while( true ) { - int old2 = pos(); - if( !match(".") ) break; - String sname = matchId(); - if( sname==null ) { pos(old2); break; } - sname = tname+"."+sname; - Type t = TYPES.get(sname); - if( t==null ) { pos(old2); break; } - tname = sname; - t0 = t; - } - } else { - // Type A.B.C starts with just A. Search our nested type path, - // e.g. path "X.Y.Z", in order: - - // If A==X its now a local type. - // If another dot, repeat for B==Y then C==Z - // Else if AX.B exists, done - // Else AX.B is a local forward ref. - // - // Else its a short cut form, one of: - // X.Y.Z.A - // X.Y.A - // X.A - // Else this all failed, assume a forward ref with A as an external symbol: - // A - - String root = _nestedType; - int ridx = root.indexOf('.'); - if( ridx != -1 ) root = root.substring(0,ridx); - if( tname.equals(root) ) { - boolean localForward = false; - while( true ) { - int old2 = pos(); - if( !match(".") ) break; - String sname = matchId(); - if( sname==null ) { pos(old2); break; } - sname = tname+"."+sname; - Type t = TYPES.get(sname); - if( t==null && !(_nestedType.equals(tname) || _nestedType.startsWith(tname+".")) && !localForward ) { - pos(old2); - break; - } - tname = sname; - t0 = t; - localForward |= t==null; - } - } else { - String nest = _nestedType, fullq = tname; - while( true ) { - fullq = nest+"."+tname; // Full qualified name - t0 = TYPES.get(fullq); - if( t0 != null ) - { tname = fullq; break; } // Search succeeded - // Subtract a layer from nested typenames - int idx = nest.lastIndexOf('.'); - if( idx== -1 ) break; // Search failed; leave as an external symbol - nest = nest.substring(0,idx); - } - if( t0==null ) { - CompUnit cu = ParseAll.findCompUnit(_code,_ref,tname); - if( cu != null ) { - tname = cu._cname; - t0 = TYPES.get(tname); - } - while( true ) { - int old2 = pos(); - if( !match(".") ) break; - String sname = matchId(); - if( sname==null ) { pos(old2); break; } - tname = tname+"."+sname; - t0 = TYPES.get(tname); - } - } else { - while( true ) { - int old2 = pos(); - if( !match(".") ) break; - String sname = matchId(); - if( sname==null ) { pos(old2); break; } - sname = tname+"."+sname; - Type t = TYPES.get(sname); - if( t==null ) { pos(old2); break; } - tname = sname; - t0 = t; - } - } - } - } - - tname = tname.intern(); - // Still no type found? Assume forward reference - t1 = t0 == null - ? TypeStruct.forward(tname) // Null: assume a forward ref type - : t0; - } - - // Structs are always actually references - if( t1 instanceof TypeStruct ts1 ) - t1 = TypeMemPtr.make(ts1); + if( t0 == null && KEYWORDS.contains(tname) ) + return posT(old1); + if( t0 == Type.BOTTOM || t0 == Type.TOP ) return t0; // var/val type inference + // Check for subtype. + while( true ) { + int old2 = pos(); + if( !match(".") ) break; + String sname = _lexer.matchId(); + if( sname==null ) { pos(old2); break; } + String tsname = tname+"."+sname; + t0 = TYPES.get(tsname); + if( t0==null ) { pos(old2); t0 = TYPES.get(tname); break; } + tname = tsname; + } + + // Still no type found? Assume forward reference + Type t1 = t0 == null ? TypeMemPtr.make(TypeStruct.open(tname)) :t0; // Null: assume a forward ref type // A qualifier before the name applies to the struct reference, not // to an enclosing array. Arrays have their own [] / [~] qualifier. if( t1 instanceof TypeMemPtr tmp ) { @@ -1582,15 +932,14 @@ public class Parser { Type t2 = t1; while( true ) { if( match("?") ) { - if( t2 instanceof TypeMemPtr tmp ) { - if( tmp.nullable() ) throw error("Type "+t2+" already allows null"); - t2 = tmp.makeNullable(); - } else + if( !(t2 instanceof TypeMemPtr tmp) ) throw error("Type "+t0+" cannot be null"); + if( tmp.nullable() ) throw error("Type "+t2+" already allows null"); + t2 = tmp.makeNullable(); } else if( match("[~]") ) { - t2 = TypeMemPtr.make(typeAry(t2,true)).makeRO(); + t2 = typeAry(t2,true).makeRO(); } else if( match("[]") ) { - t2 = TypeMemPtr.make(typeAry(t2,false)); + t2 = typeAry(t2,false); } else break; } @@ -1602,64 +951,27 @@ public class Parser { int old2 = pos(); if( !match("!") ) match("~"); String id = _lexer.matchId(); - if( !(peek(',') || peek(';') || peek('=') || match("->")) || id==null ) + if( !(peek(',') || peek(';') || match("->")) || id==null ) return posT(old1); // Reset lexer to reparse pos(old2); // Reset lexer to reparse // Yes a forward ref, so declare it - forwardRefType(tname,(TypeStruct)((TypeMemPtr)t1)._obj); + TYPES.put(tname,((TypeMemPtr)t1).withAccess(false)); // Return the (array, final) type return t2; } - private TypeMemPtr forwardRefType( String tname, TypeStruct tself ) { - TYPES.put(tname,tself); - String clzName = addClzPrefix( tname ); - TYPES.put(clzName, classStruct(tname,structInitSig(false,tself),true)); - return TypeMemPtr.make(tself); - } - - private TypeMemPtr forwardRefType( String tname ) { - tname = fullTypeName(tname); - return forwardRefType(tname,TypeStruct.forward(tname)); - } - - private String fullTypeName( String tname ) { - String nest = _nestedType, fullq; - while( true ) { - fullq = nest+"."+tname; - if( TYPES.get(fullq) != null ) - return fullq.intern(); - int idx = nest.lastIndexOf('.'); - if( idx== -1 ) return fullq.intern(); - nest = nest.substring(0,idx); - } - } - - // Make an array type of t. Always record a mutable version, - // but return the requested version. - private TypeStruct typeAry( Type t, boolean efinal ) { - // Non-null reference arrays are valid types, even though an ordinary - // zero-filled `new T[len]` cannot construct one. Allocation retains - // that check in alloc(); trusted runtimes (notably argv construction) - // may fill the private array completely before publishing it. - String tname = ("[]"+t.str()).intern(); - TypeStruct ta = (TypeStruct)TYPES.get(tname); - if( ta==null ) { - // Primitive arrays use the null compunit - String rcname = TYPES.get(t) instanceof TypeStruct ? _ref._cname : ("_."+t).intern(); - ta = TypeStruct.makeAry(tname,TypeInteger.U32,_code.alias(rcname),t,_code.alias(rcname), false ); - TYPES.put(tname,ta); - } - // Already have the aliases, just efinal is wrong - return TypeStruct.makeAry(tname,TypeInteger.U32,ta._fields[0]._alias,t,ta._fields[1]._alias,efinal ); - } - - private Type implicitSelfType() { - TypeStruct self = _scope.constructorSelf(); - // Ordinary instance methods accept the declared object shape. The - // constructor-only receiver starts at the low/default field values, - // and is built explicitly by constructorFunc(). - return self == null ? TypePtr.PTR : TypeMemPtr.make(self); + // Make an array type of t + private TypeMemPtr typeAry( Type t, boolean efinal ) { + if( t instanceof TypeMemPtr tmp && tmp.notNull() ) + throw error("Arrays of reference types must always be nullable"); + String tname = "[]"+t.str(); + TypeMemPtr ta = (TypeMemPtr)TYPES.get(tname); + if( ta==null ) + // Remember final version + TYPES.put(tname, ta = TypeMemPtr.make( TypeStruct.makeAry(tname,TypeInteger.U32,_code.getALIAS(),t,_code.getALIAS(),true) )); + // Reuse aliases, but preserve this occurrence's element permissions. + TypeStruct ary = ta._obj; + return TypeMemPtr.make(TypeStruct.makeAry(tname,TypeInteger.U32,ary._fields[0]._alias,t,ary._fields[1]._alias,efinal)); } // A function type is `{ type... -> type }` or `{ type }`. @@ -1668,19 +980,16 @@ public class Parser { match("{"); // Skip already-peeked '{' Type t0 = type(); // Either return or first arg if( t0==null ) return posT(old); // Not a function - Ary<Type> ts = new Ary<>(Type.class); - // Always reserve a receiver slot. Outside constructor scopes it is - // broad and unused, so call sites can uniformly pass their self. - ts.push(implicitSelfType()); if( match("}") ) // No-arg function { -> type } - return TypeFunPtr.make(match("?"),ts.asAry(),t0); + return TypeFunPtr.make(match("?"),false,TypeFunPtr.TEMPTY,t0); + Ary<Type> ts = new Ary<>(Type.class); ts.push(t0); // First argument while( true ) { if( match("->") ) { // End of arguments, parse return Type ret = type(); if( ret==null || !match("}") ) return posT(old); // Not a function - return TypeFunPtr.make(match("?"),ts.asAry(),ret); + return TypeFunPtr.make(match("?"),false,ts.asAry(),ret); } Type t1 = type(); if( t1==null ) return posT(old); // Not a function @@ -1789,8 +1098,8 @@ public class Parser { return cmp.unkeep(); } - // rhs is keeped() and becomes lhs - // cmp is NOT keeped() and is the last test + // Keep the middle operand separately from the boolean result. + // The next test compares adjacent operands, not that boolean. Node ifNode = new IfNode(ctrl(), cmp.unkeep()).peephole(); Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole(); @@ -1806,9 +1115,9 @@ public class Parser { throw error("Mixing relational directions in a chained relational test"); // True side parses next arm of test ctrl(ifT.unkeep()); - lhs = rhs; // lhs is keeped() and is old RHS + lhs = rhs; // Reuse the previous RHS exactly once rhs = parseShift().keep(); - cmp = makeCompBool(dir,lhs.unkeep(),rhs); // Convert to a bool + cmp = makeCompBool(dir0,lhs.unkeep(),rhs); // Convert to a bool ifNode = new IfNode(ctrl(), cmp).peephole(); ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep(); ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole(); @@ -1822,7 +1131,7 @@ public class Parser { ctrl(ifT.unkeep()); RegionNode r = fail.mergeScopes(_scope, loc()).init(); _scope = fail; - return new PhiNode("", r,_code.ZERO,con(1)).peephole(); + return new PhiNode("", r,con(0),con(1)).peephole(); } private int parseCompDir() { @@ -1833,12 +1142,11 @@ public class Parser { return 0; } - private Node makeCompBool( int dir0, Node lhs, Node rhs ) { - // Convert to a bool - if( Math.abs(dir0)==2 ) // Swap direction - { Node tmp = lhs; lhs = rhs; rhs = tmp; } - lhs = peep(dir0 < 0 ? new BoolNode.LE(lhs,rhs) : new BoolNode.LT(lhs,rhs)); - return peep(lhs); + private Node makeCompBool( int dir, Node lhs, Node rhs ) { + if( Math.abs(dir)==2 ) + { Node tmp=lhs; lhs=rhs; rhs=tmp; } + Node cmp = dir < 0 ? new BoolNode.LE(lhs,rhs) : new BoolNode.LT(lhs,rhs); + return peep(cmp); } /** @@ -1920,16 +1228,14 @@ public class Parser { // Pre-dec/pre-inc int old = pos(); if( match("--") || match("++") ) { - int delta = _lexer.peek(-1)=='+' ? 1 : -1; // Inc vs Dec + int delta = _lexer.peek(-1)=='+' ? 1 : -1; // Pre vs post String name = _lexer.matchId(); 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()); + Node expr = liftExpr(peep(new AddNode(_scope.in(n),con(delta))),n.type(),true); _scope.update(n,expr); return expr; } @@ -1949,118 +1255,76 @@ public class Parser { * <pre> * primaryExpr : integerLiteral | "string" | 'char' | true | false | null | * new Type | '(' expression ')' | Id['++','--'] | - * type['.' Id]* + * [static.]* Id * </pre> * @return a primary {@link Node}, never {@code null} */ private Node parsePrimary() { if( _lexer.isNumber(_lexer.peek()) ) return parseLiteral(); - if( _lexer.peek('"') ) return parsePostfix(newString(parseString())); + if( _lexer.peek('"') ) return newString(parseString()); if( matchx("true" ) ) return con(1); - if( matchx("false") ) return _code.ZERO; - if( matchx("null" ) ) return con(Type.NIL); + if( matchx("false") ) return ZERO; + if( matchx("null" ) ) return NIL; if( match ("'" ) ) return parseChar(); if( match ("(" ) ) return parsePostfix(require(parseAsgn(), ")")); if( matchx("new" ) ) return parsePostfix(alloc()); if( match ("{" ) ) return parsePostfix(require(func(),"}")); + // Parse static variable lookup: `type.fld` in the type namespace + // CNC: not a full type parse, just a typename parse int pos = pos(); + Type t = type(); + if( t!=null ) { + if( peek('.') ) + // A namespace lookup is not a reference declaration. + return parsePostfix(con(TYPES.get(((TypeMemPtr)t)._obj._name))); + //pos(pos); // TODO: not a `type.fld` + //return null; + throw Utils.TODO(); + } + // Expect an identifier now + + // - if null, make fref, normal var handling + // - if final constant, normal var handling + // - if method var, scan skipping blocks & methods, must hit correct struct no fcns + // - if normal var, scan skipping blocks, allowing last fcn + // - NO fcns nested in methods, NO methods nested in fcns + String id = _lexer.matchId(); if( id == null || KEYWORDS.contains(id) ) throw errorSyntax("an identifier or expression"); - int pos2 = pos(); - // Attempt a local var lookup first Var var = _scope.lookup(id); + if( var==null ) { - // Not a local var, try again as a type name - pos(pos); - Type t = type(); - if( t!=null ) { - // Static <clinit> typename lookup - if( peek('.') ) // type.fld, which is really a field lookup in the class object - return parsePostfix(con(t)); - // Direct reference to a <clinit> type. Convert the instance - // type to a class type name - if( !(t instanceof TypeMemPtr tmp) ) { - //return new FRefNode(addClzPrefix(tmp._obj._name), loc()).peephole(); - // Not a local var, but yes a bare type... disallow a local - // FREF and return null, not-a-primary parse. - pos(pos); - return null; - } - } + // If missing, assume a forward reference + _scope.define(id, FRefNode.FREF_TYPE, true, XCTRL, loc()); + var = _scope.lookup(id); + } - // Insert FREF outside enclosing Kind.Func scope. - pos(pos2); - t = FRefNode.FREF_TYPE; - // Check if this is a class. - CompUnit cu = ParseAll.findCompUnit(_code,_ref,id); - if( cu!=null ) { - // Must be a final class-type - String clzName = addClzPrefix(cu._cname); - TypeStruct clz = (TypeStruct)TYPES.get(clzName); - if( clz == null ) - TYPES.put(clzName,clz = TypeStruct.make(clzName,true)); - t = TypeMemPtr.make((byte)2,clz,true); - } - // Define the FRef - var = _scope.defineFRef(id,t,true,loc()); - _frefs.push((FRefNode)_scope.in(var)); + // If a method var, allow nested blocks and methods, but not fcns or other structs. + // Else safety check for accessing fields out of function scope. + if( instance(var) ) { + Var selfVar = _scope.lookup("self"); + Node self = liftExpr(_scope.in(selfVar),selfVar.type(),true); + return parsePostfixName(self,id); } // 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 - // widened to include null. Inside the explicit constructor, any read - // before all paths replace that value observes a partially initialized - // object. Ordinary methods run only after construction is complete; - // their enclosing declaration scope still carries the same Var marker. - if( inExplicitConstructor() && var._uninit && rvalue._type instanceof TypeNil tn && tn.nullable() ) - throw error("Field '"+id+"' might not be initialized",var._loc); - - // Check for an instance field load - int kx = _scope.kindx(var); // Declaration scope - int fx = _scope.enclosingFunction(); // Enclosing function scope - Kind kk = _scope._kinds.at(kx); - 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,0); - - // Check for a function-escaping variable; these require true - // closures. Final constants are OK; final vars require a hidden var - // argument; not-final fields can just use an explicit struct arg. - if( kx < fx && // Out of scope - !(var._final && rvalue._type.isConstant()) ) { // Not a final constant - if( kx > 0 ) // Global scope is OK, only ever one of these (not one per function invoke) - throw error("Variable '"+var._name+"' is out of function scope and must be a final constant"); - - // var.idx==2 is self argument, 3 is args, 4 or more are now static globals - if( var._idx >= 4 ) { - // Read from global scope. - // Surely a nicer way to get the top-level scope compile name? - Var clzptr = _scope.var( 2 ); - // 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,0 ); - //return parsePostfixName(_scope.in(2),id); - } - } + Node oldValue = _scope.in(var); + if( oldValue instanceof ConstantNode && !(oldValue instanceof FRefNode) && oldValue._type==Type.BOTTOM ) + throw error("Cannot read uninitialized field '"+id+"'"); + Node rvalue = liftExpr(oldValue,var.type(),true); + if( rvalue!=oldValue ) _scope.update(var,rvalue); + if( rvalue instanceof ConstantNode && rvalue._type == Type.BOTTOM ) + if( rvalue instanceof FRefNode ) + return parsePostfix(rvalue); + else throw error("Cannot read uninitialized field '"+id+"'"); // Check for assign-update, x += e0; char ch = _lexer.matchOperAssign(); - if( ch==0 ) { // Normal primary, check for postfix updates - int selfx = _scope.kindFcn(var)._lexSize; - // Might be a method call from inside a method, so no explicit "self". - // Pass the in-scope "self" - return parsePostfixMethod(rvalue,_scope.in(_scope.update(_scope.var(selfx),null))); - } + if( ch==0 ) // Normal primary, check for postfix updates + return parsePostfix(rvalue); // Assign-update direct into Scope if( var._final ) throw error("Cannot reassign final '"+var._name+"'"); @@ -2069,6 +1333,33 @@ public class Parser { return postfix(ch) ? rvalue.unkeep() : op; } + // true if instance variable lookup (var is in any number of nested blocks, + // then an instance function then inside a struct declaration). + private boolean instance(Var var ) { + // If a method var, allow nested blocks and methods, but not fcns or other structs + Kind kind = _scope.kind(var); + if( kind instanceof Kind.Define ) { + for( int i = _scope.depth()-1; i>=0 && var._idx < _scope._kinds.at(i)._lexSize; i-- ) { + Kind xkind = _scope._kinds.at(i); + if( xkind instanceof Kind.Block ) continue; + if( xkind instanceof Kind.Func ) { + if( _scope._kinds.at(i-1)==kind ) + return true; // Instance variable: lookup in a method in a decl + } + throw Utils.TODO(); + } + } else { + // If a normal var, allow any nested blocks, and a last fcn - + // but not functions further out (which involves capture). + Node def = _scope.in(var._idx); + for( int i = _scope.depth()-1; i>=0 && var._idx < _scope._kinds.at(i)._lexSize; i-- ) + if( _scope._kinds.at(i) instanceof Kind.Func && + !(def instanceof FRefNode) && !(var._final && def._type.isConstant()) ) + throw error("Variable '"+var._name+"' is out of function scope and must be a final constant"); + } + return false; + } + // Check for assign-update, "x += e0". // Returns "x + e0"; the caller assigns value. // if ch is postfix, then lhs is kept and caller will unkeep and return it. @@ -2091,17 +1382,17 @@ public class Parser { case '-' -> new SubNode(lhs,rhs); case '*' -> new MulNode(lhs,rhs); case '/' -> new DivNode(lhs,rhs); - case '|' -> new OrNode(null,lhs,rhs); - case '&' -> new AndNode(null,lhs,rhs); + case '&' -> new AndNode(loc(),lhs,rhs); + case '|' -> new OrNode(loc(),lhs,rhs); case '^' -> new XorNode(loc(),lhs,rhs); case Lexer.SHL -> new ShlNode(loc(),lhs,rhs); case Lexer.SAR -> new SarNode(loc(),lhs,rhs); case Lexer.SHR -> new ShrNode(loc(),lhs,rhs); - default -> throw TODO(); + default -> throw Utils.TODO(); }; // Convert to float ops, or narrow int types; error if not declared type. // Also, if postfix LHS is still keep() - return convertExpr(peep(op),t); + return liftExpr(peep(op),t,true); } @@ -2110,119 +1401,145 @@ public class Parser { */ private Node alloc() { Type t = type(); - if( t==null ) { - String tname = _lexer.matchId(); - if( tname==null || KEYWORDS.contains(tname) ) - throw error("Expected a type"); - t = forwardRefType(tname); - } + if( t==null ) throw error("Expected a type"); // Parse ary[ length_expr ] if( match("[") ) { if( !t.makeZero().isa(t) ) - throw error("Cannot allocate a non-nullable, since arrays are always zero/null filled"); - Node len = parseAsgn().keep(); + throw error("Cannot allocate a non-nullable, since arrays always zero/null fill"); + Node len = parseAsgn(); + if( !(len._type instanceof TypeInteger) ) + throw error("Cannot allocate an array with length "+len._type); require("]"); - return allocArray(typeAry(t,false),len); - } else { - if( !(t instanceof TypeMemPtr tmp) ) - throw error("Cannot allocate a "+t.str()); - return allocStruct(tmp._obj); - } - } - - private Node allocStruct(TypeStruct ts) { - Ary<Node> ctorArgs = constructorArgs(); - // A field declaration can leave a short forward type name in TYPES. - // Allocation happens in lexical context, so resolve that short name - // to the completed nested type before selecting its class helper. - String typeName = ts._name.indexOf('.') == -1 ? fullTypeName(ts._name) : ts._name; - Type resolved = TYPES.get(typeName); - if( resolved instanceof TypeStruct rts ) ts = rts; - TypeStruct clz = (TypeStruct)TYPES.get(addClzPrefix(typeName)); - if( clz==null ) - throw error("Unknown struct type '" + ts._name + "'"); - Field afld = clz.field("<new>"); - Node alloc; - if( afld != null ) { - TypeFunPtr tfp = (TypeFunPtr)afld._t; - FunNode fun = _code.link(tfp); - alloc = fun==null - ? new FRefNode((addClzPrefix(typeName)+".<new>").intern(),loc()) - : new FunPtrNode(tfp,_code._start,fun.ret()).peephole(); - } else { - if( !ts._fref ) - throw error("Constructor arguments for '" + ts._name + "' but no constructor is defined"); - Type[] sig = new Type[ctorArgs._len]; - for( int i=0; i<ctorArgs._len; i++ ) - sig[i] = ctorArgs.at(i)._type; - alloc = new FRefNode((addClzPrefix(typeName)+".<new>").intern(),loc(), - TypeFunPtr.make(false,sig,TypeMemPtr.make(ts))); - } - Ary<Node> args = new Ary<>(Node.class); - args.add(ctrl()); args.add(mem()); - for( Node arg : ctorArgs ) args.add(arg.unkeep()); - args.add(alloc); - return functionCall(args); - } - - private Ary<Node> constructorArgs() { - Ary<Node> args = new Ary<>(Node.class); - if( !match("(") ) - return args; - while( !peek(')') ) { - Node arg = parseAsgn(); - if( arg==null ) break; - args.push(arg.keep()); - if( !match(",") ) break; - } - require(")"); - return args; + TypeMemPtr tmp = typeAry(t,false); + return newArray(tmp._obj,len); + } + + if( !(t instanceof TypeMemPtr tmp) ) + throw error("Cannot allocate a "+t.str()); + + // Parse new struct { default_initialization } + StructNode s = INITS.get(tmp._obj._name); + if( s==null ) throw error("Unknown struct type '" + tmp._obj._name + "'"); + + Field[] fs = s._ts._fields; + // if the object is fully initialized, we can skip a block here. + // Check for constructor block: + boolean hasConstructor = match("{"); + Ary<Node> init=s._inputs; int idx=0; + if( hasConstructor ) { + idx = _scope.nIns(); + // Push a scope, and pre-assign all struct fields. + _scope.push(new Kind.Block()); + Lexer loc = loc(); + for( int i=0; i<fs.length; i++ ) + // An initial TOP means the field needs to be initialized. We + // store a BOT initially; any partial init will fall to BOT + // (merge BOT and the partial) and be obviously only a partial + // init. To be initialized the field needs a full clobber. + _scope.define(fs[i]._fname, fs[i]._t, fs[i]._final, s.in(i)._type==Type.TOP ? con(Type.BOTTOM) : s.in(i), loc); + // Parse the constructor body + require(parseBlock(new Kind.Alloc(tmp)),"}"); + init = _scope._inputs; + } + // Check that all fields are initialized + for( int i=idx; i<init.size(); i++ ) + if( missingInit(init.at(i),new java.util.BitSet()) ) + throw error("'"+tmp._obj._name+"' is not fully initialized, field '" + fs[i-idx]._fname + "' needs to be set in a constructor"); + Node ptr = newStruct(tmp._obj, off(tmp._obj, " len"), idx, init ); + if( hasConstructor ) + _scope.pop(); + return ptr; + } + + // The old constructor model requires each field on every path. An + // unresolved expression also has BOTTOM now, so inspect the actual + // sentinel and merges instead of mistaking an unknown type for no write. + private boolean missingInit(Node n, java.util.BitSet visit) { + if( n==null || visit.get(n._nid) || n instanceof FRefNode ) return false; + visit.set(n._nid); + if( n instanceof ConstantNode ) return n._type==Type.BOTTOM || n._type==Type.TOP; + if( n instanceof ConvertNode || n instanceof ReadOnlyNode ) return missingInit(n.in(1),visit); + if( n instanceof PhiNode phi ) + for( int i=1; i<n.nIns(); i++ ) + if( phi.region().in(i)!=null && phi.region().in(i)._type!=Type.XCONTROL && missingInit(n.in(i),visit) ) return true; + return false; } - private Node allocArray(TypeStruct ts, Node len) { - Field lenFld = ts.field("#" ); - Field bodyFld = ts.field("[]"); - - ConFldOffNode base = off(ts, "[]"); - int scale = ts.aryScale(); - Node size = peep(new AddNode(base,peep(new ShlNode(null,len,con(scale))))); - - // Build a NewNode; takes in and puts out all aliases. - NewNode nnn = new NewNode(ts, ctrl(), size ).init(); - ProjNode self = new ProjNode(nnn,0,ts.str()).init().keep(); - ProjNode smem = new ProjNode(nnn,1,"#selfMem").init().keep(); - - // Store length. Rest of array is zero'd via CALLOC during CodeGen. - // Length is casted to sanity. - // TODO: Needs runtime check. - Node st = peep(new StoreNode(null, "#", 1, TypeInteger.U32, null, smem, self, off(ts,"#"), len.unkeep(), true)); - // TODO: Allow add-on "constructor" to init the array elements - - Node prior = mem(); - Node esc = peep(new EscapeNode(lenFld,self,st,prior)); - mem(mergeAlias(prior,lenFld._alias,esc)); - prior = mem(); - esc = peep(new EscapeNode(bodyFld,self,smem,prior)); - mem(mergeAlias(prior,bodyFld._alias,esc)); - smem.unkeep(); - return self.unkeep(); + /** + * Return a NewNode initialized memory. + * @param obj is the declared type, with GLB fields + * @param init is a collection of initialized fields + */ + private Node newStruct( TypeStruct obj, Node size, int idx, Ary<Node> init ) { + Field[] fs = obj._fields; + if( fs==null ) + throw error("Unknown struct type '" + obj._name + "'"); + Node prior = mem().keep(); + MemMergeNode input = new MemMergeNode((Node)null); + for( Field f : fs ) if( !f._one ) input.alias(f._alias,prior); + // Allocation zeroes its fields; nonzero initializers become explicit Stores. + Node[] ns = new Node[3]; + ns[0] = ctrl(); + ns[1] = input.peephole(); + ns[2] = size; + Node nnn = new NewNode(TypeMemPtr.make(obj),ns).peephole().keep(); + Node ptr = new ProjNode(nnn,0,obj._name).peephole().keep(); + Node out = new ProjNode(nnn,1,"$mem").peephole().keep(); + MemMergeNode after = new MemMergeNode(prior); + for( Field f : fs ) if( !f._one ) after.alias(f._alias,out); + mem(after.peephole()); + out.unkeep(); + nnn.unkeep(); + prior.unkill(); + for( int i=0; i<fs.length; i++ ) { + Node val = init.get(idx+i); + if( !fs[i]._one && val._type != val._type.makeZero() ) { + Field f = fs[i]; + Node st = new StoreNode(loc(),f._fname,f._alias,f._t,mem(),ptr,off(obj,f._fname),val,true).peephole(); + mem(new MemMergeNode(mem(),f._alias,st).peephole()); + } + } + return ptr.unkeep(); } + private static final Ary<Node> ALTMP = new Ary<>(Node.class); + private Node newArray(TypeStruct ary, Node len) { + ConFldOffNode base = off(ary, "[]"); + int scale= ary.aryScale(); + Node size = peep(new AddNode(base,peep(new ShlNode(null,len.keep(),con(scale))))); + ALTMP.clear(); ALTMP.add(len.unkeep()); ALTMP.add(con(ary._fields[1]._t.makeZero())); + return newStruct(ary,size,0,ALTMP); + } private Node newString(String s) { - TypeStruct ts = typeAry(TypeInteger.U8,true); - int lenAlias = ts.field("#" )._alias; - int elemAlias = ts.field("[]")._alias; - TypeConAryB body = TypeConAryB.make(s); - TypeInteger slen = TypeInteger.constant(s.length()); + TypeMemPtr tmp = typeAry(TypeInteger.U8,true); + int lenAlias = tmp._obj.field("#" )._alias; + int elemAlias = tmp._obj.field("[]")._alias; + TypeConAryB con = TypeConAryB.make(s); + Type elem = con.elem(); // Make a TMP, not-null (byte)2, singleton (true) (requires text // strings are hash-interned), with a TypeStruct having a constant // array body. - TypeMemPtr str = TypeMemPtr.make((byte)2,TypeStruct.makeAry("[]u8", slen, lenAlias, body, elemAlias, true),true); + TypeMemPtr str = TypeMemPtr.make((byte)2,TypeStruct.makeAry("[]u8", TypeInteger.constant(s.length()), lenAlias, con, elemAlias, true),true); assert str.isConstant(); return con(str.makeRO()); } + // Memory is one hidden SSA variable, including across branches and loops. + private Node mem() { return _scope.mem(); } + private void mem(Node n) { _scope.mem(n); } + + private void store(String name, int alias, Type glb, Node ptr, Node off, Node val, boolean init, Node ctrl) { + if( !init && ptr._type instanceof TypeMemPtr view && view._ro && ctrl()._type!=Type.XCONTROL ) + throw error("Cannot modify final field '"+name+"'"); + Node prior = mem().keep(); + Node st = new StoreNode(loc(),name,alias,glb,prior,ptr,off,val,init); + st.setDef(0,ctrl); + st = st.peephole(); + mem(new MemMergeNode(prior,alias,st).peephole()); + prior.unkill(); + } + /** * Parse postfix expression; this can be a field expression, an array * lookup or a postfix operator like '#' @@ -2235,35 +1552,15 @@ public class Parser { * expr '(' [args,]* ')' // Function call * </pre> */ - private Node parsePostfixMethod(Node expr, Node self) { - // TODO: This is too broad because the parser lacks strong types here. - // Some normal functions can end up with a "self" argument. - - // Self method call? Self is a TMP-not-CLZ, then assume and pass self. - if( match("(") ) { - expr = parsePostfix(require(functionCall(expr,self),")")); - } else if( expr != self ) - self.isKill(); // Not a method call, no need for self - return parsePostfix(expr); - } private Node parsePostfix(Node expr) { - _lexer.skipWhiteSpace(); - Lexer site = loc(); String name; if( match(".") ) name = requireId(); else if( match("#") ) name = "#"; else if( match("[") ) name = "[]"; - // Can get here without an explicit receiver: "f()" or - // "self.method()(next)(next)(next)". Pass the current function self. - else if( match("(") ) return parsePostfix(require(functionCall(expr,defaultSelf()),")")); + else if( match("(") ) return parsePostfix(require(functionCall(expr,null),")")); else return expr; // No postfix - return parsePostfixName(expr,name,0,site); - } - - private Node defaultSelf() { - int selfx = _scope._kinds.at(_scope.enclosingFuncOrDecl())._lexSize; - return _scope.in(_scope.update(_scope.var(selfx),null)); + return parsePostfixName(expr,name); } /** @@ -2276,127 +1573,137 @@ public class Parser { * expr ('[' expr ']')* [ [op]= expr ] // Array reference * </pre> */ + private Node parsePostfixName(Node expr, String name) { + + if( expr._type==Type.NIL ) + throw error("Accessing unknown field '" + name + "' from 'null'"); + + // Sanity check expr for being a reference + if( !(expr._type instanceof TypeMemPtr ptr) ) + return parseUnknownField(expr,name); + + // Find the field from the Type. Lookup in the base object field names. + TypeMemPtr ptr2 = (TypeMemPtr)TYPES.get(ptr._obj._name); + if( !ptr._obj.isAry() ) + ptr = ptr.makeFrom(ptr2._obj); // The declaration fixes the field shape; retain this view's access. + TypeStruct base = ptr._obj; + int fidx = base.find(name); + if( fidx==-1 && base._open ) return parseUnknownField(expr,name); + if( fidx == -1 ) + throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'"); + + // Get field type and layout offset from base type and field index fidx + Field f = base._fields[fidx]; // Field from field index + Type tf = f._t; + if( tf instanceof TypeMemPtr ftmp && ftmp.isFRef() ) + tf = ftmp.makeFrom(((TypeMemPtr)(TYPES.get(ftmp._obj._name)))._obj); + if( base.isAry() && tf instanceof TypeConAry con ) + tf = con.elem(); - // pre is +/-1 for a prefix update, otherwise parse a postfix operation. - private Node parsePostfixName(Node expr, String name, int pre) { - return parsePostfixName(expr,name,pre,loc()); - } - - private Node parsePostfixName(Node expr, String name, int pre, Lexer site) { - // Keep expr across possible updates expr.keep(); - - // Get the best known TypeStruct from a pointer type. - TypeStruct ts = null, ts2; - if( expr._type instanceof TypeMemPtr tmp && (ts2=((TypeStruct)TYPES.get(tmp._obj._name))) != null ) - ts = (TypeStruct)tmp._obj.join(ts2); - // A Load's flow type is intentionally not promised during parsing, - // but its declaring field type is still known. Recover that static - // type for chained accesses such as `_grow(1).buf[len++]`; otherwise - // the array Store is parsed as alias #1 and can hide the independent - // side effect of the postfix `len++`. - if( ts==null && expr instanceof LoadNode load ) { - TypeStruct ots = load.off() instanceof ConFldOffNode coff ? coff._ts : null; - if( ots==null && load.ptr()._type instanceof TypeMemPtr owner ) { - Type owner0 = TYPES.get(owner._obj._name); - ots = owner0 instanceof TypeStruct obj ? obj : owner._obj; - } - if( ots != null ) { - Field loaded = ots.field(load._name); - if( loaded != null && loaded._t instanceof TypeMemPtr ltmp ) { - Type loaded0 = TYPES.get(ltmp._obj._name); - ts = loaded0 instanceof TypeStruct obj ? obj : ltmp._obj; - } - } - } - - Field fld = ts==null ? null : ts.field(name); - if( fld==null && ts!=null && !startsClzPrefix(ts._name) ) { - Type clz0 = TYPES.get(addClzPrefix(ts._name)); - if( clz0 instanceof TypeStruct clz && (fld = latestStruct(clz).field(name)) != null ) { - expr.unkeep(); - ts = latestStruct(clz); - expr = con(TypeMemPtr.make((byte)2,ts,true)).keep(); - } - } - if( fld==null && ts!=null ) - fld = fieldOrOpen(ts,name,false); - if( fld==null && ts!=null && !latestStruct(ts)._open ) - throw error("Accessing unknown field '"+name+"' from '*"+ts._name+"'"); - - // We have to record "active touches" of a new class, to track when we - // need to force class initialization. Basically, we insert an CLInit, - // and if it does not fold away it'll turn into a runtime check. - Kind.Func func = (Kind.Func)_scope._kinds.at(_scope.enclosingFuncOrDecl()); - if( (ts==null || ts==TypeStruct.BOT || ts.isHigh() || startsClzPrefix(ts._name)) && - !(ts!=null && (ts._name+".<clinit>").equals(func._name)) ) - classInit(site,expr); - - // With no field, we will update the bulk memory - int alias = fld==null ? 1 : fld._alias; - Type decl = fld==null ? Type.BOTTOM : fld._t; - // Field offset; fixed for structs, computed for arrays - Node off = (name=="[]" - // Array element math, with unknown base offset and shift amount. - ? peep(new AddNode(fldoff(expr,"[]"),peep(new ShlNode(null,require(parseAsgn(),"]"),fldoff(expr,"<<"))))) - // Named field offset on an unknown struct type - : fldoff(expr,name)).keep(); + Node off = (name.equals("[]") // If field is an array body + // Array index math + ? peep(new AddNode(off(base,"[]"),peep(new ShlNode(null,require(parseAsgn(),"]"),con(base.aryScale()))))) + // Struct field offsets are hardwired + : off(base,name)).keep(); // Disambiguate "obj.fld==x" boolean test from "obj.fld=x" field assignment - if( pre==0 && matchOpx('=','=') ) { - // Field assignment + if( matchOpx('=','=') ) { Node val = parseAsgn().keep(); - // Lift value for store - Node lift = fld==null ? val : new ConvertNode(fld._t,val).peephole(); - // Memory for store, post assignment expression - Node mem = mem(); - // Store to field - mem.keep(); - if( expr._type instanceof TypeMemPtr view && view._ro && ctrl()._type!=Type.XCONTROL ) - throw error("Cannot modify final field '"+name+"'"); - Node st = new StoreNode(loc(), name, alias, decl, ctrl(), mem, expr.unkeep(), off.unkeep(), lift, false).peephole().keep(); - storeMem(st,alias,mem); - st.unkeep(); - mem.unkeep(); + Node lift = liftExpr( val, tf, false ); + store(name,f._alias,tf,expr.unkeep(),off.unkeep(),lift,false,base.isAry() ? ctrl() : null); return val.unkeep(); // "obj.a = expr" returns the expression while updating memory } - // Memory for load - Node mem = mem(); - // Loading from a constant array, the declared type is the - // meet-over-elements and not the array itself. - if( name=="[]" && decl instanceof TypeConAry conary ) - decl = conary.elem(); - // Load field - Node load = peep(new LoadNode(loc(),name, alias, ctrl(), mem, expr, off)); + Node load = f._one && INITS.containsKey( base._name ) + ? INITS.get(base._name).in(base.find(name)) + : new LoadNode(loc(),name, f._alias, mem(), expr, off); + // Arrays include control, as a proxy for a safety range check + // Structs don't need this; they only need a NPE check which is + // done via the type system. + if( base.isAry() && !name.equals("#") ) load.setDef(0,ctrl()); + load = peep(load); // Check for assign-update, "ptr.fld += expr" or "ary[idx]++" - char ch = pre==0 ? _lexer.matchOperAssign() : (char)pre; + char ch = _lexer.matchOperAssign(); 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 ).keep(); - mem.keep(); - if( expr._type instanceof TypeMemPtr view && view._ro && ctrl()._type!=Type.XCONTROL ) - throw error("Cannot modify final field '"+name+"'"); - 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(); - - 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; + Node op = opAssign(ch,load,tf); + store(name,f._alias,tf,expr.unkeep(),off.unkeep(),op,false,base.isAry() ? ctrl() : null); + load = postfix(ch) ? load.unkeep() : op; + // And use the original loaded value as the result + return load; + //throw Utils.TODO(); // NO POSTFIX AFTER STORE, JUST RETURN + } else { + off.unkill(); + // Method call: Loaded a function from a named field, AND the base + // field type is final. + if( load._type instanceof TypeFunPtr && name!="[]" && + _code.externFunc((TypeFunPtr)load._type)==null && + ((TypeMemPtr)TYPES.get(base._name))._obj.field(name)._final && + // And calling a function + match("(") ) { + return parsePostfix(require(functionCall(load,expr),")")); + } + + // Drop expr, keeping load alive + load.keep(); + expr.unkill(); + load.unkeep(); } - off.unkill(); - // Might be a method call, so pass expr as 'self' - return parsePostfixMethod(load,expr.unkeep()); + + return parsePostfix(load); } + // Syntax fixes the access even when a forward definition or a loop + // leaves the receiver untyped. Resolve its alias and layout in the graph. + private Node parseUnknownField(Node expr, String name) { + expr.keep(); + Node off = new FldOffNode(expr,name).peephole(); + if( name.equals("[]") ) { + Node index = require(parseAsgn(),"]"); + Node scale = new FldOffNode(expr,"<<").peephole(); + off = peep(new AddNode(off,peep(new ShlNode(loc(),index,scale)))); + } + off.keep(); + if( matchOpx('=','=') ) { + Node value = parseAsgn().keep(); + Node st = new StoreNode(loc(),name,1,Type.BOTTOM,mem(),expr,off,value,false); + st.setDef(0,ctrl()); + mem(st.peephole()); + expr.unkill(); + off.unkill(); + return value.unkeep(); + } + Node load = new LoadNode(loc(),name,1,mem(),expr,off); + load.setDef(0,ctrl()); + load = peep(load); + char ch = _lexer.matchOperAssign(); + if( ch!=0 ) { + Node op = opAssign(ch,load,Type.BOTTOM).keep(); + Node st = new StoreNode(loc(),name,1,Type.BOTTOM,mem(),expr,off,op,false); + st.setDef(0,ctrl()); + mem(st.peephole()); + // The field width is not known yet. A compound assignment returns + // the stored value after conversion; postfix returns the old load. + // Reading our own Store folds once its alias and width are known. + if( postfix(ch) ) load.unkeep(); + else { + load = new LoadNode(loc(),name,1,mem(),expr,off); + load.setDef(0,ctrl()); + load = peep(load); + } + load.keep(); + op.unkill(); + } else load.keep(); + expr.unkill(); + off.unkill(); + return parsePostfix(load.unkeep()); + } + // zero/sign extend. "i" is limited to either classic unsigned (min==0) or // classic signed (min=minus-power-of-2); max=power-of-2-minus-1. private Node zsMask(Node val, Type t ) { @@ -2430,9 +1737,15 @@ public class Parser { Ary<Type> ts = new Ary<>(Type.class); Ary<String> ids = new Ary<>(String.class); java.util.BitSet finals = new java.util.BitSet(); - Type self = implicitSelfType(); - ts.push(self); - ids.push(self == TypePtr.PTR ? "#self" : "self"); + + // Defined in constructor? Add `self` argument. "static" calls still + // add a `self` they just ignore it. + if( _scope._kinds.last() instanceof Kind.Define define ) { + // Upgrade defined struct to latest fields + TypeMemPtr tmp = (TypeMemPtr)TYPES.get(define._tmp._obj._name); + ts .push(tmp); + ids.push("self"); + } // Parse other arguments _lexer.skipWhiteSpace(); @@ -2448,44 +1761,9 @@ public class Parser { } require("->"); // Make a concrete function type, with a fidx - TypeFunPtr tfp = TypeFunPtr.make1((byte)2,true,ts.asAry(),Type.BOTTOM,_code.fidx(_ref._cname)); + TypeFunPtr tfp = _code.makeFun(TypeFunPtr.make(false,false,ts.asAry(),Type.BOTTOM)); ReturnNode ret = parseFunctionBody(tfp,loc,finals,ids.asAry()); - return new FunPtrNode(tfp,_code._start,ret).peephole(); - } - - private Node constructorFunc(String name) { - require("{"); - Ary<Type> ts = new Ary<>(Type.class); - Ary<String> ids = new Ary<>(String.class); - java.util.BitSet finals = new java.util.BitSet(); - TypeStruct self = _scope.constructorSelf(); - assert self != null; - ts.push(TypeMemPtr.make(constructorRecv(self))); - ids.push("self"); - // Explicit constructors use the same hidden allocation arguments as - // <init>: public memory is the call memory edge (index 1), followed by - // self (index 2) and the private, unescaped object memory (index 3). - ts.push(TypeMem.makePrivate(self)); - ids.push("#selfMem"); - - _lexer.skipWhiteSpace(); - Lexer loc = loc(); - while( true ) { - Type t = type(); - if( t==null ) break; - finals.set(ids.size(),bindingFinal(t)); - String id = requireId(); - ts .push(t ); - ids.push(id); - match(","); - } - require("->"); - TypeFunPtr tfp = TypeFunPtr.make1((byte)2,true,ts.asAry(),TypeMem.makePrivate(self),_code.fidx(_ref._cname)); - Node[] preParse = _scope.save(); - ReturnNode ret = _parseFunctionBody((self._name+"."+name).intern(),tfp,loc,finals,ids.asAry()); - _scope.restore(preParse); - require("}"); - return new FunPtrNode(tfp,_code._start,ret).peephole(); + return new FunPtrNode(ret._fun.sig(),ret).peephole(); } /** @@ -2495,17 +1773,17 @@ public class Parser { * </pre> */ private Node functionCall(Node fcn, Node self) { - Lexer site = loc(); - site._position--; // Opening '('; arguments can contain nested calls. if( fcn._type == Type.NIL ) throw error("Calling a null function pointer"); + if( !(fcn instanceof FRefNode) && fcn._type!=Type.BOTTOM && fcn._type!=TypeScalar.BOT && !fcn._type.isa(TypeFunPtr.BOT) ) + throw error("Expected a function but got "+fcn._type.glb(false).str()); fcn.keep(); // Keep while parsing args Ary<Node> args = new Ary<>( Node.class ); args.push(null); // Space for ctrl,mem args.push(null); if( self != null ) // Method call has a self - args.push(self.keep()); + args.push(self); while( !peek(')') ) { Node arg = parseAsgn(); if( arg==null ) break; @@ -2514,19 +1792,11 @@ public class Parser { } // Control & memory after parsing args args.set(0,ctrl().keep()); - args.set(1,mem().keep()); + args.set(1,_scope.mem().keep()); args.push(fcn); // Function pointer // Unkeep them all for( Node arg : args ) arg.unkeep(); - return functionCall(args,site); - } - - private Node functionCall( Ary<Node> args ) { - return functionCall(args,loc()); - } - - private Node functionCall( Ary<Node> args, Lexer site ) { // Dead into the call? Skip all the node gen if( ctrl()._type == Type.XCONTROL ) { for( Node arg : args ) @@ -2536,18 +1806,16 @@ public class Parser { } // Into the call - CallNode call = (CallNode)new CallNode(site, args.asAry()).peephole(); + CallNode call = (CallNode)new CallNode(loc(), args.asAry()).peephole(); // Post-call setup - CallEndNode cend = (CallEndNode)new CallEndNode(call,TypeRPC.constant(_code.rpc(_ref._cname))).peephole(); + CallEndNode cend = (CallEndNode)new CallEndNode(call).peephole(); call.peephole(); // Rerun peeps after CallEnd, allows early inlining - cend = (CallEndNode)cend.keep().peephole(); // TODO: Might inline // Control from CallEnd ctrl(new CProjNode(cend,0,ScopeNode.CTRL).peephole()); - // Memory from CallEnd - Node mem = new ProjNode(cend,1,ScopeNode.MEM0).peephole(); - mem(mem); + // A call may update any alias, so its memory result is the new whole state. + _scope.mem(new ProjNode(cend,1,ScopeNode.MEM0).peephole()); // Call result - return new ProjNode(cend.unkeep(),2,"#2").peephole(); + return new ProjNode(cend,2,"#2").peephole(); } // Just after parsing "type foo = " can parse `"C"` @@ -2559,27 +1827,10 @@ public class Parser { return false; } // External linked constant - private ConstantNode externDecl( String ex, Type t ) { - if( t instanceof TypeFunPtr tfp ) { // Generic TFP from type parse - int fidx = _code.fidx(_ref._cname); // Get a FIDX - tfp = tfp.makeFrom(fidx); // Become a constant - _code.externFunc(fidx,ex); // Map fidx to extern name - t = tfp; - } - 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)); + private Node externDecl( String ex, Type t ) { + if( t instanceof TypeFunPtr tfp ) // Generic TFP from type parse + t = _code.makeFun(tfp); // Get a FIDX, becomes a constant + return new ExternNode(t,ex).peephole(); } /** @@ -2587,36 +1838,13 @@ public class Parser { * * <pre> * integerLiteral: [1-9][0-9]* | [0] - * floatLiteral: [digits].[digits]?[e [digits]]?[f]? + * floatLiteral: [digits].[digits]?[e [digits]]? * </pre> */ private ConstantNode parseLiteral() { return con(_lexer.parseNumber()); } - ConstantNode con( long con ) { return _code.con(con); } - ConstantNode con( Type t ) { return _code.con(t ); } - private void classInit(Lexer site, Node ptr) { - // Ordinary load machinery resolves the flag alias and offset when - // the receiver is still a forward reference. - Field flag = ptr._type instanceof TypeMemPtr tmp ? tmp._obj.field("$init") : null; - int alias = flag==null ? 1 : flag._alias; - Node ready = new LoadNode(site,"$init",alias,null,mem(),ptr,fldoff(ptr,"$init")).peephole(); - CLInitNode init = new CLInitNode(site,ctrl(),mem(),ptr,ready).peephole().keep(); - ctrl(new CProjNode(init,0,ScopeNode.CTRL).peephole()); - Node bulk = new ProjNode(init,1,ScopeNode.MEM0).peephole().keep(); - flag = init.flag(); - mem(flag==null ? bulk : mergeAlias(bulk,flag._alias, - new ProjNode(init,2,memName(flag._alias)).peephole())); - bulk.unkeep(); - init.unkeep(); - } - - // Field offset for a known type (but unknown offset) - public static ConFldOffNode off( TypeStruct ts, String fname ) { - return (ConFldOffNode)(new ConFldOffNode(ts,fname).peephole()); - } - // Field offset for an unknown type - public Node fldoff( Node n, String name ) { - return new FldOffNode(n,name,loc()).peephole(); - } + public static Node con( long con ) { return con==0 ? ZERO : con(TypeInteger.constant(con)); } + public static ConstantNode con( Type t ) { return (ConstantNode)new ConstantNode(t).peephole(); } + public static ConFldOffNode off( TypeStruct base, String fname ) { return (ConFldOffNode)(new ConFldOffNode(base._name,fname).peephole()); } public Node peep( Node n ) { // Peephole, then improve with lexically scoped guards return _scope.upcastGuard(n.peephole()); @@ -2666,14 +1894,6 @@ public class Parser { return require(con(TypeInteger.constant(c)),"'"); } - private boolean noLowerCase(String s) { - for( int i=0; i<s.length(); i++ ) - if( Character.isLowerCase(s.charAt(i)) ) - return false; - return true; - } - - ////////////////////////////////// // Utilities for lexical analysis @@ -2738,23 +1958,18 @@ public class Parser { // Input buffer; an array of text bytes read from a file or a string private final byte[] _input; - public final CompUnit _cu; // Original source, retained across inlining. // Tracks current position in input buffer private int _position = 0; + public boolean before(Lexer that) { + return that!=null && _input==that._input && _position<that._position; + } + // private int _line_number = 1; // Start of current line private int _line_start = 0; - /** True when this location precedes {@code that} in the same source. */ - public boolean before(Lexer that) { - return that != null && (_cu!=null ? _cu==that._cu : _input==that._input) && _position < that._position; - } - - public String position() { return _line_number+":"+(_position-_line_start+1); } - public int pos() { return _position; } - /** * Record the source text for lexing */ @@ -2767,45 +1982,6 @@ public class Parser { */ public Lexer(byte[] buf) { _input = buf; - _cu = null; - } - - private Lexer(CompUnit cu) { - _input = cu._src.getBytes(); - _cu = cu; - } - - // Imported location: source text is only needed when reporting an error. - private Lexer(CompUnit cu, int pos) { - _input = null; - _cu = cu; - _position = pos; - } - - public static void gather(Lexer loc, HashMap<String,Integer> strs) { - if( loc==null || loc._cu==null ) return; - Serialize.gather(strs,loc._cu._cname); - String file=loc._cu.sourceFile(); - if( file!=null ) Serialize.gather(strs,file); - } - - public static void packed(Lexer loc, BAOS out, HashMap<String,Integer> strs) { - if( loc==null || loc._cu==null ) { out.packed4(0); return; } - out.packed4(loc._position+1); - out.packed4(strs.get(loc._cu._cname)); - String file=loc._cu.sourceFile(); - out.packed4(file==null ? 0 : strs.get(file)); - } - - public static Lexer unpack(BAOS in, String[] strs) { - int pos=in.packed4(); - if( pos==0 ) return null; - String owner=strs[in.packed4()]; - String file=strs[in.packed4()]; - CompUnit cu=CodeGen.CODE._compunits.get(owner.replace('.','/')); - assert cu!=null : owner; - if( !file.isEmpty() ) cu._sourceFile=file; - return new Lexer(cu,pos-1); } /** @@ -2813,7 +1989,6 @@ public class Parser { */ private Lexer(Lexer l) { _input = l._input; - _cu = l._cu; _position = l._position; _line_number = l._line_number; _line_start = l._line_start; @@ -2822,7 +1997,7 @@ public class Parser { // Very handy in the debugger, shows the unparsed program @Override public String toString() { - return _input==null ? _cu._cname+"@"+_position : new String(_input, _position, _input.length - _position); + return new String(_input, _position, _input.length - _position); } // True if at EOF @@ -2941,7 +2116,7 @@ public class Parser { // Used for errors String getAnyNextToken() { - if (isEOF()) return "EOF"; + if (isEOF()) return ""; if (isIdStart(peek())) return parseId(); if (isNumber(peek())) return parseNumberString(); if (isPunctuation(peek())) return parsePunctuation(); @@ -2955,26 +2130,17 @@ public class Parser { private Type parseNumber() { int old = _position; int len = isLongOrDouble(); - boolean f32 = peek()=='f'; - if( f32 ) nextChar(); - int n = Math.abs(len); - if( f32 ) - return TypeFloat.constant(Float.parseFloat(new String(_input,old,n))); if( len > 0 ) { if( len > 1 && _input[old]=='0' ) throw error("Syntax error: integer values cannot start with '0'",this); long i = Long.parseLong(new String(_input,old,len)); return TypeInteger.constant(i); } - return TypeFloat.constant(Double.parseDouble(new String(_input,old,n))); + return TypeFloat.constant(Double.parseDouble(new String(_input,old,-len))); } private String parseNumberString() { int old = _position; int len = Math.abs(isLongOrDouble()); - if( peek()=='f' ) { - nextChar(); - len++; - } return new String(_input,old,len); } @@ -3004,7 +2170,7 @@ public class Parser { private String parseId() { int start = _position; while (isIdLetter(nextChar())) ; - return new String(_input, start, --_position - start).intern(); + return new String(_input, start, --_position - start); } // @@ -3039,9 +2205,9 @@ public class Parser { } } - ParseException errorSyntax(String syntax) { return _errorSyntax("expected `"+syntax+"`"); } + ParseException errorSyntax(String syntax) { return _errorSyntax("expected "+syntax); } private ParseException _errorSyntax(String msg) { - return error("Syntax error, "+msg+" but found `" + _lexer.getAnyNextToken()+"`"); + return error("Syntax error, "+msg+": " + _lexer.getAnyNextToken()); } ParseException error(String msg) { return error(msg,_lexer); } public static ParseException error(String msg, Lexer loc) { return new ParseException(msg,loc); } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/Var.java b/chapter25/src/main/java/com/seaofnodes/simple/Var.java index af371d21..e13b85c5 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/Var.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/Var.java @@ -1,7 +1,6 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.Utils; /** @@ -14,12 +13,9 @@ public class Var { // promotions. public int _idx; // index in containing scope private Type _type; // Declared type + public boolean _init; // Explicit declaration initializer public boolean _final; // Final field public boolean _fref; // Forward ref - // 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 ) { @@ -33,8 +29,14 @@ public class Var { _fref = fref; _loc = loc; } - public Type type() { return _type; } - public Type _type() { return _type; } // ScopeNode printer needs to have no side effects + // Raw declared type, without resolving a forward reference while printing. + public Type _type() { return _type; } + public Type type() { + if( !_type.isFRef() ) return _type; + // Update self to no longer use the forward ref type + Type def = Parser.TYPES.get(((TypeMemPtr)_type)._obj._name); + return (_type=_type.meet(def)); + } // Forward reference variables (not types) must be BOTTOM and // distinct from inferred variables @@ -49,6 +51,6 @@ public class Var { } @Override public String toString() { - return (_fref ? "FREF " : "")+(_uninit ? "UNINIT " : "")+_type.toString()+(_final ? " ": " !")+_name; + return _type.toString()+(_final ? " ": " !")+_name; } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java index 0b314978..2319714b 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java @@ -15,16 +15,11 @@ abstract public class BuildLRG { // since it will not color, just split the conflicted ranges now). Returns // true if no hard-conflicts, although we still might not color. public static boolean run(int round, RegAlloc alloc) { - FunNode fun = null; - for( Node bb : alloc._code._cfg ) { - if( bb instanceof FunNode fun2 ) - fun = fun2; + for( Node bb : alloc._code._cfg ) for( Node n : bb.outs() ) { // A synthetic never-exit value has no runtime definition. if( n._type==Type.TOP ) continue; if( n instanceof PhiNode phi && !(phi._type instanceof TypeMem) ) { - if( phi instanceof MachNode mach && mach.outregmap()==null ) - continue; // All real Phi inputs end up with the same LRG. // Pass 1: find any pre-existing LRG, to avoid make-then-Union a LRG LRG lrg = alloc.lrg(phi); @@ -33,8 +28,8 @@ abstract public class BuildLRG { if( (lrg = alloc.lrg(phi.in(i))) != null ) break; // If none, make one. - if( lrg==null ) lrg = alloc.newLRG(n,fun); - if( phi instanceof MachNode ) defLRG(alloc,n,fun); + if( lrg==null ) lrg = alloc.newLRG(n); + if( phi instanceof MachNode ) defLRG(alloc,n); // Pass 2: everybody uses the same LRG lrg=alloc.union(lrg,phi); for( int i=phi instanceof ParmNode ? 2 : 1; i<n.nIns(); i++ ) @@ -56,7 +51,7 @@ abstract public class BuildLRG { } // Define live range - defLRG(alloc,n,fun); + defLRG(alloc,n); // Now, look in the opposite direction. How are incoming // LRGs affected by this node: For all uses, make live lrgs @@ -76,9 +71,8 @@ abstract public class BuildLRG { if( n instanceof MultiNode && !(n instanceof CFGNode) ) for( Node proj : n.outs() ) if( proj instanceof MachNode ) - defLRG(alloc,proj,fun); + defLRG(alloc,proj); } - } // Collect live ranges alloc.unify(); @@ -86,12 +80,12 @@ abstract public class BuildLRG { return alloc.success(); } - private static void defLRG( RegAlloc alloc, Node n, FunNode fun ) { + private static void defLRG( RegAlloc alloc, Node n ) { MachNode mach = (MachNode)n; RegMask def_mask = mach.outregmap(); if( def_mask == null ) return; LRG lrg = mach.twoAddress() == 0 - ? alloc.newLRG(n,fun) // Define a new LRG for N + ? alloc.newLRG(n) // Define a new LRG for N : alloc.lrg2(n,mach.twoAddress()); // Use the matching 2-adr input // Record mask and mach if( !lrg.machDef(mach,def_mask.size1()).and(def_mask) ) diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java deleted file mode 100644 index 6c70b71d..00000000 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java +++ /dev/null @@ -1,214 +0,0 @@ -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.*; -import java.util.*; - -// Search for Class Initialization Cycles. - -// Done after Opto, so dead code does not make a cycle. - -// Build the inits-graph, a call-graph plus CLInits plus class parents. - -// (1) Scan all functions f, building _edges[f][]: the set of <cinit> fcns from -// CompUnits from CLInitNodes, and the set of fcn call targets in f and the -// class parent. These are all reported as fidxs (ints). -// -// (2) Walk the _edges graph using Tarjans SCC. Reject cycles containing more -// than one <clinit>. -// -// (3) Report on <clinit> cycles -// -public class ClassInitDependencies { - private final CodeGen _code; - // Function FIDX -> callees, touched initializers, and parent initializer. - public final int[][] _edgess; - private final int[] _pres; - private final int[] _lows; - private final int[] _stk ; - private int _sp; - - - // Build once; tests can inspect the graph independently of cycle checking. - public ClassInitDependencies( CodeGen code ) { - _code = code; - assert code._phase.ordinal() >= CodeGen.Phase.IterPostOpto.ordinal(); - _pres = new int[code._linker._len]; - _lows = new int[code._linker._len]; - _stk = new int[code._linker._len]; - - // - BitSet visit = code.visit(); // Visit set per-fcn - Ary<CFGNode> work = new Ary<>(CFGNode.class); // Worklist per-fcn - IntHashMap iset = new IntHashMap(0); // dedup hash set per-fcn - _edgess = new int[code._linker._len][]; - - // For all functions, build the _edges array - for( FunNode fun : code._linker ) - if( fun!=null && !fun.isDead() ) - // Visit function and collect direct class touches and callees - _edgess[fun.sig().fidx()] = walk(code,fun,work,iset,visit,null); - visit.clear(); - } - - // Return the outgoing init edge set for this function - private static int[] walk( CodeGen code, FunNode fun, Ary<CFGNode> work, IntHashMap iset, BitSet visit, - Ary<Parser.Lexer> sites) { - // Walk function body, looking for outgoing init edges - assert iset .isEmpty(); // int hash set for de-dup - assert visit.isEmpty(); // needs a visit set, because loops - assert work .isEmpty(); // needs a worklist, because DAG, parallel explore paths - - // Children classes always dependent on parents - if( fun.isClz() && fun._compunit._par != null ) - iset.put(code.link(fun._compunit._par).sig().fidx(),1); - - work.push(fun); - while( !work.isEmpty() ) { - CFGNode cfg = work.pop(); - if( visit.get(cfg._nid) ) continue; // Been there, done that - visit.set(cfg._nid); - assert !cfg._type.isHigh(); - // Do not walk off end of function - if( cfg instanceof ReturnNode ) continue; - - // CLInit adds a class init requirement to this function - // (plus all parent classes) - if( cfg instanceof CLInitNode init ) { - FunNode clz = code.link(init.cu()); // <clinit> referred to - iset.put(clz.sig().fidx(),1); // Add to edge set - if( sites!=null && sites.atX(clz.sig().fidx())==null ) sites.setX(clz.sig().fidx(),init._loc); - } - - // Calls union all their fptr fidxs into the callee set - if( cfg instanceof CallNode call && call.fptr()._type instanceof TypeFunPtr fp ) { - assert !XInt.isHigh(fp.fidxs()) && fp.nfcns()>0; // Call is alive with valid targets - // Union call targets into the callee set - for( int dstx = XInt.next(fp.fidxs(),0); dstx>=0; dstx=XInt.next(fp.fidxs(),dstx) ) { - iset.put(dstx,1); - if( sites!=null && sites.atX(dstx)==null ) sites.setX(dstx,call._loc); - } - } - - // Forward recursion to walk the whole function - for( Node use : cfg._outputs ) { - // Do not walk into another function - if( !(use instanceof CFGNode cfg1) || use instanceof FunNode || use instanceof StopNode ) continue; - // Expect control flow edge is sensible - assert (use instanceof RegionNode r && r._inputs.find(cfg)>0) || use.in(0)==cfg; - if( !visit.get(cfg1._nid) ) // Small speed opt - work.add(cfg1); - } - } - - visit.clear(); - int[] edges = iset.isEmpty() ? null : iset.keySet(); - iset.clear(); - return edges; - } - - // Return an error string, or null if no cycle contains multiple clinits. - public String check() { - Arrays.fill(_pres,0); - _sp = 0; - _pres[0] = 1; // Preorders stored in pres[0] and start at 1 - - int idx = -1; - for( int fidx = 1; fidx < _edgess.length; fidx++ ) - if( _pres[fidx] == 0 && // Already visited - (idx = scc(fidx)) != -1 ) // And found a cycle - return report(idx); // Report cycle - return null; - } - - // Every internal edge of an SCC belongs to a cycle. Print all of them: - // stack order is not a path, and enumerating all cycle paths can explode. - // This bookkeeping and formatting are only needed on an error. - private String report(int idx) { - BitSet members = new BitSet(); - for( int i=idx; i<_sp; i++ ) members.set(_stk[i]); - SB sb = new SB().p("Cyclic class initialization:"); - // Recover one surviving source site per edge only after finding an - // error. A representative touch is enough; runtime first-touch order - // need not be statically known. Lexer snapshots retain the source CU - // even when the node has been inlined into a different function. - Ary<CFGNode> work = new Ary<>(CFGNode.class); - IntHashMap iset = new IntHashMap(0); - BitSet visit = new BitSet(); - Ary<Parser.Lexer> sites = new Ary<>(Parser.Lexer.class); - for( int i=idx; i<_sp; i++ ) { - int src = _stk[i]; - FunNode fun = _code.link(src); - Arrays.fill(sites._es,null); - sites.clear(); - walk(_code,fun,work,iset,visit,sites); - if( _edgess[src]!=null ) - for( int dst : _edgess[src] ) - if( members.get(dst) ) { - sb.p("\n ").p(name(src)).p(" -> ").p(name(dst)); - Parser.Lexer loc = sites.atX(dst); - if( loc!=null && loc._cu!=null ) - sb.p('\n').p(SourceError.format(loc._cu,loc.pos(),"cyclic class initialization")); - else if( fun.isClz() && fun._compunit._par==_code.link(dst)._compunit ) - sb.p("\n parent initialization requirement"); - } - } - return sb.toString(); - } - - private String name(int fidx) { - FunNode fun = _code.link(fidx); - String owner=fun._compunit._cname; - if( fun.isClz() ) return owner; - String label=fun.label(); - return (label.startsWith(owner+".") ? label : owner+"."+label)+" [fidx "+fidx+"]"; - } - - // Tarjan SCC - int scc( int fidx ) { - // Assign pre-order number - _lows[fidx] = _pres[fidx] = _pres[0]++; - _stk[_sp++] = fidx; - // walk outbound edges - if( _edgess[fidx]!=null ) { - for( int edge : _edgess[fidx] ) { - if( edge >= _pres.length ) continue; // Out of bounds happens when calling C fcns - if( _pres[edge] == 0 ) { // Not visited - int idx = scc(edge); - if( idx != -1 ) return idx; // Found a cycle - _lows[fidx] = Math.min(_lows[fidx],_lows[edge]); - } else if( _edgess[edge]!=null && onStk(edge) ) { - _lows[fidx] = Math.min(_lows[fidx],_pres[edge]); - } - } - } - // Found a cycle - if( _lows[fidx] == _pres[fidx] ) { - int idx = find(fidx); - if( hasClzCycle(idx) ) - return idx; // Found a class cycle - // Pop stack, including fidx - _sp = idx; - } - return -1; // No cycle - } - - private int find(int e) { - for( int i=0; i<_sp; i++ ) if( _stk[i] == e ) return i; - return -1; - } - private boolean onStk(int e) { return find(e) != -1; } - - private boolean hasClzCycle(int idx) { - int clinits = 0; - for( int i=idx; i<_sp; i++ ) { - FunNode fun = _code.link(_stk[i]); - if( fun!=null && fun.isClz() && ++clinits > 1 ) - return true; - } - return false; - } - -} 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 47ec0112..b79eb73d 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java @@ -1,340 +1,118 @@ package com.seaofnodes.simple.codegen; import com.seaofnodes.graph.GraphObserver; + import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.print.*; import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.*; -import java.io.File; +import com.seaofnodes.simple.util.Ary; +import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.util.Utils; +import java.io.IOException; import java.util.*; - -// Compile Driver for a Compilation Unit @SuppressWarnings("unchecked") public class CodeGen { - - // Last created CodeGen as a global; used all over to avoid passing about a "context". - public static CodeGen CODE; - - // Location of built-in CPU ports + public GraphObserver<Node> _obs; public static final String PORTS = "com.seaofnodes.simple.node.cpus"; + // Last created CodeGen as a global; used all over to avoid passing about a + // "context". + public static CodeGen CODE; - // Compile phases public enum Phase { - Parse, // Parse ASCII text into Sea-of-Nodes IR - Iter, // Pessimistic peepholes after parse - Opto, // SCCP, numeric modes, seed the peephole worklist + Parse, // Build ideal IR from source + Iter, // Pessimistic peepholes + Opto, // Optimistic interprocedural SCCP IterPostOpto, // Peepholes and inlining using SCCP results - FinalizeIR, // Field layout, offset cleanup, function/CU roots - TypeCheck, // Last check for bad programs - Serialize, // Save ideal IR, including opaque class initialization - Lower, // Expand checked class initialization to ordinary IR - LoopTree, // Build a loop tree; break infinite loops - Unlink, // Unlink call sites before machine code generation - Select, // Convert to target hardware nodes - Schedule, // Global schedule (code motion) nodes - LocalSched, // Local schedule - RegAlloc, // Register allocation - Encoding, // Encoding - Export, // Export - LastPhase // After last phase - } - public Phase _phase; - // Null for normal compilation; owned by this compilation, not a global viewer. - public GraphObserver<Node> _obs; - - /** True when tests deliberately randomize Iter worklist order. */ - public static boolean iterSeedOverridden() { return System.getProperty("simple.iter.seed") != null; } - - // Expensive assert sampling: - // -Dsimple.assert.expensive=0 disables these checks - // -Dsimple.assert.expensive=1 checks every step - // -Dsimple.assert.expensive=N checks once every 1<<N steps - private static final int EXPENSIVE_ASSERT_LOG = Integer.getInteger("simple.assert.expensive",8); - public static boolean expensiveAssert() { return EXPENSIVE_ASSERT_LOG > 0; } - public static boolean expensiveAssert( int trip ) { - if( EXPENSIVE_ASSERT_LOG <= 0 ) return false; - if( EXPENSIVE_ASSERT_LOG == 1 ) return true; - int shift = Math.min(EXPENSIVE_ASSERT_LOG,30); - return (trip & ((1<<shift)-1)) == 0; - } - - // --------------------------- - // Module Source Root - public final String _modDir; - // Module Build Root - public final String _buildDir; - // Search path from CWD for `.o` files containing external symbols and types. - // This can contain archives, zips and other .o file containers. - public final Ary<String> _externPaths; - - // Dotted path from module root to file containing the source, sans ".smp". - // e.g. module root: "sys", nested source name "sys.io". - public final String _srcName; - - // Only available for tests, otherwise source comes from the CompUnit - private final String _src; - - // Current Working Directory; default module base - private final String _cwd; - - // Compilation Units used in this compile; typically one per source/object - // file, although you can nest structs and get more than one. These are all - // required to compile, and are expected to have their source timestamps - // ahead of their obj file. Some of these may entirely be dead or inlined - // in this CompUnit and thus not needed in any linking stage. All the - // alive ones (inlined away or not) participate in class init cycle checks. - public HashMap<String,CompUnit> _compunits; - - // Test shortcut for only one compilation unit - public CompUnit compunit() { - assert _compunits.size()==1; - for( CompUnit cu : _compunits.values() ) - return cu; - assert false : "no compilation unit"; - return null; - } - - // True when fidx is published through a public class or instance field. - public boolean publicFIDX(int fidx) { - if( fidx < GlobalBits.RESERVED ) return false; - // A public class initializer is itself an external root; unlike - // constructors and methods, it is not reached through a class field. - FunNode fun = _linker.atX(fidx); - if( fun != null && fun.isClz() && fun.isPublic() ) - return true; - // Include instance methods even while their constant function fields - // are stored in each instance rather than on the class object. - for( Type t : Parser.TYPES.values() ) - if( t instanceof TypeStruct clz && - Parser.startsClzPrefix(clz._name) && - Parser.isPublicClassName(clz._name) && - resolvedClass(clz) ) { - if( publicFieldFIDX(clz,fidx) ) return true; - Type self = Parser.TYPES.get(clz._name.substring(Parser.CLZ.length())); - if( self instanceof TypeStruct ts && publicFieldFIDX(ts,fidx) ) return true; - } - return false; - } - - private static boolean publicFieldFIDX(TypeStruct clz, int fidx) { - boolean hasCtor = clz.field("<ctor>") != null; - for( Field fld : clz._fields ) - if( fld._fname.charAt(0)!='_' && fld._t instanceof TypeFunPtr tfp && - // With a user constructor, class:N.N is the private <init>. - !(hasCtor && clz.hiddenInit(fld)) && - XInt.bit(tfp._fidxs,fidx) ) - return true; - return false; - } - - public static boolean hasUserConstructor(TypeStruct self) { - Type t = Parser.TYPES.get(Parser.addClzPrefix(self._name)); - return t instanceof TypeStruct clz && clz.field("<ctor>") != null; - } - - // A forward reference gets a class object and constructor signature so - // parsing can proceed, but it has no callable definition yet. - private static boolean resolvedClass(TypeStruct clz) { - Type t = Parser.TYPES.get(clz._name.substring(Parser.CLZ.length())); - return !(t instanceof TypeStruct ts) || !ts._fref; - } - - private int[] _publicFIDXs, _publicAliases; - - /** - * Freeze the externally visible portion of the program after ParseAll has - * loaded every compilation unit and closed/upgraded all types. Later - * optimization can remove linker entries and create inline-only fidxs; - * neither operation changes this interface. - */ - void freezePublicInterface() { - assert _publicFIDXs==null && _publicAliases==null; - int[] fidxs = XInt.EMPTY; - for( int fidx=GlobalBits.RESERVED; fidx<_linker._len; fidx++ ) - if( publicFIDX(fidx) ) - fidxs = XInt.make(fidxs,fidx); - int[] aliases = XInt.EMPTY; - for( Type t : Parser.TYPES.values() ) - if( t instanceof TypeStruct clz && - Parser.startsClzPrefix(clz._name) && - Parser.isPublicClassName(clz._name) && - resolvedClass(clz) ) { - boolean hasCtor = clz.field("<ctor>") != null; - for( Field fld : clz._fields ) - if( fld._fname.charAt(0)!='_' && fld._t!=Type.TOP && - !(hasCtor && clz.hiddenInit(fld)) ) - aliases = XInt.make(aliases,fld._alias); - } - _publicFIDXs = fidxs; - _publicAliases = aliases; - } - - public int[] publicFIDXs() { assert _publicFIDXs !=null; return _publicFIDXs; } - public int[] publicAliases(){ assert _publicAliases!=null; return _publicAliases; } - public boolean publicInterfaceFrozen() { return _publicFIDXs != null; } - - // --------------------------- - - // Very common nodes, cached here - public final ConstantNode ZERO; - public final XCtrlNode XCTRL; + FinalizeIR, // Finish function cleanup and layouts + TypeCheck, // Check the surviving ideal graph + LoopTree, // Discover loops and connect nonreturning paths + Select, // Select machine instructions + Schedule, // Global code motion + LocalSched, // Order instructions within blocks + RegAlloc, // Assign registers and stack slots + Encoding, // Encode instructions + Export; // Link in memory or write ELF - public Node constant( Type t ) { - assert _phase==null || _phase.ordinal() <= Phase.Lower.ordinal(); - return ConstantNode.make(t).peephole(); } - public ConstantNode con( Type t ) { return (ConstantNode)constant(t); } - public ConstantNode con( long con ) { return con==0 ? ZERO : con(TypeInteger.constant(con)); } - - - // --------------------------- - // Test setup; no module nor file with specific argument - public CodeGen( String src ) { this(src, 126L, true ); } - - // Test setup; no module nor file; can alter seed & argument; can re-run same CodeGen - public CodeGen( String src, Type arg ) { - this(null,null,null,null, src, 126L, true, arg); - } - // Test setup; no module nor file; can alter seed & argument; can re-run same CodeGen - public CodeGen( String src, long workListSeed, boolean resetTypes ) { - this(null,null,null,null, src, workListSeed, resetTypes, TypeInteger.BOT); - } - - // Generic CodeGen, including full module setup - public CodeGen( String modDir, String buildDir, Ary<String> externPaths, - String srcName, String src, long workListSeed, boolean resetTypes, Type arg ) { - // Public singleton to avoid passing about this state to a huge count - // of places. Probably becomes a TLS at some point. - CODE = this; - if( resetTypes ) Type.reset(); // Reset to recover from failed test - - _cwd = System.getProperty("user.dir")+"/"; - _modDir = modDir == null ? _cwd : modDir; - _buildDir = buildDir == null ? _cwd : buildDir; - _externPaths = externPaths; - _srcName = srcName; - _callingConv = null; // Calling convention - // Source code from test strings, not files - _src = src; - // All the compilation units - _compunits = new HashMap<>(); - _phase = null; - // Start GVN table - _gvn = new HashMap<>(); - // Allow whole-suite worklist-order sweeps without rewriting the many - // explicit seeds used by tests and command-line compilation. - String iterSeed = System.getProperty("simple.iter.seed"); - _iter = new IterPeeps(iterSeed == null ? workListSeed : Long.parseLong(iterSeed)); - // End points of graph - _stop = new StopNode().init(); - _start = new StartNode(null,_stop,arg).init(); - ZERO = con(TypeInteger.ZERO).keep(); - XCTRL = new XCtrlNode().peephole().keep(); - P = new Parser(this); - } - + public Phase _phase; - // Run requested phases. File export must request Serialize before Lower; - // requesting Export up front does that automatically. Encoding alone does - // not retain reloadable ideal IR. - - // No code emission, just IR generation - public CodeGen driver( Phase phase ) { return driver(phase,null,null,false,false,0); } - // No object file writing, but code generation for a specific cpu/os pair (allows emulation) - public CodeGen driver( Phase phase, String cpu, String callingConv ) { - if( phase.ordinal() < Phase.Export.ordinal() ) - // No code outputted - return driver(phase,cpu,callingConv,false,false,0); - if( _srcName == null ) - throw new RuntimeException("No source filename provided, so do not know how to name the obj file"); - boolean emitEntrySymbol = !_srcName.contains("."); - return driver( phase, cpu, callingConv, false, emitEntrySymbol, 0 ); - } - // Write an object file for a specific cpu/os pair - - public CodeGen driver( String cpu, String callingConv, boolean inMemory, boolean emitEntrySymbol ) { return driver(Phase.Export,cpu,callingConv,inMemory,emitEntrySymbol,0); } - - // The Generic driver - public CodeGen driver( Phase target, String cpu, String callingConv, boolean inMemory, boolean emitEntrySymbol, int dump ) { - // Parse a Simple file, and for mentioned external symbols either load - // from .o or recursively parse - step(target, Phase.Parse, this::parse, dump); - // Run peepholes to a fixed point - step(target, Phase.Iter, this::iter, dump); - // Run SCCP (and resolve int/flt expressions) - step(target, Phase.Opto, this::opto, dump); - // Run peepholes to a fixed point again, on the better types. + // Run through target, skipping phases already completed. + public CodeGen driver(Phase target) { return driver(target,null,null); } + public CodeGen driver(Phase target, String cpu, String callingConv) { return driver(target,cpu,callingConv,0); } + public CodeGen driver(Phase target, String cpu, String callingConv, int dump) { + if( target.ordinal()>=Phase.RegAlloc.ordinal() && cpu==null && _mach==null ) + throw new IllegalArgumentException("Machine code requires a CPU target"); + if( target==Phase.Export ) throw new IllegalArgumentException("Use exportELF with an output file or null for memory linking"); + step(target, Phase.Parse, this::parse, dump); + step(target, Phase.Iter, this::iter, dump); + step(target, Phase.Opto, this::opto, dump); step(target, Phase.IterPostOpto, this::iterPostOpto, dump); - // Field layouts; field symbols become constant offsets - step(target, Phase.FinalizeIR, this::finalizeIR, dump); - // Final error check - bad programs fail here - step(target, Phase.TypeCheck, this::typeCheck, dump); - // Save reloadable ideal IR for a later seperate compilation - step(target, Phase.Serialize, () -> Serialize.forTarget(this,target,inMemory), dump); - // Replace "fat" nodes with their exploded explicit IR; this can - // introduce CFG diamonds - step(target, Phase.Lower, this::lower, dump); - // Compute the loop-tree, used for approximating exec frequencies for - // scheduling and register allocation - step(target, Phase.LoopTree, this::loopTree, dump); - // Drop imports for machine selection, retain them for ideal evaluation. - step(target, Phase.Unlink, () -> unlink(cpu!=null), dump); - // Instruction selection + step(target, Phase.FinalizeIR, this::finalizeIR, dump); + step(target, Phase.TypeCheck, this::typeCheck, dump); + step(target, Phase.LoopTree, this::loopTree, dump); step(target, Phase.Select, () -> instSelect(cpu,callingConv), dump); - // Global Code Motion - step(target, Phase.Schedule, this::GCM, dump); - // Local (basic block only) scheduling - step(target, Phase.LocalSched, this::localSched, dump); - // Register allocation - step(target, Phase.RegAlloc, this::regAlloc, dump); - // Encode machine instructions into bits - step(target, Phase.Encoding, this::encode, dump); - // Export bits (and serialized IR) into ELF files - step(target, Phase.Export, () -> exportELF(inMemory,emitEntrySymbol), dump); + step(target, Phase.Schedule, this::GCM, dump); + step(target, Phase.LocalSched, this::localSched, dump); + step(target, Phase.RegAlloc, this::regAlloc, dump); + step(target, Phase.Encoding, this::encode, dump); return this; } - // The driver names the order; optional phases decide locally whether to run. private void step(Phase target, Phase next, Runnable work, int dump) { - if( next.ordinal() > target.ordinal() || (_phase!=null && _phase.ordinal()>=next.ordinal()) ) return; + if( next.ordinal()>target.ordinal() || (_phase!=null && _phase.ordinal()>=next.ordinal()) ) return; Phase prior = _phase; work.run(); - if( _phase!=prior ) dump(dump); + if( _phase!=prior && (dump & (1<<_phase.ordinal()))!=0 ) { + if( (dump & (1<<30))!=0 ) System.err.println("After "+_phase+":"); + System.err.println(this); + } } - // All phase entry points share ordering checks, timing, and viewer notification. - // Record elapsed time even on failure, but report completion only on success. - CodeGen phase(Phase next, boolean ordered, Runnable work) { + public final long[] _times = new long[Phase.values().length]; + private CodeGen phase(Phase next, boolean ordered, Runnable work) { assert ordered : "Cannot run "+next+" after "+_phase; _phase = next; long t0 = System.currentTimeMillis(); - work.run(); // Do The Phase - _times[next.ordinal()] = System.currentTimeMillis() - t0; - if( _obs!=null ) _obs.phase(next.name()); // Record for viewer + try { work.run(); } + finally { _times[next.ordinal()] = System.currentTimeMillis()-t0; } + if( _obs!=null ) _obs.phase(next.name()); return this; } - public String entryClinitName() { - return Parser.addClzPrefix(_srcName==null ? "Test" : _srcName)+".<clinit>"; - } - // Verbose printing during compilation - private void dump(int dump) { - int p2 = _phase.ordinal(); - if( (dump & (1<<p2)) == 0 ) return; - if( (dump & (1<<30)) != 0 ) - System.err.println("After "+_phase+":"); - System.err.println(_phase.ordinal() >= Phase.LocalSched.ordinal() - ? asm(new SB()).toString() - : IRPrinter.prettyPrint(this)); + // --------------------------- + // Compilation source code + public final String _src; + // Compile-time known initial argument type + public final TypeInteger _arg; + + // --------------------------- + public CodeGen( String src ) { this(src, TypeInteger.BOT, 123L, true ); } + public CodeGen( String src, TypeInteger arg) { this(src, arg==null ? TypeInteger.BOT : arg, 123L, true ); } + + public CodeGen( String src, TypeInteger arg, long workListSeed, boolean reset ) { + CODE = this; + if( reset ) Type.reset(); + _main = makeFun(TypeFunPtr.MAIN); + _phase = null; + _callingConv = null; + _start = new StartNode(arg); + _stop = new StopNode(src); + _src = src; + _arg = arg; + _iter = new IterPeeps(workListSeed); + P = new Parser(this,arg); } - // Record times by phase - public final long[] _times = new long[Phase.LastPhase.ordinal()]; + // Run all the phases through final ELF emission + public CodeGen driver( String cpu, String callingConv, String obj ) throws IOException { + return driver(Phase.Encoding,cpu,callingConv).exportELF(obj); + } + // --------------------------- /** @@ -347,45 +125,9 @@ public class CodeGen { return _uid++; } - - // These next fields all 2-way map some *global* program feature to a - // local dense index, suitable for packing in BitSets. The mapping takes a - // global value - always a {source file/class} name, and an order number - // counting up per feature in the same file. The dense local index can be - // different in different compilations. - - // The global info allows loading the same remote class info from different - // paths and aligning them. Example: Compiling A loads pre-compiled B and - // C, both of which load pre-compiled D. The 2 different D loads unify via - // this global info. - - // These mappings are all trivial identities when compiling one file; they - // only become complex when loading separately compiled code. - - // 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 - 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 - public final GlobalBits _fidxs = new GlobalBits(); - // Return local index for specific global file & order. Used to find fidxs for e.g. FREF class <init> fcns - public int fidx( String clz, int order ) { return _fidxs.next(clz, order); } - // New fidx in the current file - public int fidx( String clz ) { return fidx(clz, -1); } - - // Compute local RPC index from global RPC info, one per call - public final GlobalBits _rpcs = new GlobalBits(); - public int rpc( String clz ) { return _rpcs.next(clz); } + // Next available memory alias number + private int _alias = 2; // 0 is for control, 1 for memory + public int getALIAS() { return _alias++; } // Inlining can insert CFG edges and invalidate cached depth ordering. @@ -400,35 +142,40 @@ public class CodeGen { public final BitSet _visit = new BitSet(); public BitSet visit() { assert _visit.isEmpty(); return _visit; } - // Start and Stop; end points of the generated IR + // Start and stop; end points of the generated IR public StartNode _start; public StopNode _stop; // Global Value Numbering. Hash over opcode and inputs; hits in this table // are structurally equal. - public final HashMap<Node,Node> _gvn; + public final HashMap<Node,Node> _gvn = new HashMap<>(); + // Source of unique function indices + private int _fidx =0; + public TypeFunPtr makeFun( TypeFunPtr fun ) { + int fidx = _fidx++; + assert fidx<64; // TODO: need a larger FIDX space + return fun.makeFrom(fidx); + } + // Signature for MAIN + public TypeFunPtr _main; + public void setMain(FunNode main) { + _main = main.sig(); + link(main); + } // Reverse from a constant function pointer to the IR function being called. // Error to call with a non-constant TFP public FunNode link( TypeFunPtr tfp ) { return link(tfp.fidx()); } - // Read-only linker lookup, suitable for printers and debugger display. - // In particular, do not lazily remove a partially dead function. - public FunNode _link(int fidx) { return _linker.atX(fidx); } + // Read-only diagnostic lookup; preserve stale entries. public FunNode _link(TypeFunPtr tfp) { return _link(tfp.fidx()); } - // Return the FunNode from a fidx + public FunNode _link(int fidx) { return _linker.atX(fidx); } + // Return the FunNode from a fidx, cleaning up dead entries. public FunNode link( int fidx ) { FunNode fun =_linker.atX(fidx); if( fun!=null && fun.isDead() ) { _linker.setX(fidx,null); fun = null; } return fun; } - // Return the <clinit> for a CompUnit - public FunNode link( CompUnit cu ) { - for( FunNode fun : _linker ) - if( fun!=null && fun._compunit == cu && fun.isClz() ) - return fun; - return null; - } // Insert linker mapping from constant function signature to the function // being called. @@ -440,33 +187,32 @@ public class CodeGen { // "Linker" mapping from constant TypeFunPtrs to heads of function. These // TFPs all have exact single fidxs and their return is wiped to BOTTOM (so // the return is not part of the match). - public final Ary<FunNode> _linker = new Ary<>(FunNode.class); - - // Extern function declarations; input is the FIDX assigned to the name. - final HashMap<Integer,String> _externFunc = new HashMap<>(); - public void externFunc(int fidx, String ex) { - assert !_externFunc.containsKey(fidx); - _externFunc.put(fidx,ex); - } - public String externFunc(int fidx) { return _externFunc.get(fidx); } - - public String funcName(int fidx ) { - FunNode fun = _link(fidx); - if( fun!=null ) return fun._name; - return externFunc(fidx); - } - - public boolean owns(FunNode fun) { - return fun._compunit != null && fun._compunit._src != null; - } + final Ary<FunNode> _linker = new Ary<>(FunNode.class); + // Native symbols belong to function identities, not to one ExternNode. + // Selection and spill copies can turn that node into an ordinary constant. + private final HashMap<TypeFunPtr,String> _externs = new HashMap<>(); + public void externFunc(TypeFunPtr tfp, String name) { _externs.put(tfp.makeFrom(Type.BOTTOM),name); } + public String externFunc(TypeFunPtr tfp) { return _externs.get(tfp.makeFrom(Type.BOTTOM)); } - // --------------------------- // Parser object public final Parser P; // Parse ASCII text into Sea-of-Nodes IR - public CodeGen parse() { return phase(Phase.Parse, _phase==null, () -> ParseAll.parse(this,_src)); } + public CodeGen parse() { + return phase(Phase.Parse, _phase==null, () -> { + P.parse(); + // Close the declarations as a group before replacing old forward + // shapes in the graph. A can mention B before B mentions A; both + // must use the same complete cycle, including through functions. + HashMap<String,Type> closed = new HashMap<>(); + for( var entry : Parser.TYPES.entrySet() ) + if( entry.getValue() instanceof TypeMemPtr ptr && !ptr._obj._open ) + closed.put(entry.getKey(),ptr.makeFrom(ptr._obj.close(Parser.TYPES))); + Parser.TYPES.putAll(closed); + _start.walk(n -> { n.upgradeType(Parser.TYPES); return null; }); + }); + } // --------------------------- @@ -480,55 +226,28 @@ public class CodeGen { public void iterCnt() { if( !_midAssert ) _iter_cnt++; } public void iterNop() { if( !_midAssert ) _iter_nop_cnt++; } - // Pessimistic peepholes after parsing. This lifts loaded and parsed - // types before the optimistic Opto pass resets types and lets them fall. - public CodeGen iter() { return phase(Phase.Iter, _phase==Phase.Parse, () -> Iter.run(this)); } + // Pessimistic peepholes reach a fixed point after parsing. + public CodeGen iter() { return phase(Phase.Iter, _phase==Phase.Parse, () -> _iter.iterate(this)); } - // SCCP and numeric-mode resolution; leave cleanup work queued for Iter. + // SCCP computes types and queues changed nodes; Iter applies their rewrites. public CodeGen opto() { return phase(Phase.Opto, _phase==Phase.Iter, () -> Opto.opto(this)); } - public CodeGen iterPostOpto() { return phase(Phase.IterPostOpto, _phase==Phase.Opto, () -> _iter.iterate(this)); } public CodeGen finalizeIR() { return phase(Phase.FinalizeIR, _phase==Phase.IterPostOpto, () -> FinalizeIR.run(this)); } - - // Root a surviving function without claiming that it has unknown callers. - void keepInCompUnit(FunNode fun) { - StopCUNode stop = fun._compunit._stop; - if( stop._inputs.find(fun.ret()) == -1 ) - add(stop).addDef(fun.ret()); - } public <N extends Node> N add( N n ) { return _iter.add(n); } public void addAll( Ary<Node> ary ) { _iter.addAll(ary); } - public void addAll( Node n ) { _iter.add(n); _iter.addAll(n._inputs); } // --------------------------- // Last check for bad programs public CodeGen typeCheck() { return phase(Phase.TypeCheck, _phase==Phase.FinalizeIR, () -> TypeCheck.run(this)); } - public static TypeStruct requiredStruct(Node n) { - // CNC: remove? cleanup? - if( n instanceof NewNode alloc ) return alloc._ts; - if( n instanceof ConFldOffNode off ) return off._ts; - if( n instanceof FldOffNode off && off.in(0)._type instanceof TypeMemPtr ptr ) return ptr._obj; - return null; - } - - // --------------------------- - // TypeCheck queued surviving CLInits during its existing error walk. - public CodeGen lower() { - return phase(Phase.Lower, (_phase==Phase.TypeCheck || _phase==Phase.Serialize), () -> _iter.iterate(this)); - } - // --------------------------- // Build the loop tree; break never-exit loops public CodeGen loopTree() { - return phase(Phase.LoopTree, _phase==Phase.Lower, () -> _start.buildLoopTree(_linker,_stop)); - } - - // --------------------------- - public BAOS _serial; - public void serialize() { - phase(Phase.Serialize, _phase==Phase.TypeCheck, () -> Serialize.serialize(this)); + return phase(Phase.LoopTree, _phase==Phase.TypeCheck, () -> { + // Build the loop tree, fix never-exit loops + _start.buildLoopTree(_start,_stop); + }); } // --------------------------- @@ -536,10 +255,8 @@ public class CodeGen { public Machine _mach; // Chosen calling convention (usually either Win64 or SystemV) public String _callingConv; - public String _cCallingConv; // Callee save registers public RegMask _callerSave; - public RegMask _cCallerSave; // All returns have the following inputs: // 0 - ctrl @@ -554,16 +271,11 @@ public class CodeGen { // Convert to target hardware nodes public CodeGen instSelect( String cpu, String callingConv ) { return instSelect(cpu,callingConv,PORTS); } public CodeGen instSelect( String cpu, String callingConv, String base ) { - return InstSelect.select(this,cpu,callingConv,base); + if( cpu==null ) return this; + return phase(Phase.Select, _phase==Phase.LoopTree, + () -> new InstSelect(this).run(cpu,callingConv,base)); } - // --------------------------- - public void unlink() { - unlink(true); - } - private void unlink(boolean removeImports) { - phase(Phase.Unlink, _phase==Phase.LoopTree, () -> Unlink.run(this,removeImports)); - } // --------------------------- // Control Flow Graph in Reverse Post Order. @@ -571,12 +283,17 @@ public class CodeGen { // Global schedule (code motion) nodes public CodeGen GCM() { - return phase(Phase.Schedule, (_phase==Phase.Unlink || _phase==Phase.Select), () -> GlobalCodeMotion.buildCFG(this)); + return phase(Phase.Schedule, (_phase==Phase.LoopTree || _phase==Phase.Select), () -> { + GlobalCodeMotion.buildCFG(this); + + }); } // --------------------------- // Local (basic block) scheduler phase, a classic list scheduler - public CodeGen localSched() { return phase(Phase.LocalSched, _phase==Phase.Schedule, () -> ListScheduler.sched(this)); } + public CodeGen localSched() { + return phase(Phase.LocalSched, _phase==Phase.Schedule, () -> ListScheduler.sched(this)); + } // --------------------------- @@ -589,10 +306,11 @@ public class CodeGen { }); } - // Human-readable register name - public String reg(Node n) { + // Human readable register name + public String reg(Node n) { return reg(n,null); } + public String reg(Node n, FunNode fun) { if( _phase.ordinal() >= Phase.RegAlloc.ordinal() ) { - String s = _regAlloc.reg(n); + String s = _regAlloc.reg(n,fun); if( s!=null ) return s; } return "N"+ n._nid; @@ -601,154 +319,50 @@ public class CodeGen { // --------------------------- // Encoding - public Encoding _encoding; - public CodeGen encode() { return phase(Phase.Encoding, _phase==Phase.RegAlloc, () -> _encoding = new Encoding(this).encode()); } - - // --------------------------- - // Exporting to external formats - ElfWriter _elf; - public CodeGen exportELF( boolean inMemory, boolean emitEntrySymbol ) { - return phase(Phase.Export, _phase==Phase.Encoding, () -> { - if( inMemory ) new LinkMem(this).link(_encoding); - else _elf = new ElfWriter(this).export(emitEntrySymbol); + public Encoding _encoding; // Encoding object + public void preEncode() { } // overridden by alternative ports + public CodeGen encode() { + return phase(Phase.Encoding, _phase==Phase.RegAlloc, () -> { + _encoding = new Encoding(this); + preEncode(); + _encoding.encode(); }); } - // --------------------------- - - // Search a external path list, each path is recursively searched for .o - // files, which are partially loaded and searched for public symbols. - - // This is a state machine which lazily searches down the set of search - // paths until the requested module is class is found. - - // Map from external Strings to either partially read Simple ElfFile or ExternNode - public final HashMap<String,Object> _externSymbols = new HashMap<>(); - - // State machine elements; the outermost element is _externPaths - private int _extPathIdx; // Search index into the extern path list - private final Ary<File> _files = new Ary<>(File.class); // Files in the current extern path being searched - private int _extFileIdx; // Index into _files - - public ElfReader findExternalSimple( String name ) { - return findExternalSymbol(name) instanceof ElfReader elf ? elf : null; - } - - public ExternNode findExternal( String name ) { - return findExternalSymbol(name) instanceof ExternNode extern ? extern : null; - } - - // Search the search-path for the name; return an ElfReader if found in a - // Simple-made ELF; return an ExternNode if from another ELF, or null if - // not found. - private Object findExternalSymbol( String name ) { - // State Machine! - - while( true ) { - // Check for an immediate hit - switch( _externSymbols.get(name) ) { - - case ExternNode extern: - // Name maps to an ExternNode - return extern; - - case ElfReader elf: - return elf; - - case null: - // Name is unknown. Advance the file-system search, pulling - // out new ELF files from the search directory list and loading - // their published symbols. - - // The last directory was mined out, so get the next search - // directory and find all ELF-like files. - if( _extFileIdx >= _files._len ) { - if( _externPaths==null || _extPathIdx >= _externPaths._len ) - return null; // A true miss in the whole search path - loadExtPath(); // Load next batch file of files - break; - } - // Load and parse an ELF header - ElfReader elf = getElf(_files.at(_extFileIdx++)); - if( elf == null ) break; // Not an ELF - // Load public symbols from the ELF - elf.loadPublicSymbols(); - if( elf._strs==null ) break; // No .simple section with strings - // Put public symbol in global table; first names shadow all others - // Map from symbol string to ELFReader; most lookups - // will miss and no need to unpack ElfReader types - _externSymbols.putIfAbsent(elf._strs[1],elf); - - // Publish definitions only; referenced classes belong to other objects. - for( String fname : elf._units ) - _externSymbols.putIfAbsent(Parser.addClzPrefix(fname.replace('/','.')),elf); - break; - - default: throw Utils.TODO("should not reach here"); - } - } - } - - // Load all the .o files in the next external path. - private void loadExtPath() { - _files.clear(); - _extFileIdx = 0; - String extpath = _externPaths.at(_extPathIdx++); - File f = new File(extpath); - if( !f.isAbsolute() ) - f = new File(_cwd+extpath); - fillExtFiles(f); - } - - // Recursive search (TODO: gzip, archives) and gather all .o files. - private void fillExtFiles(File dir) { - if( dir.isDirectory() ) - for( File f : dir.listFiles() ) - fillExtFiles(f); - else if( dir.getName().endsWith(".o") ) - _files.add(dir); - } - - // Map from object file names to ElfReaders - public final HashMap<String,ElfReader> _elfs = new HashMap<>(); - ElfReader getElf(File f) { - ElfReader elf = _elfs.get(f.getPath()); - if( elf != null ) - return elf; - _elfs.put(f.getPath(), elf = ElfReader.load(f,null)); - return elf; + // Exporting to external formats + public CodeGen exportELF(String fname) throws IOException { + assert _phase==Phase.Encoding; + _phase=Phase.Export; + long t0=System.currentTimeMillis(); + try { + if( fname == null ) new LinkMem(this).link(); // In memory patching + else new ElfFile(this).export(fname); // External ELF file + } finally { _times[Phase.Export.ordinal()]=System.currentTimeMillis()-t0; } + if( _obs!=null ) _obs.phase(Phase.Export.name()); + return this; } // --------------------------- public boolean _asmLittle=true; + SB asm(SB sb) { return ASMPrinter.print(sb,this); } public String asm() { return asm(new SB()).toString(); } - SB asm(SB sb) { - ASMPrinter.print(sb,this); - return sb; - } // Testing shortcuts - public Node ctrl() { return compunit()._stop.ret().ctrl(); } - public Node expr() { return compunit()._stop.ret().expr(); } - public String print() { return compunit()._stop.print(); } + public Node ctrl() { return _stop.ret().ctrl(); } + public Node expr() { return _stop.ret().expr(); } + public String print() { return _stop.print(); } // Debugging helper @Override public String toString() { - if( _stop == null ) - return "No StopNode"; - return IRPrinter.prettyPrint(this); + return _phase.ordinal() > Phase.Schedule.ordinal() + ? IRPrinter._prettyPrint( this ) + : IRPrinter.prettyPrint(this); } // Debugging helper - public Node f(int idx) { - for( CompUnit cu : _compunits.values() ) { - Node n = (cu._stop == null ? _stop : cu._stop).find(idx); - if( n != null ) return n; - } - return null; - } + public Node f(int idx) { return _stop.find(idx); } String printCFG() { @@ -767,4 +381,28 @@ public class CodeGen { } System.out.println(); } + + public void print_as_hex() { + for (byte b : _encoding._bits.toByteArray()) { + System.out.print(String.format("%02X", b)); + } + System.out.println(); + } + + //// Debug purposes for now +// public CodeGen printENCODING() { +// ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); +// +// for(Node bb : CodeGen.CODE._cfg) { +// for(Node n: bb.outs()) { +// if(n instanceof MachNode) { +// ((MachNode) n).encoding(E); +// } +// } +// } +// +// print_as_hex(outputStream); +// // Get the raw bytes from the output stream +// return this; +// } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java deleted file mode 100644 index 128c4136..00000000 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java +++ /dev/null @@ -1,143 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeStruct; -import com.seaofnodes.simple.util.*; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.*; -import static com.seaofnodes.simple.codegen.CodeGen.CODE; - -// A compilation-unit, which is an external reference and source and object -// files. These are mirrored over a file system tree (so form a tree), both -// the source tree and build/object tree. -public class CompUnit { - public final CompUnit _par; // Parent, e.g. A/B - final String _fname; // Full slashed file name starting from module root, e.g. A/B/C - final public String _cname; // Full dotted class name starting from module root, e.g. A.B.C - final String _name; // Base name, e.g. C - final ExternNode _ext; // Found in external libs; e.g. libc - final File _smp; // Simple source file, e.g. module_root/A/B/C.smp - public final File _obj; // ELF output file, e.g. build/A/B/C.o - final public String _src; // Source code, from file or test case - // Original source path from an imported diagnostic location. Does not - // affect discovery/rebuild decisions, which use _smp and _src. - public String _sourceFile; - - public String sourceFile() { - return _smp!=null ? _smp.getAbsolutePath() : _sourceFile; - } - - public TypeStruct _clz; // The one TypeStruct being published - - // _deps records units required for types, code, or class references, - // without requiring their initializers to run. These discovery/rebuild - // dependencies may form a cycle. - Ary<CompUnit> _deps; - - // Per-Compilation-Unit Start/StopNodes, keeping alive all exported Nodes. - // Null means no code loaded (yet). - public StartCUNode _start; - public StopCUNode _stop ; - - - // Discovered source file. Source is loaded if required to parse, and null - // otherwise. - CompUnit( CompUnit par, String fname, String cname, String name ) throws IOException { - _par = par; - _fname= fname; - _cname= cname; - _name = name; - _ext = null; - _smp = new File( CODE. _modDir + "/" + fname + ".smp"); - _obj = new File( CODE._buildDir + "/" + fname + ".o" ); - - boolean mustCompile = _smp.exists() && // Has source to compile - ( !_obj.exists() || // No object file - _smp.lastModified() > _obj.lastModified() || // Out-of-date with source? - checkDependentObjs() ); // Out-of-date with other objs? - _src = mustCompile ? new String(Files.readAllBytes(_smp.toPath())) : null; - } - - // Test case. No source file. Source code passed in as a String. Fake - // class name. - CompUnit( String fname, String src ) { - _par = null; - _name = fname; - _fname= fname; - _cname= fname; - _ext = null; - _smp = null; - _obj = null; - _src = src; - } - - // External CompUnit, pulled in from an external library like libc - CompUnit( ExternNode ext, String symbol ) { - _par = null; - _name = symbol; - _fname= _name; - _cname= null; - _ext = ext; - _smp = null; - _obj = null; - _src = null; - } - - // External Simple object file, or namespace parent, found on an external search path. - CompUnit( File obj, CompUnit par, String fname, String cname, String name ) { - _par = par; - _name = name; - _fname= fname; - _cname= cname; - _ext = null; - _smp = null; - _obj = obj; - _src = null; - } - - // Return true if _obj is out-of-date relative to dependents. - private boolean checkDependentObjs() { - ElfReader elf = CODE.getElf(_obj); - elf.loadPublicSymbols(); - for( String dep : elf._deps ) { - File obj = new File(CODE._buildDir+"/"+dep+".o"); - if( obj.lastModified() > _obj.lastModified() ) // Out-of-date with deps? - return true; - } - return false; - } - - // Add a discovered dependency, for later writing out in an ELF file, ".simple" section - void addDep(CompUnit dep) { - if( _deps == null ) _deps = new Ary<>(CompUnit.class); - _deps.add(dep); - } - - public void addFun(CodeGen code, FunNode fun) { - assert fun.in(1)==_start; - _stop.addDef(fun.ret()); - code.add(_stop); - _stop.addDep(fun); - } - - void setStart(CodeGen code) { - _stop = new StopCUNode().init(); - _start = new StartCUNode(code._start,_stop, Type.BOTTOM, _fname).init(); - code._stop.addDef(_stop); - } - - @Override public String toString() { - String x = "{"+_cname; - if( _smp!=null && _smp.exists()) x += ",hasSMP"; - if( _obj!=null && _obj.exists()) x += ",hasObj"; - if( _src!=null ) x += ",hasSrc"; - if( _ext != null ) x += ",isExt"; - x += "}"; - return x; - } -} diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfExpr.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfExpr.java new file mode 100644 index 00000000..9d9b8a1c --- /dev/null +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfExpr.java @@ -0,0 +1,87 @@ +package com.seaofnodes.simple.codegen; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class ElfExpr { + public static void main(String[] args) { + try (FileOutputStream fos = new FileOutputStream("hello.o")) { + ByteBuffer buffer = ByteBuffer.allocate(512); + buffer.order(ByteOrder.LITTLE_ENDIAN); + + // === ELF HEADER (64 bytes) === + buffer.put(new byte[]{ + 0x7F, 'E', 'L', 'F', // ELF Magic + 2, 1, 1, 0, // 64-bit, little-endian, version + 0, 0, 0, 0, 0, 0, 0, // ABI padding + 2, 0, // Type: Executable (ET_EXEC) + 0x3E, 0x00, // Machine: x86-64 + 1, 0, 0, 0, // ELF version + 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Entry point + 0, 0, 0, 0, 0, 0, 0, 0, // Program header offset + 64, 0, 0, 0, 0, 0, 0, 0, // Section header offset + 0, 0, 0, 0, // Flags + 64, 0, // ELF header size + 0, 0, // Program header entry size + 0, 0, // Number of program header entries + 40, 0, // Section header entry size + 2, 0, // Number of section headers + 1, 0 // Section header string table index + }); + + // === .TEXT SECTION (Executable Code) === + byte[] textSection = { + // sys_write (write(1, message, 13)) + (byte) 0x48, (byte) 0xC7, (byte) 0xC0, 0x01, 0x00, 0x00, 0x00, // mov rax, 1 + (byte) 0x48, (byte) 0xC7, (byte) 0xC7, 0x01, 0x00, 0x00, 0x00, // mov rdi, 1 + (byte) 0x48, (byte) 0xBE, (byte)0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov rsi, message + (byte) 0x48, (byte) 0xC7, (byte) 0xC2, 0x0D, 0x00, 0x00, 0x00, // mov rdx, 13 + (byte) 0x0F, (byte) 0x05, // syscall + + // sys_exit (exit(0)) + (byte) 0x48, (byte) 0xC7, (byte) 0xC0, 0x3C, 0x00, 0x00, 0x00, // mov rax, 60 + (byte) 0x48, (byte) 0x31, (byte) 0xFF, // xor rdi, rdi + (byte) 0x0F, (byte) 0x05 // syscall + }; + + // Append section data + buffer.position(256); + buffer.put(textSection); + + // === STRING MESSAGE (.data Section) === + byte[] message = "Hello, World!\n".getBytes(); + buffer.position(400); + buffer.put(message); + + // === SECTION HEADERS === + buffer.position(64); + + // Null Section (required as first entry) + buffer.put(new byte[40]); + + // .text Section + buffer.putInt(1); // Offset in section string table (".text") + buffer.putInt(1); // Type: SHT_PROGBITS + buffer.putLong(6); // Flags: Executable | Allocatable + buffer.putLong(0); // Virtual address + buffer.putLong(256); // Offset to section data + buffer.putLong(textSection.length); // Section size + buffer.putInt(0); // Link + buffer.putInt(0); // Info + buffer.putLong(4); // Address alignment + buffer.putLong(0); // No extra entries + + // === SECTION HEADER STRING TABLE === + buffer.position(500); + buffer.put(".text".getBytes()); + buffer.put((byte) 0x00); // Null terminator + + fos.write(buffer.array()); + System.out.println("ELF object file 'hello.o' created."); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfFile.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfFile.java new file mode 100644 index 00000000..b6267600 --- /dev/null +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfFile.java @@ -0,0 +1,382 @@ +package com.seaofnodes.simple.codegen; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.Encoding.BAOS; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.Ary; +import java.io.*; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.HashMap; +import java.util.Map; + +public class ElfFile { + + final CodeGen _code; + Ary<Section> _sections = new Ary<>(Section.class); + + ElfFile( CodeGen code ) { _code = code; } + + public static int writeCString(DataSection strtab, String name) { + // place name in the string table + int pos = strtab._contents.size(); + byte[] name_bytes = name.getBytes(); + strtab._contents.write(name_bytes, 0, name_bytes.length); + // the strings are null terminated + strtab._contents.write(0); + return pos; + } + + public static void write4( ByteArrayOutputStream bits, int op ) { + bits.write(op ); + bits.write(op>> 8); + bits.write(op>>16); + bits.write(op>>24); + } + public static void write8( ByteArrayOutputStream bits, long i64 ) { + write4(bits, (int) i64 ); + write4(bits, (int)(i64>>32)); + } + + public static final int SYMBOL_SIZE = 4+1+1+2+8+8; + + public static final int SYM_BIND_LOCAL = 0; + public static final int SYM_BIND_GLOBAL = 1; + public static final int SYM_BIND_WEAK = 2; + + public static final int SYM_TYPE_NOTYPE = 0; + public static final int SYM_TYPE_OBJECT = 1; + public static final int SYM_TYPE_FUNC = 2; + public static final int SYM_TYPE_SECTION = 3; + + public static class Symbol { + String _name; + int _name_pos; + + // index in the symbol table + int _index; + + // which section is it in + int _parent; + + // depends on the symbol type + // TODO + int _value, _size; + + // top 4bits are the "bind", bottom 4 are "type" + int _info; + + // bind 1011 + // 00001111 + // 10110000 + + Symbol(String name, int parent, int bind, int type) { + _name = name; + _parent = parent; + _info = (bind << 4) | (type & 0xF); + } + + void writeHeader(ByteBuffer out) { + out.putInt(_name_pos); // name + out.put((byte) _info); // info + // default visibility + out.put((byte) 0); // other + out.putShort((short) _parent); // shndx + out.putLong(_value); // value + out.putLong(_size); // size + } + } + + public static final int SHF_WRITE = 1; + public static final int SHF_ALLOC = 2; + public static final int SHF_EXECINSTR = 4; + public static final int SHF_STRINGS = 32; + public static final int SHF_INFO_LINK = 64; + + public static final int SECTION_HDR_SIZE = 64; + abstract public static class Section { + String _name; + int _index; + + int _type, _flags; + int _link, _info; + + // where's the name in the string table + int _name_pos; + int _file_offset; + + Section(String name, int type) { + _name = name; + _type = type; + } + + void writeHeader(ByteBuffer out) { + out.putInt(_name_pos); // name + out.putInt(_type); // type + out.putLong(_flags); // flags + out.putLong(0); // addr + out.putLong(_file_offset); // offset + out.putLong(size()); // size + out.putInt(_link); // link + out.putInt(_info); // info + out.putLong(16); // addralign + if (_type == 2) { + out.putLong(SYMBOL_SIZE);// entsize + } else if (_type == 4) { + out.putLong(24); // entsize + } else { + out.putLong(0); // entsize + } + } + + abstract void write(ByteBuffer out); + abstract int size(); + } + + public static class SymbolSection extends Section { + Ary<Symbol> _symbols = new Ary<>(Symbol.class); + Ary<Symbol> _loc = new Ary<>(Symbol.class); + + SymbolSection(String name, int type) { + super(name, type); + } + + @Override + void writeHeader(ByteBuffer out) { + // points to string table section + _link = 1; + + // number of non-local symbols + _info = _loc.len() + 1; + + super.writeHeader(out); + } + + void push(Symbol s) { + if ((s._info >> 4) != SYM_BIND_LOCAL) { + _symbols.push(s); + } else { + _loc.push(s); + } + } + + @Override + void write(ByteBuffer out) { + // Index 0 both designates the first entry in the table and serves as the undefined symbol index + for( int i = 0; i < SYMBOL_SIZE/4; i++ ) { + out.putInt(0); + } + // index is already set + for( Symbol s : _loc ) { + s.writeHeader(out); + } + for( Symbol s : _symbols ) { + s.writeHeader(out); + } + } + + @Override + int size() { + return (1 + _symbols.len() + _loc.len()) * SYMBOL_SIZE; + } + } + + public static class DataSection extends Section { + ByteArrayOutputStream _contents; + + DataSection(String name, int type) { + super(name, type); + _contents = new ByteArrayOutputStream(); + } + + DataSection(String name, int type, ByteArrayOutputStream contents) { + super(name, type); + _contents = contents; + } + + @Override + void write(ByteBuffer out) { + out.put(_contents.toByteArray()); + } + + @Override + int size() { + return _contents.size(); + } + } + + private void pushSection(Section s) { + _sections.push(s); + // ELF is effectively "1-based", due to the NULL section + s._index = _sections._len; + } + + /* creates function and stores where it starts*/ + private final HashMap<TypeFunPtr,Symbol> _funcs = new HashMap<>(); + private void encodeFunctions(SymbolSection symbols, DataSection text) { + for( int i=0; i<_code._cfg._len; i++ ) { + if( !(_code._cfg.at(i) instanceof FunNode fun) ) continue; + // skip until the function ends + while( !(_code._cfg.at(i) instanceof ReturnNode ret) ) + i++; + int end = _code._encoding._opStart[ret._nid] + _code._encoding._opLen[ret._nid]; + + if( fun._name==null ) continue; // Anonymous addresses are relocated within .text. + Symbol func = new Symbol(fun._name, text._index, SYM_BIND_GLOBAL, SYM_TYPE_FUNC); + func._value = _code._encoding._opStart[fun._nid]; + func._size = end - func._value; + symbols.push(func); + _funcs.put(fun.sig(), func); + } + } + + public void export(String fname) throws IOException { + DataSection strtab = new DataSection(".strtab", 3 /* SHT_SYMTAB */); + // first byte is reserved for an empty string + strtab._contents.write(0); + pushSection(strtab); + + // place all the symbols + SymbolSection symbols = new SymbolSection(".symtab", 2 /* SHT_STRTAB */); + pushSection(symbols); + + // zero flag by default + // we've already constructed this entire section in the encoding phase + DataSection text = new DataSection(".text", 1 /* SHT_PROGBITS */, _code._encoding._bits); + text._flags = SHF_ALLOC | SHF_WRITE | SHF_EXECINSTR; + pushSection(text); + + // Build and write constant pool + BAOS cpool = new BAOS(); + Encoding enc = _code._encoding; + enc.writeConstantPool(cpool,false); + DataSection rdata = new DataSection(".rodata", 1 /* SHT_PROGBITS */, cpool); + rdata._flags = SHF_ALLOC; + pushSection(rdata); + + // populate function symbols + encodeFunctions(symbols, text); + + int idx = 1; + for( Section s : _sections ) { + Symbol sym = new Symbol(s._name, idx++, SYM_BIND_LOCAL, SYM_TYPE_SECTION); + // we can reuse the same name pos from the actual section + sym._name_pos = s._name_pos; + sym._size = s.size(); + symbols.push(sym); + } + + // calculate local index + int num = 1; + for( Symbol s : symbols._loc ) + s._index = num++; + // extra space for .rela.text + int start_global = num+1; // Add one to skip the final .rela.text local symbol + for( Symbol a: symbols._symbols ) + a._index = start_global++; + + // create .text relocations + DataSection text_rela = new DataSection(".rela.text", 4 /* SHT_RELA */); + for( Node n : enc._externals.keySet()) { + int nid = n._nid; + String extern = enc._externals.get(n); + + Symbol sym = new Symbol(extern, 0, SYM_BIND_GLOBAL, SYM_TYPE_NOTYPE); + sym._index = start_global++; + symbols.push(sym); + + int offset = enc._opStart[nid] + enc._opLen[nid] - 4; + + // u64 offset + write8(text_rela._contents, offset); + // u64 info + write8(text_rela._contents, ((long)sym._index << 32L) | 4L /* PLT32 */); + // i64 addend + write8(text_rela._contents, -4); + } + + // Write relocations for the constant pool + for( Encoding.Relo relo : enc._bigCons.values() ) { + Symbol glob = new Symbol("CPOOL$"+start_global, rdata._index, SYM_BIND_GLOBAL, SYM_TYPE_FUNC); + glob._value = relo._target; + glob._size = relo._t.size(); + glob._index = start_global++; + symbols.push(glob); + write8(text_rela._contents, relo._opStart+relo._off); + write8(text_rela._contents, ((long)glob._index << 32L) | relo._elf ); + write8(text_rela._contents, -4); + } + + text_rela._flags = SHF_INFO_LINK; + text_rela._link = 2; + text_rela._info = text._index; + pushSection(text_rela); + + // Final .rela.text symbol + Symbol sym = new Symbol(text_rela._name, num++, SYM_BIND_LOCAL, SYM_TYPE_SECTION); + sym._name_pos = text_rela._name_pos; + sym._size = text_rela.size(); + symbols.push(sym); + + // populate string table + for( Section s : _sections ) { s._name_pos = writeCString(strtab, s._name); } + for( Symbol s : symbols._symbols ) { s._name_pos = writeCString(strtab, s._name); } + for( Symbol s : symbols._loc ) { s._name_pos = writeCString(strtab, s._name); } + + int size = 64; // ELF header + // size of all section data + for( Section s : _sections ) { + s._file_offset = size; + size += s.size(); + } + // headers go at the very end + int shdr_offset = size; + // there's a "null" section at the start of the section headers + size += SECTION_HDR_SIZE*(1 + _sections.len()); + + // System.out.printf("Hello %d, %d, %s\n", size, shdr_offset); + ByteBuffer out = ByteBuffer.allocate(size); + out.order(ByteOrder.LITTLE_ENDIAN); + + // ELF header + out.put(new byte[]{ 0x7F, 'E', 'L', 'F', 2, 1, 1, 0 }); + out.put(new byte[8]); + out.putShort((short)1 /* ET_REL */); // type + out.putShort((short)62 /* AMD64 */); // machine + out.putInt(1); // version + out.putLong(0); // entry + out.putLong(0); // phoff + out.putLong(shdr_offset); // shoff + out.putInt(0); // flags + out.putShort((short)64); // ehsize + out.putShort((short)0); // phentsize + out.putShort((short)0); // phnum + out.putShort((short)64); // shentsize + out.putShort((short) (1 + _sections.len())); // shnum + out.putShort((short)1); // shstrndx + + // raw section data + for( Section s : _sections ) { + s.write(out); + } + + // "null" section + for( int i = 0; i < 16; i++ ) { + out.putInt(0); + } + + for( Section s : _sections ) { + s.writeHeader(out); + } + assert out.position() == size; + + File file = new File(fname); + if( file.getParentFile()!=null ) + file.getParentFile().mkdirs(); + BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file)); + bos.write(out.array()); + bos.close(); + } + +} diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfReader.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfReader.java deleted file mode 100644 index 10720300..00000000 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfReader.java +++ /dev/null @@ -1,236 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.type.TypeMemPtr; -import com.seaofnodes.simple.type.TypeStruct; -import com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.util.BAOS; -import com.seaofnodes.simple.util.Utils; -import java.io.*; -import java.nio.file.Files; -import java.util.HashMap; - -public class ElfReader { - - public final File _file; - public final byte[] _buf; - int _x; // Cursor into buffer - // ELF Header - public final ElfHeader _header; - // All sections - public final Section[] _sections; - // Simple section, if available - public final Section _simple; - // Byte array in/out stream - used as a reader - public final BAOS _bais; - // Compilation unit; written into FunNodes - public final CompUnit _compunit; - - // Strings from the Simple section, either small public set or whole set - public String[] _strs; - // Dependent file names - public String[] _deps; - // Compilation units defined by this object, independent of init analysis. - public String[] _units; - // Only published symbol this file - public TypeStruct _clz; - // Canonical named types, distinct from optimized same-named graph states. - public TypeStruct[] _pubTypes; - // Nodes read in this Elf - public Ary<Node> _nodes; - - // Load an Elf file (mostly lazily) - public static ElfReader load( File f, CompUnit cu ) { - try { return new ElfReader(f,cu); } - catch( IOException ioe ) { return null; } - } - - // Load the Simple section from the ELF file - private ElfReader( File f, CompUnit cu ) throws IOException { - _file = f; - _compunit = cu; - _buf = Files.readAllBytes(f.toPath()); - _header = new ElfHeader(); - // No attempt is made to parse the program header table. - _sections = new Section[_header._shnum]; - for( int i=0; i<_header._shnum; i++ ) - _sections[i] = new Section(i); - - // Load section names - Section secstr = _sections[_header._shstrndx]; - for( Section sec : _sections ) - if( sec._type != 0 ) - sec._name = secstr.str(sec._noff); - - Section simple = null; - for( Section sec : _sections ) - if( ".simple".equals(sec._name) ) - simple = sec; - _simple = simple; - _bais = new BAOS(_buf, (int)_simple._offset); - } - - // For Serialize self-test - ElfReader( BAOS bais ) { - _file = null; - _compunit = null; - _buf = bais.buf(); - _header = null; - _sections = null; - _simple = null; - _bais = bais; - } - - // Load the public symbols and dependent objs, but not the whole IR - void loadPublicSymbols() { - // Check already loaded and cached - if( _strs == null && _simple != null ) - // Read only the published public strings, for speed purposes when - // scanning a large count of ELF files but getting hits in only a - // few of them. i.e. optimizing (prematurely?) for the traditional - // case of linking a small file against a large library - Serialize.read_public_strs(this); - } - - // Loads the entire IR - public TypeStruct loadSimple(CodeGen code) { - Serialize.readAll(code, this, code._aliases, code._fidxs, code._rpcs); - return _clz; - } - - // Loads strings, mappings, and types, but not Nodes. Used after the - // public-string scan finds a matching Simple ELF, so parsing can resolve - // imported field/array aliases before the full graph is linked in later. - public TypeStruct loadPublicTypes(CodeGen code) { - Serialize.readTypes(code, this, code._aliases, code._fidxs, code._rpcs); - return _clz; - } - - int u1() { return _buf[_x++]&0xFF; } - int magic() { // Big endian - return (u1() << 24) | (u1() << 16) | (u1() << 8) | u1(); - } - int u2() { return u1() | (u1()<<8); } // Little endian - int u4() { // Little endian - return u1() | (u1() << 8) | (u1() << 16) | (u1() << 24); - } - long u8() { return u4() | ((long)u4()<<32); } - void skip(int skip) { _x += skip; } - - - public class ElfHeader { - final int _magic; - final boolean _format32; // Format32 or format64 - final byte _osabi; - final boolean _little; // True if little endian, false if big endian - final char _type; - final char _machine; - final long _entry; // Process entry point - final long _phoff; // Program header table offset, from file start - final long _shoff; // Section header table offset, from file start - final int _flags; // Flags - final int _phnum; // Number of program header entries - final int _shentsize; - final int _shnum; // Number of section header entries - final int _shstrndx; // Section header index contains section names - - - ElfHeader() throws IOException { - _x = 0; - if( (_magic = magic()) != 0x7F454C46 ) bad("magic number",_magic); - byte EI_CLASS = (byte)u1(); - if( EI_CLASS != 1 && EI_CLASS != 2 ) bad("EI_CLASS",EI_CLASS); - _format32 = (EI_CLASS==1); - byte EI_DATA = (byte)u1(); - if( EI_DATA != 1 && EI_DATA != 2 ) bad("EI_DATA",EI_DATA); - _little = EI_DATA == 1; - if( u1() != 1 ) bad("version",1); - if( (_osabi=(byte)u1()) != 0 ) bad("EI_VERSION",_osabi); - byte abiversion = (byte)u1(); - if( abiversion != 0 ) bad("EI_ABIVERSION",abiversion); - skip(7); // Pad - - // Endian matters now - if( !_little ) throw new IOException("TODO: Big endian"); - if( (_type = (char)u2()) != 1 ) bad("ET_REL",_type); - if( (_machine = (char)u2()) != 0x3E ) bad("AMD x86-64",_machine); - int ver = u4(); - if( ver != 1 ) bad("Version",ver); - // Format32 matters now - if( _format32 ) bad("Format",1); - _entry = u8(); // Process entry point - _phoff = u8(); // Program header table offset - _shoff = u8(); // Section header table offset - _flags = u4(); // Flags - int ehsize = u2(); - if( ehsize != 0x40 ) bad("EHSIZE",ehsize); - int phentsize = u2(); - if( phentsize != 0x38 && phentsize != 0 ) bad("PHENTSIZE",phentsize); - _phnum = u2(); - _shentsize = u2(); - if( _shentsize != 0x40 ) bad("SHENTSIZE",_shentsize); - _shnum = u2(); - // String section - _shstrndx = u2(); - assert _x == ehsize; - } - } - - public class Section { - final int _noff; // Offset in string section for name - final int _type; // Section type - final long _flags; - final long _addr; // Offset in virtual mem, if loaded - final long _offset; // Offset in file - final long _size; // Size in bytes - final int _link; // Associated section index, varies - final int _info; // Varies - final byte _align; // Log2 alignment - final int _esize; // Element size, if non-zero - String _name; - - Section(int sidx) throws IOException { - _x = (int)(_header._shoff + (sidx * _header._shentsize)); - _noff = u4(); - _type = u4(); - _flags = u8(); - _addr = u8(); - _offset= u8(); - _size = u8(); - _link = u4(); - _info = u4(); - long align = u8(); - if( align==0 ) align=1; - _align = (byte)Long.numberOfTrailingZeros(align); - if( (1L << _align ) != align ) - throw new IOException("Bad section alignment: "+align); - _esize = (int)u8(); - } - - String str(int off) { - assert _type == 3/*SHT_STRTAB*/; // String table section header - int start = (int)(_offset+off); - int x = start; - while( _buf[x++] !=0 ) ; - return new String(_buf,start,x-start-1); - } - } - - private static void bad(String msg, int val) throws IOException { - throw new IOException("Bad "+msg+": "+val); - } - - - - //public TypeMemPtr lookup( String name ) { - // if( _simple == null ) return null; - // BAOS baos = new BAOS(_buf, (int)_simple._offset); - // Ary<String> strs = Serialize.read_strs(baos); - // - // - // - // Ary<Node> nodes = Serialize.read_types(baos); - // - // throw Utils.TODO("Elf load symbol "+name); - //} -} diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java deleted file mode 100644 index 5a280723..00000000 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java +++ /dev/null @@ -1,467 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeFunPtr; -import com.seaofnodes.simple.type.TypeStruct; -import com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.util.BAOS; -import java.io.*; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.HashMap; - -public class ElfWriter { - - final CodeGen _code; - Ary<Section> _sections; - StringSection _strtab; - - ElfWriter( CodeGen code ) { - _code = code; - } - - // ------------------------------------------------------------------------ - abstract public class Section { - static final int SHT_NULL = 0; - static final int SHT_PROGBITS= 1; - static final int SHT_SYMTAB = 2; - static final int SHT_STRTAB = 3; - static final int SHT_RELA = 4; - static final int SHT_SIMPLE = 10; // Simple section - - final String _name; - final int _type, _entsize, _index, _name_pos, _flags; - int _link, _info; - int _file_offset; - - Section(String name, int type, int entsize, int flags) { - _name = name; - _type = type; - _entsize = entsize; - _flags = flags; - _index = _sections._len; - _sections.push(this); - _name_pos = name.isEmpty() - ? 0 // Null section - : _strtab == null - ? 1 // StringSection - // All the other sections - : _strtab.writeCString(name); - } - - void writeHeader(ByteBuffer out) { - out.putInt(_name_pos); // name - out.putInt(_type); // type - out.putLong(_flags); // flags - out.putLong(0); // addr - out.putLong(_file_offset); // offset - out.putLong(size()); // size - out.putInt(_link); // link - out.putInt(_info); // info - out.putLong(16); // addralign - out.putLong(_entsize); // Fixed-size element size - } - - abstract void write(ByteBuffer out); - abstract int size(); - } - - // ------------------------------------------------------------------------ - // Generic data sections; carries about a BAOS for bits - public class DataSection extends Section { - static final int SHF_WRITE = 1; - static final int SHF_ALLOC = 2; - static final int SHF_EXECINSTR = 4; - static final int SHF_INFO_LINK = 64; - - final BAOS _contents; - - DataSection( String name, int type, int flags, int entsize ) { this(name, type, flags, entsize, new BAOS() ); } - DataSection( String name, int type, BAOS contents, int flags ) { this(name, type, flags, 0, contents ); } - private DataSection( String name, int type, int flags, int entsize, BAOS contents ) { - super(name, type, entsize, flags); - _contents = contents; - } - - @Override void write(ByteBuffer out) { out.put(_contents.toByteArray()); } - @Override int size() { return _contents.size(); } - public void write2( int op ) { - _contents.write(op ); - _contents.write(op>> 8); - } - public void write4( int op ) { - _contents.write(op ); - _contents.write(op>> 8); - _contents.write(op>>16); - _contents.write(op>>24); - } - public void write8( long i64 ) { - write4( (int) i64 ); - write4( (int)(i64>>32)); - } - - } - - // ------------------------------------------------------------------------ - // Section to collect and de-dup strings - public class StringSection extends DataSection { - final HashMap<String,Integer> _dedup = new HashMap<>(); - - StringSection() { - super(".strtab",SHT_STRTAB, 0, 0 ); - int emptyoff = writeCString(""); // Always empty string at offset 0 - assert emptyoff == 0; - int namepos = writeCString(".strtab"); - assert namepos == _name_pos; - } - - public int writeCString(String name) { - if( name==null ) return 0; // Anonymous functions have no name - Integer ii = _dedup.get(name); - if( ii != null ) return ii; - // place name in the string table - int pos = _contents.size(); - _dedup.put(name,pos); - byte[] name_bytes = name.getBytes(); - _contents.write(name_bytes); - // the strings are null terminated - _contents.write(0); - return pos; - } - } - - // ------------------------------------------------------------------------ - // The relocations; 3 words per relocation - public class ReloSection extends DataSection { - ReloSection( int info ) { - 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(addend); - } - } - - - // ------------------------------------------------------------------------ - // Symbols section; emit the symbols into the data section directly - public class SymbolSection extends DataSection { - public static final int SYMBOL_SIZE = 4+1+1+2+8+8; - final BAOS _globals; - final int _nlocals; - - SymbolSection(int nlocals) { - super(".symtab", SHT_SYMTAB, 0/*flags*/, SYMBOL_SIZE); - _globals = new BAOS(); - _nlocals = nlocals; - } - - // Global symbol index - int gidx() { return _nlocals+(_globals.size()/SYMBOL_SIZE); } - - public int symbol( String name, int shndx, int bind, int type, long value, long size ) { - int gidx = gidx(); // Global index before I write to _globals - BAOS baos = bind==SYM_BIND_LOCAL ? _contents : _globals; - write4(baos,_strtab.writeCString(name)); - int info = (bind << 4) | (type & 0xF); - baos.write(info); - baos.write(0); // other/visibility - write2(baos,shndx); // section index - write8(baos,value); - write8(baos,size); - return bind==SYM_BIND_LOCAL ? -99 : gidx; - } - - public int symbol( String name, int shndx, int bind, int type ) { - return symbol(name,shndx,bind,type,0,0); - } - - // creates function and stores where it starts. - // Filters to just this class.obj file. - void encodeFunctions(StopNode stop, Encoding enc, int text_idx, boolean emitEntrySymbol) { - for( Node stopcu : stop._inputs ) { - for( Node ret : stopcu._inputs ) { - FunNode fun = ((ReturnNode)ret).fun(); - if( !_code.owns(fun) ) - continue; - int end = enc.opStart(fun.ret()) + enc.opLen(fun.ret()); - long value = enc.opStart(fun); - long size = end - value; - if( fun._name != null ) { // Anonymous functions have no name - // The C runtime owns `main` and adapts argc/argv into - // a normal Simple array. Keep the Simple function on - // its native ABI under a private entry symbol. - String name = emitEntrySymbol && "main".equals(fun._name) - ? "simple_main" - : fun._name; - symbol(name, text_idx, SYM_BIND_GLOBAL, SYM_TYPE_FUNC, value, size); - } - } - } - } - - // Insert a default-main for top-level compilations, if the top-level - // does not declare a main. - void symbolEntryClinit(int text_idx, Encoding enc) { - FunNode clinit = null; - String entryName = _code.entryClinitName(); - for( FunNode fun : _code._linker ) - if( fun != null && _code.owns(fun) ) { - if( "main".equals(fun._name) ) - return; // encodeFunctions exported this as simple_main - if( entryName.equals(fun._name) ) - clinit = fun; // Keep top-level entry, if we don't find an explicit main - } - // No top-level main, so the top-level clinit becomes a default main - if( clinit != null ) { - assert enc.opStart(clinit) == 0; - symbol("simple_main",text_idx, SYM_BIND_GLOBAL, SYM_TYPE_FUNC, enc.opStart(clinit), 0); - return; - } - throw new IllegalStateException("Missing entry <clinit> "+entryName); - } - - static void write2( BAOS bits, int op ) { - bits.write(op ); - bits.write(op>> 8); - } - static void write4( BAOS bits, int op ) { - bits.write(op ); - bits.write(op>> 8); - bits.write(op>>16); - bits.write(op>>24); - } - static void write8( BAOS bits, long i64 ) { - write4(bits, (int) i64 ); - write4(bits, (int)(i64>>32)); - } - - @Override - void writeHeader(ByteBuffer out) { - // points to string table section - _link = _strtab._index; - _info = _nlocals; - super.writeHeader(out); - } - - @Override void write(ByteBuffer out) { - out.put(_contents.toByteArray()); - out.put(_globals .toByteArray()); - } - @Override int size() { - return _contents.size() + _globals.size(); - } - } - - public static final int SYM_BIND_LOCAL = 0; - public static final int SYM_BIND_GLOBAL = 1; - public static final int SYM_BIND_WEAK = 2; - - public static final int SYM_TYPE_NOTYPE = 0; - public static final int SYM_TYPE_OBJECT = 1; - public static final int SYM_TYPE_FUNC = 2; - public static final int SYM_TYPE_SECTION = 3; - - public static final int SECTION_HDR_SIZE = 64; - - // ------------------------------------------------------------------------ - public class SimpleSection extends DataSection { - SimpleSection(BAOS serial) { super(".simple", SHT_SIMPLE, serial, 0); } - } - - - // ------------------------------------------------------------------------ - - // Native code can live above 4G, outside our four-byte function pointers. - // Give address-taken natives a low entry which tail-jumps through the PLT. - // A weak, stable name makes references from separate objects agree on one - // entry. Do not patch references straight to the local copy: that would - // bypass the linker's choice and break pointer equality across objects. - private int nativeEntry(String name, HashMap<String,Integer> entries, - DataSection text, SymbolSection symbols, ReloSection relos) { - Integer old = entries.get(name); - if( old!=null ) return old; - int offset = text.size(); - text._contents.write(0xE9); // jmp rel32; preserve arguments and return address - text.write4(0); - int target = symbols.symbol(name,0,SYM_BIND_GLOBAL,SYM_TYPE_FUNC); - relos.writeReloOffPlus(offset+1,target,(byte)4/*PLT32*/); - int entry = symbols.symbol("__simple_native$"+name,text._index, - SYM_BIND_WEAK,SYM_TYPE_FUNC,offset,5); - entries.put(name,entry); - return entry; - } - - public ElfWriter export(boolean emitEntrySymbol) { - if( _code._serial==null ) - throw new IllegalStateException("ELF export needs ideal IR: request Export from the driver, or Serialize before Lower"); - // Sections are created in the order they are emitted. - _sections = new Ary<>(Section.class); - - // Null section - Section nullSection = new DataSection("",Section.SHT_NULL,0,0); - - // String section next; written out early and available to record/dedup - // strings for all remaining sections - _strtab = new StringSection(); - - // place all the symbols - 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 - Encoding enc = _code._encoding; - // Append ELF-only thunks to a copy. Export must not change instruction - // offsets or the image used by the in-memory linker and emulators. - BAOS textBits = new BAOS(enc._bits.toByteArray(),enc._bits.size()); - DataSection text = new DataSection(".text" , Section.SHT_PROGBITS, textBits, DataSection.SHF_ALLOC | DataSection.SHF_WRITE | DataSection.SHF_EXECINSTR ); - // Constant pool - DataSection rodata = new DataSection(".rodata", Section.SHT_PROGBITS, enc._cpool, DataSection.SHF_ALLOC ); - // Static/global pool - DataSection rwdata = new DataSection(".data" , Section.SHT_PROGBITS, enc._sdata, DataSection.SHF_ALLOC | DataSection.SHF_WRITE ); - - - // populate function symbols - symbols.encodeFunctions(_code._stop, _code._encoding, text._index, emitEntrySymbol); - // Export the source compilation unit <clinit> as the C runtime "main". - // Encoding keeps this function at text offset zero. - if( emitEntrySymbol ) - symbols.symbolEntryClinit(text._index,enc); - - // create external .text relocations - ReloSection relocations = new ReloSection(text._index); - HashMap<String,Integer> nativeEntries = new HashMap<>(); - - for( Node n : enc._externals.keySet()) { - String extern = enc._externals.get(n); - boolean nativeAddress = n instanceof FunPtrNode && - _code.externFunc(((TypeFunPtr)n._type).fidx())!=null; - int symidx = nativeAddress - ? nativeEntry(extern,nativeEntries,text,symbols,relocations) - : symbols.symbol(extern,0,SYM_BIND_GLOBAL,SYM_TYPE_FUNC); - int offset = enc.opStart(n) + enc.opLen(n) - 4; - // Address materialization and static fields name the same thunk; - // direct calls still go straight to the native symbol's PLT entry. - relocations.writeReloOffPlus(offset,symidx,(byte)(nativeAddress ? 2/*PC32*/ : 4/*PLT32*/)); - } - - // Native ELF output is AMD64. Data addresses use PC32, not PLT calls. - 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*/); - } - - // 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() ) { - 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()); - String nativeName=relo.value() instanceof TypeFunPtr fp ? _code.externFunc(fp.fidx()) : null; - Integer sym=nativeName==null ? dataSymbols.get(name) - : nativeEntry(nativeName,nativeEntries,text,symbols,relocations); - if( sym==null ) { - sym=symbols.symbol(name,0,SYM_BIND_GLOBAL,SYM_TYPE_NOTYPE); - dataSymbols.put(name,sym); - } - // 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 - SimpleSection simple = new SimpleSection(_code._serial); - - // Write the local section header symbols - assert nlocals == 1+_sections._len; - symbols.symbol("",0,0,0); // Null symbol at index 0 - for( Section s : _sections ) - symbols.symbol(s._name, s._index, SYM_BIND_LOCAL, SYM_TYPE_SECTION, 0, s.size()); - - int size = 64; // ELF header size - // size of all section data - for( Section s : _sections ) { - s._file_offset = size; - size += s.size(); - } - // headers go at the very end - int shdr_offset = size; - size += SECTION_HDR_SIZE*_sections.len(); - - ByteBuffer out = ByteBuffer.allocate(size); - out.order(ByteOrder.LITTLE_ENDIAN); - - // ELF header - out.put(new byte[]{ 0x7F, 'E', 'L', 'F', 2, 1, 1, 0 }); - out.put(new byte[8]); - out.putShort((short)1 /* ET_REL */); // type - out.putShort((short)62 /* AMD64 */); // machine - out.putInt(1); // version - out.putLong(0); // entry - out.putLong(0); // phoff - out.putLong(shdr_offset); // shoff - out.putInt(0); // flags - out.putShort((short)64); // ehsize - out.putShort((short)0); // phentsize - out.putShort((short)0); // phnum - out.putShort((short)64); // shentsize - out.putShort((short) _sections.len()); // shnum - out.putShort((short) _strtab._index); // shstrndx - - // raw section data - for( Section s : _sections ) - s.write(out); - - // Section headers - for( Section s : _sections ) - s.writeHeader(out); - - assert out.position() == size; - - String objName = _code._buildDir + "/" + _code._srcName + ".o"; - File file = new File(objName); - if( file.getParentFile()!=null ) - file.getParentFile().mkdirs(); - try { - BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file)); - bos.write(out.array()); - bos.close(); - } catch( IOException ioe ) { - // Caller does not want to deal, but it is a failed compilation in any case - throw new RuntimeException(ioe); - } - // Reset for next ELF - _sections = null; - _strtab = null; - return this; - } - -} 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 06bf14f5..019e3892 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Encoding.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Encoding.java @@ -1,10 +1,12 @@ package com.seaofnodes.simple.codegen; -import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.print.IRPrinter; import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.*; +import com.seaofnodes.simple.util.Ary; +import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.util.Utils; +import java.io.ByteArrayOutputStream; import java.util.*; @@ -22,14 +24,11 @@ public class Encoding implements com.seaofnodes.isa.CodeSink { // Top-level program graph structure public final CodeGen _code; - // CFG Nodes in RPO order, per-encoding. - public Ary<CFGNode> _cfg; - // Instruction bytes. The registers are encoded already. Relocatable data // is in a fixed format depending on the kind of relocation. // - RIP-relative offsets into the same chunk are just encoded with their - // chunk relative offsets; no relocation info is required. + // check relative offsets; no relocation info is required. // - Fixed address targets in the same encoding will be correct for a zero // base; moving the entire encoding requires adding the new base address. @@ -37,12 +36,17 @@ public class Encoding implements com.seaofnodes.isa.CodeSink { // - RIP-relative to external chunks have a zero offset; the matching // relocation info will be used to patch the correct value. - public final BAOS _bits = new BAOS(); // Instructions / encodings - 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<>(); + public static class BAOS extends ByteArrayOutputStream { + public byte[] buf() { return buf; } + void set( byte[] buf0, int count0 ) { buf=buf0; count=count0; } + }; + final public BAOS _bits = new BAOS(); + + public int [] _opStart; // Start of opcodes, by _nid + public byte[] _opLen; // Length of opcodes, by _nid + + // Function headers now padded when printing + public boolean _padFunHeads; // Big Constant relocation info. public static class Relo { @@ -52,28 +56,17 @@ public class Encoding implements com.seaofnodes.isa.CodeSink { public final int _structSize; // Capture layout during encoding, not printing public final byte _off; // Offset from start of opcode public final byte _elf; // ELF relocation type, e.g. 2/PC32 - public int _target; // Where constant is finally placed - public int _opStart; // Opcode start + public int _target; // Where constant is finally placed + public int _opStart; // Opcode start Relo( Node op, Type t, byte off, byte elf ) { _op=op; _t=t; _off=off; _elf=elf; _align = t.alignment(); _structSize = t instanceof TypeStruct ts ? ts.size() : 0; - _readOnly = readOnly(t); - } - public boolean readOnly() { return _readOnly; } - private final boolean _readOnly; - private static boolean readOnly(Type t) { - // Class initializers still write their fields, even when every - // final value is constant. Their storage must remain writable. - return !(t instanceof TypeStruct ts) || - (!Parser.startsClzPrefix(ts._name) && ts.isConstant()); } } + public final HashMap<Node,Relo> _bigCons = new HashMap<>(); - Encoding( CodeGen code ) { - _code = code; - _data = new StaticData(this); - } + Encoding( CodeGen code ) { _code = code; } // Shortcut to the defining register public short reg(Node n) { @@ -114,6 +107,14 @@ public class Encoding implements com.seaofnodes.isa.CodeSink { } // Convenience for writing log-N + static void addN( int log, Type t, BAOS bits ) { + long x = t instanceof TypeInteger ti + ? ti.value() + : log==3 + ? Double.doubleToRawLongBits( ((TypeFloat)t).value()) + : Float.floatToRawIntBits((float)((TypeFloat)t).value()); + addN(log,x,bits); + } static void addN( int log, long x, BAOS bits ) { for( int i=0; i < 1<<log; i++ ) { bits.write((int)x); @@ -126,95 +127,52 @@ public class Encoding implements com.seaofnodes.isa.CodeSink { public Encoding add4( int op ) { addN(2,op,_bits); return this; } public Encoding add8(long op ) { addN(3,op,_bits); return this; } - private final static int SENTINEL = -1; - private final IntHashMap _opStart = new IntHashMap(SENTINEL); - private final IntHashMap _opLen = new IntHashMap(SENTINEL); - - - // Read op start for n - public int opStart(Node n) { return _opStart.get(n._nid); } - // Read op length for n - public byte opLen (Node n) { return (byte)_opLen.get(n._nid); } - - // Set op start for n - public void opStart(Node n, int off) { - assert off != SENTINEL; - _opStart.put(n._nid,off); - } - - // Set op start for n - public void opStartAdd(Node n, int off) { - if( off==0 ) return; - int old = _opStart.get(n._nid); - assert old != SENTINEL && old+off != SENTINEL; - _opStart.put(n._nid,old+off); - } - - // Set op length for n - public void opLen(Node n, byte len) { - assert len != SENTINEL; - _opLen.put(n._nid,len); - } // Nodes need "relocation" patching; things done after code is placed. // Record src and dst Nodes. private final HashMap<Node,CFGNode> _internals = new HashMap<>(); // Source is a Call, destination in the Fun. public Encoding relo( CallNode call ) { - FunNode fun = _code.link(call.tfp()); - if( fun != null && !_code.owns(fun) ) - _externals.put(call,fun._name); - else - _internals.put(call,fun); + _internals.put(call,_code.link(call.tfp())); return this; } - // Code address as a constant - public Encoding relo( FunPtrNode fptr ) { - TypeFunPtr tfp = (TypeFunPtr)fptr._type; - // Native functions have no local FunNode, but are still callable. - // Do not treat their addresses as identities of deleted functions. - String nativeName = _code.externFunc(tfp.fidx()); - if( nativeName!=null ) return external(fptr,nativeName); - FunNode fun = _code.link(tfp); - if( fun != null && !_code.owns(fun) ) - _externals.put(fptr,fun._name); - else - _internals.put(fptr,fun); + public Encoding relo( ConstantNode con ) { + TypeFunPtr tfp = (TypeFunPtr)con._con; + // A native address has no local FunNode. Keep its symbol even after + // selection or a spill copy has replaced the original ExternNode. + String name = _code.externFunc(tfp); + if( name!=null ) return external(con,name); + _internals.put(con,_code.link(tfp)); return this; } - // Local jump public void jump( CFGNode jmp, CFGNode dst ) { _internals.put(jmp,dst.uctrlSkipEmpty()); } - // External references only located by Strings a link-time - public final HashMap<Node,String> _externals = new HashMap<>(); + + final HashMap<Node,String> _externals = new HashMap<>(); public Encoding external( Node n, String extern ) { _externals.put(n,extern); 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. + // addressing to load it public void largeConstant( Node relo, Type t, int off, int elf ) { - // TODO: Any-old struct in the cpool - assert t.isConstant() || (t instanceof TypeStruct ts && Parser.startsClzPrefix(ts._name)); + assert t.isConstant(); assert (byte)off == off; assert (byte)elf == elf; _bigCons.put(relo,new Relo(relo,t,(byte)off,(byte)elf)); } // -------------------------------------------------- - Encoding encode( ) { + void encode() { // Basic block layout: negate branches to keep blocks in-order; insert // unconditional jumps. Attempt to keep backwards branches taken, // forwards not-taken (this is the default prediction on most // hardware). Layout is still Reverse Post Order but with more // restrictions. - basicBlockLayout( ); + basicBlockLayout(); // Write encoding bits in order into a big byte array. // Record opcode start and length. @@ -224,13 +182,8 @@ public class Encoding implements com.seaofnodes.isa.CodeSink { // encodings and compact the code, changing all the offsets. compactShortForm(); - // Place pool objects and record relocations within their contents. - _data.write(); - // Patch RIP-relative and local encodings now. patchLocalRelocations(); - - return this; } // -------------------------------------------------- @@ -238,48 +191,29 @@ public class Encoding implements com.seaofnodes.isa.CodeSink { // unconditional jumps. Attempt to keep backwards branches taken, forwards // not-taken (this is the default prediction on most hardware). Layout is // still Reverse Post Order but with more restrictions. - private void basicBlockLayout( ) { + private void basicBlockLayout() { IdentityHashMap<LoopNode,Ary<CFGNode>> rpos = new IdentityHashMap<>(); - _cfg = new Ary<>(CFGNode.class); - rpos.put(_code._start.loop(),_cfg); + Ary<CFGNode> rpo = new Ary<>(CFGNode.class); + rpos.put(_code._start.loop(),rpo); BitSet visit = _code.visit(); - - // Do them all except the <clinit>s - Ary<FunNode> clinits = new Ary<>(FunNode.class); - FunNode entry = null; - String entryName = _code.entryClinitName(); - for( FunNode fun : _code._linker ) { - if( fun != null && !fun.isDead() && _code.owns(fun) ) { - if( fun.isClz() ) { - if( fun._name.equals(entryName) ) - entry = fun; - else - clinits.add(fun); - } else { - int x = _cfg._len; - _rpo_cfg(fun,visit,rpos); - assert _cfg.at(x) instanceof ReturnNode; - } + rpo.add(_code._stop); + FunNode main = _code.link(_code._main); + for( Node n : _code._start._outputs ) + if( n instanceof FunNode fun && fun != main ) { + int x = rpo._len; + _rpo_cfg(fun, visit, rpos ); + assert rpo.at(x) instanceof ReturnNode; } - } - // Now the <clinit>s last, so when reversed they become first. The - // program entry <clinit> is last of all, so it lands at offset zero. - for( FunNode clinit : clinits ) { - int x = _cfg._len; - _rpo_cfg(clinit,visit,rpos); - assert _cfg.at(x) instanceof ReturnNode; - } - if( entry != null ) { - int x = _cfg._len; - _rpo_cfg(entry,visit,rpos); - assert _cfg.at(x) instanceof ReturnNode; - } - + // Do any main function last... so it lands at offset 0 in the RPO + if( main!=null ) + _rpo_cfg(main, visit, rpos ); + rpo.add(_code._start); // Reverse in-place - for( int i=0; i< _cfg.size()>>1; i++ ) - _cfg.swap(i,_cfg.size()-1-i); + for( int i=0; i< rpo.size()>>1; i++ ) + rpo.swap(i,rpo.size()-1-i); visit.clear(); + _code._cfg = rpo; // Save the new ordering } @@ -353,7 +287,7 @@ public class Encoding implements com.seaofnodes.isa.CodeSink { private static boolean shouldInvert(CFGNode t, CFGNode f, int bld) { int tld = t.loopDepth(), fld = f.loopDepth(); // These next two are symmetric and can happen in any order; if `tld < - // bld` is true, then `fld < bld` must be false, or else both directions + // bld` is true, the `fld < bld` must be false, or else both directions // exit the loop... and the IF test would not be in the loop. // true to exit a loop usually (and false falls into Yet Another Loop @@ -398,26 +332,24 @@ public class Encoding implements com.seaofnodes.isa.CodeSink { // -------------------------------------------------- // Write encoding bits in order into a big byte array. // Record opcode start and length. - - // Current function is used by the spill-op encodings to query the stack - // frame layout and get the spill offsets. + public FunNode _fun; // Currently encoding function private void writeEncodings() { - for( CFGNode bb : _cfg ) { + _opStart= new int [_code.UID()]; + _opLen = new byte[_code.UID()]; + for( CFGNode bb : _code._cfg ) { if( !(bb instanceof MachNode mach0) ) - opStart(bb, _bits.size()); - else if( bb instanceof FunNode ) { - opStart(bb, _bits.size()); + _opStart[bb._nid] = _bits.size(); + else if( bb instanceof FunNode fun ) { + _fun = fun; // Currently encoding function + _opStart[bb._nid] = _bits.size(); mach0.encoding( this ); - opLen(bb, (byte) (_bits.size() - opStart(bb))); + _opLen[bb._nid] = (byte) (_bits.size() - _opStart[bb._nid]); } for( Node n : bb._outputs ) { - if( n instanceof MachNode mach && !(n instanceof FunNode) && - // FunPtrs are encoding in the block of their control, not - // the block of the ReturnNode input - which is also a CFG. - !(n instanceof FunPtrNode && bb instanceof ReturnNode) ) { - opStart(n, _bits.size()); + if( n instanceof MachNode mach && !(n instanceof FunNode) ) { + _opStart[n._nid] = _bits.size(); mach.encoding( this ); - opLen(n, (byte) (_bits.size() - opStart(n))); + _opLen[n._nid] = (byte) (_bits.size() - _opStart[n._nid]); } } } @@ -427,10 +359,10 @@ public class Encoding implements com.seaofnodes.isa.CodeSink { // Short-form RIP-relative support: replace short encodings with long // encodings and expand the code, changing all the offsets. private void compactShortForm() { - int len = _cfg._len; + int len = _code._cfg._len; int[] oldStarts = new int[len]; for( int i=0; i<len; i++ ) - oldStarts[i] = opStart(_cfg.at(i)); + oldStarts[i] = _opStart[_code._cfg.at(i)._nid]; // TODO: Rewrite this algo to use the small "_jmps" list of just the // jumps instead of walking all blocks. @@ -444,27 +376,24 @@ public class Encoding implements com.seaofnodes.isa.CodeSink { while( slide != 0) { // While no fails slide = 0; for( int i=0; i<len; i++ ) { - CFGNode bb = _cfg.at(i); - opStartAdd(bb, slide); + CFGNode bb = _code._cfg.at(i); + _opStart[bb._nid] += slide; // Slide down all other (non-CFG) ops in the block for( Node n : bb._outputs ) - if( n instanceof MachNode && !(n instanceof CFGNode) && - // Match writeEncodings(): FunPtrs are encoded in the - // block of their control, not again at the Return. - !(n instanceof FunPtrNode && bb instanceof ReturnNode) ) - opStartAdd(n, slide); + if( n instanceof MachNode && !(n instanceof CFGNode) ) + _opStart[n._nid] += slide; if( bb instanceof RIPRelSize riprel ) { CFGNode target = (bb instanceof IfNode iff ? iff.cproj(0) : (CFGNode)bb.out(0)).uctrlSkipEmpty(); // Delta is from opStart to opStart. X86 at least counts // the delta from the opEnd, but we don't have the end until // we decide the size - so the encSize has to deal - int delta = opStart(target) - opStart(bb); + int delta = _opStart[target._nid] - _opStart[bb._nid]; byte opLen = riprel.encSize(delta); // Recorded size is smaller than the current size? - if( opLen(bb) < opLen ) { + if( _opLen[bb._nid] < opLen ) { // Start sliding the code down; record slide amount and new size - slide += opLen - opLen(bb); - opLen(bb, opLen); + slide += opLen - _opLen[bb._nid]; + _opLen[bb._nid] = opLen; } } } @@ -475,21 +404,21 @@ public class Encoding implements com.seaofnodes.isa.CodeSink { // short-jumps span function headers, the padding will not make any // short jumps fail. for( int i=0; i<len; i++ ) { - CFGNode bb = _cfg.at(i); + CFGNode bb = _code._cfg.at(i); // Functions pad to align 16 if( bb instanceof FunNode ) { - int newStart = opStart(bb)+slide; + int newStart = _opStart[bb._nid]+slide; slide += (newStart+15 & -16)-newStart; } - opStartAdd(bb, slide); + _opStart[bb._nid] += slide; for( Node n : bb._outputs ) - if( n instanceof MachNode && !(n instanceof CFGNode) && - !(n instanceof FunPtrNode && bb instanceof ReturnNode) ) - opStartAdd(n, slide); + if( n instanceof MachNode && !(n instanceof CFGNode) ) + _opStart[n._nid] += slide; } + _padFunHeads = true; // Copy/slide the bits to make space for all the longer branches - int grow = opStart(_cfg.at(len-1)) - oldStarts[len-1]; + int grow = _opStart[_code._cfg.at(len-1)._nid] - oldStarts[len-1]; if( grow > 0 ) { // If no short-form ops, nothing to do here int end = _bits.size(); byte[] bits = new byte[end+grow]; @@ -497,7 +426,7 @@ public class Encoding implements com.seaofnodes.isa.CodeSink { int start = oldStarts[i]; if( start==0 && i>1 ) continue; int oldStart = oldStarts[i]; - int newStart = opStart(_cfg.at(i)); + int newStart = _opStart[_code._cfg.at(i)._nid]; System.arraycopy(_bits.buf(),oldStart,bits,newStart,end-start); end = start; } @@ -512,59 +441,103 @@ public class Encoding implements com.seaofnodes.isa.CodeSink { void patchLocalRelocations() { // Walk the local code-address relocations for( Node src : _internals.keySet() ) { - int start = opStart(src); + int start = _opStart[src._nid]; Node dst = _internals.get(src); - // If function is entirely dead, only the function pointer remains - // and, it can only be used to test against zero or equals to - // another function pointer... i.e., there Is No Code Here. - int target = dst == null ? start : opStart(dst); - ((RIPRelSize)src).patch(this, start, opLen(src), target - start); + int target = _opStart[dst._nid]; + ((RIPRelSize)src).patch(this, start, _opLen[src._nid], target - start); } } - // Emit a single scalar as bits - static void addN( int log, Type t, BAOS bits ) { - long x = switch( t ) { - case TypeInteger ti -> ti.value(); - case TypeFloat tf -> log==3 - ? Double.doubleToRawLongBits( tf.value()) - : Float.floatToRawIntBits((float)tf.value()); - case TypeFunPtr tfp -> 0; // These need to be relocated - case TypeMemPtr tmp -> 0; // These need to be relocated - default -> { if( t==Type.NIL ) yield 0; else throw Utils.TODO(); } - }; - addN(log,x,bits); + // -------------------------------------------------- + // Write the constant pool into the BAOS and optionally patch locally + void writeConstantPool( BAOS bits, boolean patch ) { + 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); + 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 = 4; 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 -> cpool(align,bits,tt); + case TypeStruct ts -> cpool(bits,ts); + // Simple primitive (e.g. larger int, float) + default -> addN(align,relo._t,bits); + } + } + // Record target address and opcode start + relo._target = target; + relo._opStart= _opStart[relo._op._nid]; + // Go ahead and locally patch in-memory + if( patch ) + ((RIPRelSize)relo._op).patch(this, relo._opStart, _opLen[relo._op._nid], relo._target - relo._opStart); + } + } + } + + // Constant tuples put all entries at same alignment + private void cpool(int align, BAOS bits, TypeTuple tt) { + for( Type tx : tt._types ) + addN(align,tx,bits); + } + + // Structs use internal field layout + private void cpool( 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]]; + 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,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); - 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); + int start = _opStart[src._nid]; String dst = _externals.get(src); - int target = externalAddress(dst); - ((RIPRelSize)src).patch(this, start, opLen(src), target - start); + int target = switch( dst ) { + case "calloc" -> SENTINEL_CALLOC; + case "write" -> SENTINEL_WRITE ; + default -> throw Utils.TODO(); + }; + ((RIPRelSize)src).patch(this, start, _opLen[src._nid], target - start); } } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/FinalizeIR.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/FinalizeIR.java index 923486c6..9e3b3e32 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/FinalizeIR.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/FinalizeIR.java @@ -1,44 +1,76 @@ package com.seaofnodes.simple.codegen; +import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.*; import java.util.Arrays; -// Comnpute field layouts only after SCCP and its peepholes converge. -// Keep surviving functions rooted without introducing unknown callers. +// Settle layouts and detach the analyzed call graph before the backend. abstract class FinalizeIR { static void run(CodeGen code) { - // Compute field layouts and replace symbolic field offsets by constants. + fieldSizes(code); + code._iter.iterate(code); + removeCallGraph(code); + Arrays.sort(code._stop._inputs._es,0,code._stop.nIns(),(x,y) -> x._nid - y._nid); + } + + // Freeze field sizes; do struct layouts; convert field offsets into + // constants. + private static void fieldSizes( CodeGen code ) { for( int i=0; i<code._start.nOuts(); i++ ) { Node use = code._start.out(i); if( use instanceof ConFldOffNode off ) { - Node con = off.asOffset(); - assert con != null : off._ts._name+"."+off._fname; - off.subsume(con); + TypeMemPtr tmp = (TypeMemPtr) Parser.TYPES.get(off._name); + off.subsume( off.asOffset(tmp._obj) ); i--; } } - code._iter.iterate(code); // Propagate field offsets and clean up. - - // Retain surviving functions for emission without adding unknown callers. - for( FunNode fun : code._linker ) - if( fun != null ) { - // A folded static function field can leave a detached body - // with internal cycles. It has no callers; later phases must - // not revive it merely because its nodes are still allocated. - if( fun.isDead() || fun._type.isHigh() ) { - code._linker.set(fun.sig().fidx(),null); - continue; + } + + // The remaining passes assume all calls are unlinked; i.e. we are throwing + // away the Call Graph here. Functions only reachable from internal calls + // need to be re-hooked to stop/start less they go dead. + private static void removeCallGraph(CodeGen code) { + code._start.walk( x -> { + // Unlink all existing (conservative) linkages. + if( x instanceof CallNode call ) { + for( Node y : call._outputs ) { + // Keep function alive and hooked to Start so it gets + // codegen'd. Function is callable by this call via + // TFP, although perhaps not by any other means. + if( y instanceof FunNode fun && !(fun.in(1) instanceof StartNode) ) { + ParmNode rpc = fun.rpc(); + if( rpc==null ) // Ensure valid RPC + rpc = makeRPC(fun); + // Insert a hook to Start and Stop + fun.insertDef(1,code._start); + for( Node use : fun._outputs ) + if( use instanceof ParmNode parm ) + parm.insertDef(1,ConstantNode.make(parm._type).peephole()); + code._stop.addDef(fun.ret()); + } + } + call.unlink_all(); } - code.keepInCompUnit(fun); - } - for( CompUnit cu : code._compunits.values() ) { - ProjNode mem = cu._start.proj(1); - if( mem != null ) code.add(mem.unkeep()); + return null; + } ); + + } + + // Make a valid Parm RPC. + // First make sure a valid RPC; single-call into multi-fun will have each + // function having a single call site, so the RPC becomes a constant and + // folds - but since multiple targets, the Call never inlines. Recreate a + // valid RPC so codegen (and Eval2) understands the calling convention. + private static ParmNode makeRPC( FunNode fun ) { + ParmNode rpc = new ParmNode("$rpc",0,fun.ret().rpc()._type,fun); + for( int i=1; i<fun.nIns(); i++ ) { + CallEndNode cend = ((CallNode)fun.in(i)).cend(); + rpc.addDef(ConstantNode.make(cend._rpc).peephole()); } - // Stable print order: initializers first, then other Returns by NID. - for( Node stop : code._stop._inputs ) - Arrays.sort(stop._inputs._es,0,stop.nIns(), - (x,y) -> ((((ReturnNode)x).fun().isClz() ? -1 : x._nid) - - (((ReturnNode)y).fun().isClz() ? -1 : y._nid) )); + fun.ret().setDef(3,rpc.init()); + return rpc; } + + } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalBits.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalBits.java deleted file mode 100644 index bc9c9d39..00000000 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalBits.java +++ /dev/null @@ -1,177 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.util.*; -import java.util.HashMap; -import java.util.Arrays; -import java.util.Objects; - - -// This utility 2-way maps some *global* program feature to a local dense -// index, suitable for packing in BitSets. The mapping takes a global value - -// always a {source file/class} name and an order number counting up per -// feature in the same file. The dense local index can be different in -// different compilations. - -// The global info allows loading the same remote class info from different -// paths and aligning them. Example: Compiling A loads pre-compiled B and C, -// both of which load pre-compiled D. The 2 different D loads unify via this -// global info. - -// These mappings are all trivial identities when compiling one file; they -// only become complex when loading seperately compiled code. - -public class GlobalBits { - public static final int RESERVED = 2; - private final HashMap<String,int[]> _clzOrder2Local = new HashMap<>(); - private final Ary<String> _local2Clz = new Ary<>(String.class); - private final AryInt _local2Order = new AryInt(); - // Local dense index - private int _local; - - GlobalBits() { - _local = RESERVED; - // Reserved null-class bits are global identities and are never - // remapped. - int[] xs = new int[RESERVED]; - xs[0] = RESERVED; // Per-class next order counter - for( int i=0; i<RESERVED; i++ ) { - if( i > 0 ) xs[i] = i; - _local2Clz .setX(i,null); - _local2Order.setX(i,i); - } - _clzOrder2Local.put(null,xs); - } - - // Get a next local index for this clz - int next( String clz ) { return next(clz,-1); } - - // Given a clz and order, return an existing local index or make a new - // mapping and return that. - int next(String clz, int order ) { - // Get the per-clz Order->Local mapping - int[] xs = _clzOrder2Local.get(clz); - if( xs == null ) - xs = new int[]{RESERVED,1}; // Make, if it does not exist - // Caller does not have an order in mind, just take the next one stored - // in xs[0] - if( order == -1 ) - order = xs[0]++; - // Order exists from prior compilation, but not recorded locally - else if( order >= xs[0] ) - xs[0] = order+1; - // Grow as needed - while( xs.length <= order ) - xs = Arrays.copyOf(xs,xs.length*2); - // Have a local mapping already? - if( xs[order] != 0 ) - return xs[order]; - int local = _local++; - bind(local,clz,order); - return local; // The dense local index - } - - // Bind a specific dense local index to {clz,order}. Used by both fresh - // allocation and deserialization, where local indexes must not compact - // across holes because XInt bitsets are serialized against this space. - private void bind( int local, String clz, int order ) { - if( _local <= local ) - _local = local+1; - _local2Clz .setX(local,clz ); - _local2Order.setX(local,order); - - // Inline-only fidx holes are represented as {null,0}. They need the - // reverse mapping above, but no forward map because xs[0] is the - // per-class next-order counter. - if( clz==null && order==0 ) - return; - - int[] xs = _clzOrder2Local.get(clz); - if( xs == null ) - xs = new int[]{RESERVED,1}; - if( order >= xs[0] ) - xs[0] = order+1; - while( xs.length <= order ) - xs = Arrays.copyOf(xs,xs.length*2); - _clzOrder2Local.put(clz,xs); - xs[order] = local; - } - - // A file-local function that will inline, and never write to disk - public int nextInline( ) { - int local = _local++; - _local2Clz .setX(local,null); - _local2Order.setX(local,0); - return local; - } - // Get a next local index for the same clz as this old index - public int next( int old ) { return next(_local2Clz.at(old)); } - - // Map a file-local dense index to this compilation unit's local dense index. - public int map( GlobalBits fileBits, int fileLocal ) { - if( fileLocal >= fileBits._local2Clz.size() ) - throw new ArrayIndexOutOfBoundsException(""+fileLocal+" >= "+fileBits._local2Clz.size()); - return findOrNext(fileBits._local2Clz.at(fileLocal), fileBits._local2Order.at(fileLocal)); - } - - // Map a global {clz,order} to a local one; keep any existing mapping or - // make a new one as needed. - private int findOrNext(String clz, int order ) { - // Reserved null-class identities are not represented in xs[order], - // because xs[0] is the next-order counter. - if( clz==null && order < _local2Clz.size() && _local2Order.at(order)==order && _local2Clz.at(order)==null ) - return order; - int[] xs = _clzOrder2Local.get(clz); - if( xs != null && order < xs.length ) { - int local = xs[order]; - if( local < _local2Clz.size() && _local2Order.at(local)==order && Objects.equals(_local2Clz.at(local),clz) ) - return local; - } - 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. - baos.packed2(_local2Clz.size()); - for( int i=RESERVED; i<_local2Clz.size(); i++ ) { - String clz = _local2Clz.at(i); - baos.packed2(clz==null ? 0 : strs.get(clz)); - baos.packed2( _local2Order.at(i) ); - } - } - - void gather( HashMap<String,Integer> strs ) { - for( int i=RESERVED; i<_local2Clz.size(); i++ ) - Serialize.gather(strs,_local2Clz.at(i)); - } - - // Read in the BAIS bits needed to make a file-local -> global mapping - static GlobalBits packed( BAOS bais, String[] strs ) { - var gb = new GlobalBits(); - int n = bais.packed2(); - for( int i=RESERVED; i<n; i++ ) { - int sx = bais.packed2(); - String clz = strs[sx]; - if( clz.isEmpty() ) clz = null; - int order = bais.packed2(); - gb.bind(i,clz,order); - } - return gb; - } - - @Override public String toString() { - SB sb = new SB().p('['); - for( int i=0; i<_local2Clz.size(); i++ ) { - if( i > 0 ) sb.p(','); - sb.p(i).p(" <-> ").p(_local2Clz.at(i)).p('#').p(_local2Order.at(i)); - } - return sb.p(']').toString(); - } - -} diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java index 09eb8d33..da45db4f 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java @@ -27,15 +27,16 @@ public abstract class GlobalCodeMotion { breakUpGlobalConstants(code._start); code._visit.clear(); - schedLate(code); + schedLate (code); } // Post-Order of CFG private static void _rpo_cfg(CFGNode def, Node use, BitSet visit, Ary<CFGNode> rpo) { if( !(use instanceof CFGNode cfg) || visit.get(cfg._nid) ) return; // Been there, done that - assert !( def instanceof ReturnNode && use instanceof CallEndNode ); // All calls unwired now - assert !( def instanceof CallNode && use instanceof FunNode ); // All calls unwired now + if( def instanceof ReturnNode && use instanceof CallEndNode ) + return; + assert !( def instanceof CallNode && use instanceof FunNode ); // All calls unwired now visit.set(cfg._nid); for( Node useuse : cfg._outputs ) _rpo_cfg(cfg,useuse,visit,rpo); @@ -49,7 +50,7 @@ public abstract class GlobalCodeMotion { var cons = new ArrayList<Node>(); for( Node con : start._outputs ) if( con!=null && !(con instanceof CFGNode) && - (con.isConst() || con instanceof MachNode mach && mach.outregmap()!=null) ) { + (con.isConst() || (con instanceof MachNode mach && mach.outregmap()!=null)) ) { globals.put(con,true); cons.add(con.keep()); // Preserve original inputs while rewiring users. } @@ -60,7 +61,6 @@ public abstract class GlobalCodeMotion { if( use==null || globals.containsKey(use) ) continue; FunNode fun = useFun(use); if( fun==null ) continue; // Global metadata has no function owner. - assert CodeGen.CODE._mach==null || CodeGen.CODE.owns(fun); // Ideal evaluation includes imported bodies. var local = copies.computeIfAbsent(fun,f -> new IdentityHashMap<>()); Node copy = cloneGlobal(con,fun,globals,local); for( int i=0; i<use.nIns(); i++ ) @@ -142,9 +142,8 @@ public abstract class GlobalCodeMotion { // Record Load NIDs at all their CFG block choices, then check against // Store block choices to force a Load above an anti-dependent Store. int[] anti = new int[code.UID()]; - Ary<Node> antideps = new Ary<>(Node.class); // Breadth-first scheduling - breadth(code._stop,ns,late,anti, antideps); + breadth(code._stop,ns,late,anti); // Copy the best placement choice into the control slot for( int i=0; i<late.length; i++ ) @@ -152,7 +151,7 @@ public abstract class GlobalCodeMotion { ns[i].setDef(0,late[i]); } - private static void breadth(Node stop, Node[] ns, CFGNode[] late, int[] anti, Ary<Node> antideps) { + private static void breadth(Node stop, Node[] ns, CFGNode[] late, int[] anti) { // Things on the worklist have some (but perhaps not all) uses done. WorkList<Node> work = new WorkList<>(); work.push(stop); @@ -173,12 +172,12 @@ public abstract class GlobalCodeMotion { // Loads need their memory inputs' uses also done if( n instanceof MemOpNode ld && ld._isLoad ) - for( Node memuse : ld.antiDeps(antideps) ) + for( Node memuse : ld.antiDeps() ) if( late[memuse._nid]==null ) continue outer; // All uses done, schedule - _doSchedLate(n,ns,late,anti,antideps); + _doSchedLate(n,ns,late,anti); } // A use just finished; reconsider its inputs and waiting loads, @@ -195,7 +194,7 @@ public abstract class GlobalCodeMotion { } } - private static void _doSchedLate(Node n, Node[] ns, CFGNode[] late, int[] anti, Ary<Node> antideps) { + private static void _doSchedLate(Node n, Node[] ns, CFGNode[] late, int[] anti) { // Walk uses, gathering the LCA (Least Common Ancestor) of uses CFGNode early = n.in(0) instanceof CFGNode cfg ? cfg : n.in(0).cfg0(); assert early != null; @@ -206,7 +205,8 @@ public abstract class GlobalCodeMotion { // Loads may need anti-dependencies, raising their LCA if( n instanceof MemOpNode load && load._isLoad ) - lca = find_anti_dep(lca,load,early,late,anti, antideps); + lca = find_anti_dep(lca,load,early,late,anti); + // Walk up from the LCA to the early, looking for best place. This is // the lowest execution frequency, approximated by least loop depth and @@ -224,8 +224,7 @@ public abstract class GlobalCodeMotion { // Block of use. Normally from late[] schedule, except for Phis, which go // to the matching Region input. private static CFGNode use_block(Node n, Node use, CFGNode[] late) { - if( use instanceof ParmNode parm ) - return late[use._nid]; + if( use instanceof ParmNode ) return late[use._nid]; if( !(use instanceof PhiNode phi) ) return late[use._nid]; CFGNode found=null; @@ -246,7 +245,7 @@ public abstract class GlobalCodeMotion { best instanceof IfNode; } - // A writer may consume a load's memory through an aggregate. + // An allocation may consume a load's memory through a partial aggregate. private static void wakeLoads(Node def, CFGNode[] late, WorkList<Node> work, BitSet visit) { if( visit.get(def._nid) ) return; visit.set(def._nid); @@ -257,26 +256,35 @@ public abstract class GlobalCodeMotion { if( def.in(i)!=null ) wakeLoads(def.in(i),late,work,visit); } - private static CFGNode find_anti_dep(CFGNode lca, MemOpNode load, CFGNode early, CFGNode[] late, int[] anti, Ary<Node> antideps) { + private static CFGNode find_anti_dep(CFGNode lca, MemOpNode load, CFGNode early, CFGNode[] late, int[] anti) { // We could skip final-field loads here. // Walk LCA->early, flagging Load's block location choices - for( CFGNode cfg=lca; cfg!=early.idom(); cfg = cfg.idom() ) + for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() ) anti[cfg._nid] = load._nid; // Walk load->mem uses, looking for Stores causing an anti-dep - for( Node mem : load.antiDeps(antideps) ) { - if( mem instanceof PhiNode phi ) { + for( Node mem : load.antiDeps() ) { + switch( mem ) { + case MemOpNode st: + assert late[st._nid]!=null; + lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st,anti); + break; + case CallNode st: + assert late[st._nid]!=null; + lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st,anti); + break; + case PhiNode phi: // Inputs carrying different memory might overwrite this alias. - // Raise the LCA for those paths; the unchanged-memory paths - // do not clobber the load. No scheduling edge to the Phi. + // Raise the LCA for those paths; unchanged memory does not + // clobber the load. No scheduling edge to the Phi. for( int i=1; i<phi.nIns(); i++ ) if( phi.in(i)!=load.mem() ) lca = anti_dep(load,phi.region().cfg(i),load.mem().cfg0(),lca,null,anti); - } else { - // Hunt for clobbering stores - assert mem instanceof MemOpNode || mem instanceof EscapeNode || - mem instanceof CallNode || mem instanceof CLInitNode; - assert late[mem._nid]!=null; - lca = anti_dep(load,late[mem._nid],mem.cfg0(),lca,mem,anti); + break; + case NewNode st: + assert late[st._nid]!=null; + lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st,anti); + break; + default: throw Utils.TODO(); } } return lca; @@ -286,15 +294,14 @@ public abstract class GlobalCodeMotion { private static CFGNode anti_dep( MemOpNode load, CFGNode stblk, CFGNode defblk, CFGNode lca, Node st, int[] anti ) { // A conditional writer can precede the load without dominating its late block. // Walk back to the first overlap with the load's placement range. - // A different Phi input need not be dominated by this memory's - // definition. Stop at its depth rather than looking for that ancestor. - // Retain the sentinel too: a function entry has a null idom. + // Another Phi input need not be dominated by this memory definition. + // Include its depth, and retain the sentinel for an entry's null idom. for( ; stblk != defblk.idom() && stblk.idepth() >= defblk.idepth(); stblk = stblk.idom() ) { // Store and Load overlap, need anti-dependence if( anti[stblk._nid]==load._nid ) { lca = stblk.domLCA(lca,null); // Raise Loads LCA if( lca == stblk && st != null && !(st instanceof CallNode) && st._inputs.find(load) == -1 ) // And if something moved, - st.addDef(load); // Add anti-dep as well (calls already end their block) + st.addDef(load); // Add anti-dep as well; Calls already end their block. return lca; // Cap this stores' anti-dep to here } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/IFG.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/IFG.java index bcdb14bf..bcd1957d 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/IFG.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/IFG.java @@ -69,9 +69,9 @@ abstract public class IFG { // Reset all to empty resetBBLiveOut(); resetIFG(); - WORK.clear(); // Last block has nothing live out + assert WORK.isEmpty(); for( CFGNode bb : alloc._code._cfg ) if( bb.blockHead()) WORK.push( bb ); @@ -208,13 +208,7 @@ abstract public class IFG { assert tlrg.leader(); // Always, tlrg cannot use kills if( tlrg._mask.overlap(killMask) ) { - Node live = TMP.get(tlrg); - // Disallow clone-ables from living across killed registers. - // Fail them and let splitting re-clone closer to the target use. - if( live instanceof MachNode mach && mach.isClone() ) - alloc.fail(tlrg); - else if( !tlrg.sub(killMask) ) - alloc.fail(tlrg); + if( !tlrg.sub(killMask) ) alloc.fail(tlrg); } } } @@ -322,7 +316,7 @@ abstract public class IFG { // Pull all lrgs from IFG, in trivial order if possible while( sptr < color_stack.length ) { // Swap best color up front - pickColor(color_stack,sptr,swork,alloc); + pickColor(color_stack,sptr,swork); // Pick a trivial lrg, and (temporarily) remove from the IFG. LRG lrg = color_stack[sptr++]; @@ -378,10 +372,10 @@ abstract public class IFG { } // Pick LRG from color stack - private static void pickColor(LRG[] color_stack, int sptr, int swork, RegAlloc alloc) { + private static void pickColor(LRG[] color_stack, int sptr, int swork) { // Out of trivial colorable, pick an at-risk to pull if( sptr==swork ) - swap(color_stack,sptr,pickRisky(color_stack,sptr,alloc)); + swap(color_stack,sptr,pickRisky(color_stack,sptr)); // When coloring, we'd like to give more choices; so when coloring we'd // like to see the single-def first (since no choices anyway), then // non-split related (so more live ranges get colored), then @@ -398,7 +392,6 @@ abstract public class IFG { swap(color_stack,sptr,bidx); // Pick best at sptr } - // Returns true if 'lrg' is better than 'best' private static boolean betterLRG( LRG best, LRG lrg ) { // If single-def varies, keep the not-single-def if( best.size1() != lrg.size1() ) @@ -406,22 +399,18 @@ abstract public class IFG { // If hasSplit varies, keep the hasSplit if( best.hasSplit() != lrg.hasSplit() ) return lrg.hasSplit(); - // If both are single register (so both sizes are 1), - // return a multi-use over a multi-def - if( best.size1() ) - return !best._multiUse && lrg._multiUse; // Keep large register count return best.size() < lrg.size(); } // Pick a live range that hasn't already spilled, or has a single-def- // single-use that are not adjacent. - private static int pickRisky( LRG[] color_stack, int sptr, RegAlloc alloc ) { + private static int pickRisky( LRG[] color_stack, int sptr ) { int best=sptr; - int bestScore = pickRiskyScore(color_stack[best],alloc); + int bestScore = pickRiskyScore(color_stack[best]); for( int i=sptr+1; i<color_stack.length; i++ ) { if( bestScore == 1000000 ) return best; // Already max score - int iScore = pickRiskyScore(color_stack[i],alloc); + int iScore = pickRiskyScore(color_stack[i]); if( iScore > bestScore ) { best = i; bestScore = iScore; } } @@ -436,60 +425,30 @@ abstract public class IFG { // // Picking a live range that is very close to coloring might allow it to // color despite being risky. - private static int pickRiskyScore( LRG lrg, RegAlloc alloc ) { - // Callee-save live ranges cover the whole function and are cheap to - // split. Rank scheduled area recovered against the loop-scaled cost - // of the def- and use-side moves. - if( lrg._machDef instanceof CalleeSaveNode ) - return areaScore(functionArea(lrg._fun,alloc), - splitCost((Node)lrg._machDef,(Node)lrg._machUse)); - if( lrg._splitDef != null && lrg._splitDef. in(1) instanceof CalleeSaveNode && - lrg._splitUse != null && lrg._splitUse.out(0) instanceof ReturnNode ) - return areaScore(functionArea(lrg._fun,alloc), - splitCost(lrg._splitDef,lrg._splitUse)); - + private static int pickRiskyScore( LRG lrg ) { // Pick single-def clonables that are not right next to their single-use. - // Rank by the area recovered by cloning closer to the use. + // Failing to color these will clone them closer to their uses. if( !lrg._multiDef && lrg._machDef!=null && lrg._machUse!=null && lrg._machDef.isClone() ) { - Node def = ((Node)lrg._machDef); - Node use = ((Node)lrg._machUse); + Node def = (Node)lrg._machDef; + Node use = (Node)lrg._machUse; CFGNode cfg = def.cfg0(); - int area = cfg != use.cfg0() - ? functionArea(lrg._fun,alloc) - : cfg._outputs.find(use)-cfg._outputs.find(def)-1; - // A single-use function address can move next to its use without - // adding an instruction. Prefer that to saving a callee-save - // register just to hold the address. Stay in the same block and - // require a gap so the clone makes progress. - if( def instanceof FunPtrNode && !lrg._multiUse && - cfg == use.cfg0() && area > 0 ) + if( lrg._multiUse || cfg != use.cfg0() || // Many uses or different blocks OR + // Same block, but not close + cfg._outputs.find(def)+1 < cfg._outputs.find(use) ) return 1000000; - if( area > 0 ) - return areaScore(area,loopCost(use)); } - // Default for ranges without a specific area/cost estimate. - return 1000 + (lrg._multiUse ? -100 : 0); - } - - private static int functionArea(FunNode fun, RegAlloc alloc) { - int area=0; - for( CFGNode bb : alloc._code._cfg ) - if( bb.fun()==fun ) - area += bb.nOuts(); - return area; - } - - private static int areaScore(int area, int cost) { - return 1000 + (area<<10)/cost; - } - - private static int splitCost(Node def, Node use) { - return loopCost(def) + (use==null ? 0 : loopCost(use)); - } + // Always pick callee-save registers as being very large area recovered + // and very cheap to spill. + if( lrg._machDef instanceof CalleeSaveNode ) + return 1000000-2-lrg._mask.firstReg(); + if( lrg._splitDef != null && lrg._splitDef.in(1) instanceof CalleeSaveNode && + lrg._splitUse != null && lrg._splitUse.out(0) instanceof ReturnNode ) + return 1000000-1; - private static int loopCost(Node n) { - return 1 << (n.cfg0().loopDepth()*3); + // TODO: cost/benefit model. Perhaps counting loop-depth (freq) of def/use for cost + // and "area" for benefit + return 1000; } private static short biasColor( RegAlloc alloc, LRG lrg, short reg, RegMask mask ) { diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/InstSelect.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/InstSelect.java index 5fc002a1..5a91cf9f 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/InstSelect.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/InstSelect.java @@ -1,43 +1,30 @@ package com.seaofnodes.simple.codegen; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.*; import java.util.BitSet; import java.util.IdentityHashMap; -// Replace ideal nodes with machine nodes while preserving CFG/loop metadata. -// Selection also owns target ABI masks and rebuilding the copied graph's uses. +// Replace ideal nodes with machine nodes, then rebuild the copied graph's uses. class InstSelect { private final CodeGen _code; - private InstSelect(CodeGen code) { _code=code; } + InstSelect(CodeGen code) { _code=code; } - static CodeGen select(CodeGen code, String cpu, String callingConv, String base) { - // Ideal evaluation schedules the ideal graph without a CPU port. - if( cpu==null ) return code; - return code.phase(CodeGen.Phase.Select, code._phase==CodeGen.Phase.Unlink, - () -> new InstSelect(code).run(cpu,callingConv,base)); - } - - private void run(String cpu, String callingConv, String base) { + @SuppressWarnings("unchecked") + void run(String cpu, String callingConv, String base) { _code._callingConv = callingConv; // Look for CPU in fixed named place: // com.seaofnodes.simple.node.cpus."cpu"."cpu.class" String clzFile = base+"."+cpu+"."+cpu; - try { _code._mach = Class.forName(clzFile).asSubclass(Machine.class).getDeclaredConstructor(CodeGen.class).newInstance(_code); } + try { _code._mach = ((Class<Machine>) Class.forName( clzFile )).getDeclaredConstructor(new Class[]{CodeGen.class}).newInstance(_code); } catch( Exception e ) { throw new RuntimeException(e); } - _code._cCallingConv = _code._mach.cCallingConv(callingConv); - // Build global copies of common register masks. long callerSave = _code._mach.callerSave(); - long cCallerSave= _code._mach.callerSave(_code._cCallingConv); long neverSave = _code._mach. neverSave(); int maxReg = Math.min(64,_code._mach.regs().length); assert maxReg>=64 || (-1L << maxReg & callerSave)==0; // No stack slots in callerSave - assert maxReg>=64 || (-1L << maxReg & cCallerSave)==0; // No stack slots in cCallerSave _code._callerSave = new RegMask(callerSave); - _code._cCallerSave= new RegMask(cCallerSave); // Build a Return RegMask array. All returns have the following inputs: // 0 - ctrl @@ -56,18 +43,11 @@ class InstSelect { _code._uid = 1; // All new machine nodes reset numbering var map = new IdentityHashMap<Node,Node>(); _instSelect( _code._stop, map ); - _code._stop = (StopNode)map.get(_code._stop ); + _code._stop = ( StopNode)map.get(_code._stop ); StartNode start = (StartNode)map.get(_code._start); _code._start = start==null ? new StartNode(_code._start) : start; _instOuts(_code._stop,_code.visit()); _code._visit.clear(); - - // Replace the CompUnit stop (and list of functions) - // with hardware-specific ones - for( CompUnit cu : _code._compunits.values() ) { - cu._start= (StartCUNode)map.get(cu._start); - cu._stop = (StopCUNode )map.get(cu._stop ); - } } private Node _instSelect( Node n, IdentityHashMap<Node,Node> map ) { @@ -85,28 +65,14 @@ class InstSelect { } // 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=_code._mach.instSelect(n) ); - // Carry loop-tree and pre-order info across the ideal->mach transition - if( n instanceof CFGNode ncfg && x instanceof CFGNode xcfg ) { - xcfg._ltree = ncfg._ltree; - xcfg._pre = ncfg._pre; - } + map.put(n, x=select(n) ); + // Stop retains the callable body. Its lifetime edge is not a machine operand. + if( n instanceof FunPtrNode ) x._inputs.pop(); // Walk machine op and replace inputs with mapped inputs for( int i=0; i < x.nIns(); i++ ) x._inputs.set(i, _instSelect(x.in(i),map) ); - // Post selection action - if( x instanceof MachNode mach ) { - if( n instanceof ReturnNode ret ) - ((ReturnNode)mach)._fun = (FunNode)map.get(ret._fun); + if( x instanceof MachNode mach ) mach.postSelect(_code); // Post selection action - } return x; } @@ -117,10 +83,29 @@ class InstSelect { for( Node in : n._inputs ) if( in!=null ) { in._outputs.push(n); - // CallNode special: outputs are partially ordered; CallEnd in slot 0 - if( in instanceof CallNode call && n instanceof CallEndNode cend && call.out(0) != cend ) - call._outputs.swap(0,call.nOuts()-1); _instOuts(in,visit); } } + + + private Node select(Node n) { + if( n instanceof GuardNode guard ) return new GuardMach(guard); + if( n instanceof PtrToIntNode ptr ) return new PtrToIntMach(ptr); + Node op = n; + if( n instanceof ModeNode mode && mode.mode()==2 ) { + // Adapt to the existing CPU selectors. As with machine expansion, + // copy inputs without adding uses to the old graph; _instOuts + // rebuilds reverse edges after selection has finished. + op = switch(n) { + case AddNode add -> new AddFNode(add); + case SubNode sub -> new SubFNode(sub); + case MulNode mul -> new MulFNode(mul); + case DivNode div -> new DivFNode(div); + case MinusNode neg -> new MinusFNode(neg); + default -> n; + }; + op._type = n._type; + } + return _code._mach.instSelect(op); + } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Iter.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Iter.java deleted file mode 100644 index c5120370..00000000 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Iter.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.*; - -// Establish a pessimistic fixed point after parsing/loading. Later Iter runs -// reuse the worklist and types produced by the preceding phase. -abstract class Iter { - static void run(CodeGen code) { - // The phase shift closes all parser-in-progress states and freezes - // unknown external inputs. Imported nodes carry post-Opto escape - // precision, but Iter is a fresh pessimistic solve: memory escape - // summaries must restart at FULL so newly discovered private escapes - // can only be removed, never added. Seed the complete graph once. - boolean hasImports = code._externPaths != null; - code._start.walk(n -> { - if( !(n instanceof ConstantNode) ) - n._type = hasImports ? iterImportReset(n._type) : iterEscapeReset(n._type); - code.add(n); - return null; - }); - code._iter.iterate(code); - // Expensive assert. - assert !CodeGen.expensiveAssert() || Opto.fixedPointCheck(code); - } - - private static Type iterEscapeReset(Type t) { - if( t instanceof TypeMem mem ) - return mem._one ? mem : mem.makeFrom(XInt.FULL,XInt.FULL); - if( t instanceof TypeTuple tt ) { - TypeTuple rez = tt; - for( int i=0; i<tt._types.length; i++ ) { - Type tx = iterEscapeReset(tt._types[i]); - if( tx != tt._types[i] ) rez = rez.makeFrom(i,tx); - } - return rez; - } - return t; - } - - private static Type iterImportReset(Type t) { - if( t.isHigh() ) - return t.dual(); - if( t instanceof TypeMem mem ) - return TypeMem.make(mem._alias,iterImportReset(mem._t),mem._one,mem._clz,mem._final,XInt.FULL,XInt.FULL); - if( t instanceof TypeMemPtr ) - return t.makeStorage(); - if( t instanceof TypeStruct ) - return t.makeStorage(); - if( t instanceof TypeTuple tt ) { - TypeTuple rez = tt; - for( int i=0; i<tt._types.length; i++ ) { - Type tx = iterImportReset(tt._types[i]); - if( tx != tt._types[i] ) rez = rez.makeFrom(i,tx); - } - return rez; - } - return t; - } -} diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/LRG.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/LRG.java index fdde8838..0c36ae2b 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/LRG.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/LRG.java @@ -56,10 +56,6 @@ public class LRG { // Adjacent Live Range neighbors. Only valid during coloring Ary<LRG> _adj; - // Function fully containing the live range; used to find the stack frame - // layout, for spill op offsets. - final public FunNode _fun; - public int nadj() { return _adj==null ? 0 : _adj._len; } public RegMask useMask() { return _machUse==null ? null : _machUse.regmap(_uidx); } @@ -84,7 +80,7 @@ public class LRG { // More registers than neighbors boolean lowDegree() { return nadj() < _mask.size(); } - LRG( short lrg, FunNode fun ) { _lrg = lrg; _reg = -1; _fun = fun; } + LRG( short lrg ) { _lrg = lrg; _reg = -1; } boolean leader() { return _leader == null; } @@ -120,7 +116,6 @@ public class LRG { } // Union `this` and `lrg`, folding together all stats. private LRG _union( LRG lrg ) { - assert _fun == lrg._fun; // Set U-F leader lrg._leader = this; // Fold together stats 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 252b5bf0..2415203d 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java @@ -1,8 +1,6 @@ package com.seaofnodes.simple.codegen; import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.type.TypeStruct; -import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; // In-memory linking @@ -10,24 +8,16 @@ public class LinkMem { final CodeGen _code; LinkMem( CodeGen code ) { _code = code; } - public CodeGen link(Encoding enc) { + public CodeGen link() { + Encoding enc = _code._encoding; + // Patch external calls internally enc.patchGlobalRelocations(); - // Place constant pool after code, static data after constants - int code = 0; // Code start - int cpool = (enc._bits .size() + code + 15) & -16; // pad to 16 - int sdata = (enc._cpool.size() + cpool + 15) & -16; - - - // 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 = enc._data.address(enc._data.object(relo._t),cpool,sdata); - ((RIPRelSize)relo._op).patch(enc, relo._opStart, enc.opLen(relo._op), target - relo._opStart); - } + // Write any large constants into a constant pool; they + // are accessed by RIP-relative addressing. + enc.writeConstantPool(enc._bits,true); - enc._data.link(cpool,sdata); return _code; } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java index b6b3a22c..ddf5a679 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java @@ -28,7 +28,7 @@ public abstract class ListScheduler { } void free() { _n = null; FREE.push(this); } - static XSched get(Node n) { return n==null ? null : XS.get(n instanceof ProjNode && !(n.in(0) instanceof CFGNode) ? n.in(0) : n); } + static XSched get(Node n) { return n==null ? null : XS.get(n instanceof ProjNode && !(n.in(0) instanceof CallEndNode) ? n.in(0) : n); } private XSched init(CFGNode bb, Node n) { _n = n; diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Machine.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Machine.java index 3eee8fc3..7ca465ba 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Machine.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Machine.java @@ -7,42 +7,28 @@ import com.seaofnodes.simple.type.TypeInteger; abstract public class Machine { // Human readable machine name. Something like "x86-64" or "arm" or "risc5" public abstract String name(); - // Default opcode size for printing; 4 bytes for most 32-bit risc chips public int defaultOpSize() { return 4; } - // Human-readable name for a register number, e.g. "RAX" or "R0" public abstract String[] regs(); - - // Create a split op; any register to any register, including stack slots. - // The FunNode holds the stack layout after RegAlloc, which in turn - // dictates the stack slot (the frame offset) for stack splits. - public abstract SplitNode split( LRG lrg, String kind, byte round ); - + // Create a split op; any register to any register, including stack slots + public abstract SplitNode split( String kind, byte round, LRG lrg); // List of caller-save registers, as a 64bit mask public abstract long callerSave(); - public long callerSave(String conv) { return callerSave(); } - // List of never-save registers, e.g. RSP or a ZERO register if you have one public abstract long neverSave(); - // Call Argument Mask. Passed in the function signature and argument // number (2-based; 0 is for control and 1 for memory). Also passed in a 0 // for the function itself, or for *outgoing* calls, the maximum stack slot // given to the incoming function arguments (stack slots reserved for // incoming arguments). public abstract RegMask callArgMask(TypeFunPtr tfp, int arg, int maxArgSlot); - public RegMask callArgMask(TypeFunPtr tfp, int arg, int maxArgSlot, String conv) { return callArgMask(tfp,arg,maxArgSlot); } - // Return register mask, based on signature (GPR vs FPR) public abstract RegMask retMask(TypeFunPtr tfp); - // Return PC register public abstract int rpc(); - // Return a MachNode unconditional branch public abstract CFGNode jump(); - // Instruction select from ideal nodes public abstract Node instSelect( Node n ); @@ -108,17 +94,13 @@ Pre-alloc Post-Alloc SP+48 RPC // shadow slots if defined in the ABI, even if all arguments are passed in // registers. public abstract short maxArgSlot(TypeFunPtr tfp); - public short maxArgSlot(TypeFunPtr tfp, String conv) { return maxArgSlot(tfp); } - - // Calling convention used for external C symbols. - public String cCallingConv(String callingConv) { return callingConv; } // Only the declared C result width describes the physical return register. // Simple functions already return canonical full-width integer values. protected static TypeInteger cReturn(ProjNode prj) { if( prj._idx==2 && prj.in(0) instanceof CallEndNode cend && - cend.call().fptr()._type instanceof TypeFunPtr tfp && tfp.isConstant() && - CodeGen.CODE.externFunc(tfp.fidx())!=null && + cend.call().fptr()._type instanceof TypeFunPtr tfp && + CodeGen.CODE.externFunc(tfp)!=null && tfp.ret() instanceof TypeInteger ret && ret.log_size()<3 ) return ret; return null; diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Module.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Module.java deleted file mode 100644 index a5d85dbc..00000000 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Module.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.seaofnodes.simple.codegen; - - -/*** - Modules!?!?!? - - Goal: under no circumstances can you load from an uninitialized variable - (although you can see default inits for fields that have defaults). - Goal: nested tree-structured name spaces, to control complexity. - Goal: field decl order within a file is fairly flexible. - - A Module a root-level SMP file, and the full file path must be provided on - the command line. All other Simple structs are relative to this file. - - Example: "simple /user/Dave/hello.smp" - - Defines a module "hello" in directory "/user/Dave", compiles and produces - a `hello` or `hello.exe` output file, and runs it. - - A SMP file has some <finit> file-init code which is static-global. - This code is executed on "first touch" of the file (defined below). - - Example: hello.smp contains: - sys.io.p("Hello World!"); - On first-touch, will print "Hello World!". - - A SMP file is a lexical scope, and code inside it can reference those names. - - Example: hello.smp contains: - val greet = "Hello World!"; // Declare file-scoped variable - sys.io.p(greet); // Reference file-scoped variable - - Static code is NOT allowed to have forward-references AND be executed - before the <finit> finishes initializing all variables. After var - initialization, a singleton instance of the implied file struct is made, - and then <finit> code can call anything. - - Example: - sys.io.p(greet); // Illegal forward reference - val greet = "Oops!"; - - Example: - val delay = { -> greet }; // Legal forward ref, but cannot execute (yet) - val greet = "Hello World!"; // Ref is resolved - sys.io.p(delay()); // Allowed to call delay() - val nprimes = { N -> - // compute number of primes < N - } - // Can otherwise execute any code in a <finit> - sys.io.p(nprimes(100)); - - Inside a directory, you can have sibling SMP files: - File hello.smp contains: - sys.io.p(I9A.GREETING); // File hello references sibling file I9A static globals - File I9A.smp contains: - val GREETING = translate("Hello World!"); - val translate = { str msg -> ... }; - - <finit> code cannot reference sibling files (nor child files, see below) - until all static variables are initialized. This prevents cyclic - initialization bugs, where file A uses vars from file B and vice-versa. - - File Chicken.smp contains: - val lay = Egg.hatch; // Illegal, defining var 'lay' after calling sibling Egg - File Egg.smp contains: - val hatch = Chicken.lay; // Illegal - - You can make cross references, if they get delayed until after all fields - are declared. - - File Chicken.smp contains: - int lay = 2; - lay = Egg.egg; - - File Egg.smp contains: - int egg = 3; - egg = Chicken.lay; - - The problem here, of course, is that you need to touch one of Chicken or - Egg first. - - - Inside a SMP file, you can define structs; structs are also a lexical - scope. Bare code in the struct is part of the struct's <init> and only - runs when an instance is created. Structs do not have static <clinit> (nor - <finit>). Structs <init> cannot have any forward references, and locally - declared functions cannot have both forward references and be executed (or - passed along to a possibly-executing function). - - In the same directory as a SMP file, Simple allows other directories with - names matching SMP files. Inside these dirs can be more SMP files that are - lexically nested, similar to embedded structs. - - -*/ - -public abstract class Module { -} diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Opto.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Opto.java index ef222f60..c78e732e 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Opto.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Opto.java @@ -1,9 +1,9 @@ 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.Ary; - import java.util.BitSet; // Optimistic Algorithm @@ -24,25 +24,41 @@ abstract public class Opto { // get a fairly precise set of Call Graph edges between CallNodes and // FunNodes. public static void opto(CodeGen code) { + // Reset Types before running the worklist algorithm Ary<Type> oldTypes = new Ary<>(Type.class); // Used for asserts only resetToTop(code,oldTypes); - unlinkStart(code); + + // The whole-world assumption: we use it if compiling `main`, and not otherwise. + // The assumption is no unknown callers into functions: + // - during or past an Opto phase (but not before, since parse through + // pessimistic must assume somebody calls an unknown function which + // calls everybody). + // - Have a `main` to start tracing the Call Graph from + // - - True for most test cases + // - - False for lacking a main; we assume the linker will call all public functions + + // True if we can make the "whole world" assumption, which means we are + // compiling the program entry: `main`. + boolean wholeWorld = code.link(code._main) != null; + + + // If wholeWorld, remove the Start inputs to functions - they are not + // called unless they get linked (hence go dead and can be removed). + // If not wholeWorld, keep only public methods (and not the std lib). + unlinkStart(wholeWorld,code); + // OPTIMISTIC INTERPROCEDURAL SCCP sccp(code, oldTypes); - - // SCCP can leave a recursive numeric component at BOTTOM with no - // arithmetic mode selected. Resolve the whole component from all - // available int/float evidence before IterPeeps consumes the types. resolveNumericModes(code); - - if( !code._iter._work.isEmpty() ) - sccp(code, oldTypes); + if( !code._iter._work.isEmpty() ) sccp(code, oldTypes); // After SCCP add everything changed to the worklist and see if // peepholes can make more progress. moveChangesToWorklist(code, oldTypes); + + } // Reset Types before running the worklist algorithm @@ -51,6 +67,9 @@ abstract public class Opto { oldTypes.setX(x._nid,x._type); // Record original types for asserts x._type = Type.TOP; // Reset all Nodes to TOP code._iter.add(x); // Visit everybody at least once; most Nodes produce better than TOP + // Unlink all existing (conservative) linkages. + if( x instanceof CallNode call ) + call.unlink_all(); return null; } ); } @@ -59,57 +78,44 @@ abstract public class Opto { // If wholeWorld, remove the Start inputs to functions - they are not // called unless they get linked (hence go dead and can be removed). If // not wholeWorld, keep only public methods (and not the std lib). - private static void unlinkStart(CodeGen code) { - // Keep the Start & Start.ProjMem alive even as we remove all uses - for( CompUnit cu : code._compunits.values() ) { - ProjNode mem = cu._start.proj(1); - if( mem!=null ) mem.keep(); - } - - // Iterate the Stop-of-Stops - for( Node stop : code._stop._inputs ) { - for( int i=0; i<stop.nIns(); i++ ) { - ReturnNode ret = (ReturnNode)stop.in(i); - FunNode fun = ret.fun(); - // Start is a semantic unknown-caller edge. Public functions - // keep it; closed-world functions must be reached by a - // concrete Call edge discovered during SCCP. Initializers, - // including private/imported ones, are called by opaque CLInit - // obligations whose Call edges are introduced only in Lower. - if( !fun.isClz() && - !code.publicFIDX(fun.sig().fidx()) && - fun.in(1) instanceof StartNode ) { - fun.removeDeadPath(1); - // Unhook from Stop without treating the ReturnNode as - // DEAD. It stays in limbo until Opto links when a caller - // is discovered (or not, and the function is really dead). - stop._inputs.del(i--); - ret.delUse(stop); - } - // Unlink the existing conservative call linkages. - for( int j=1; j<fun.nIns(); j++ ) - if( fun.in(j) instanceof CallNode call ) - call.unlink(fun,j--); + private static void unlinkStart(boolean wholeWorld, CodeGen code) { + for( FunNode fun : code._linker ) { + if( fun != null && !fun.isDead() && !funIsPublic(code,wholeWorld,fun) ) { + assert fun.in(1)==code._start; // Start always in slot 1 + fun.removeDeadPath(1); + // By same logic, remove stop->return linkage, without + // remove the (now dead) ReturnNode; it awaits in limbo + // for the opto phase to link to some valid call + code._stop._inputs.del(code._stop._inputs.find(fun.ret())); + fun.ret().delUse(code._stop); } } } + // Function is public (callable from Start directly). + // - Always true for main + // - True for function addresses used as values + // - Otherwise false for stdlib or anonymous functions + // - Named functions use `!wholeWorld` + private static boolean funIsPublic( CodeGen code, boolean wholeWorld, FunNode fun ) { + // Always true for main + if( fun.sig().fidx() == code._main.fidx() ) return true; + // No escape analysis yet: a surviving address may be called from outside. + if( fun.hasPtr() ) return true; + // Never true for stdlib or anonymous functions + if( fun._name == null || fun._name.startsWith("sys.") ) + return false; + // Named functions (and not stdlib) are callable if not whole-world. + return !wholeWorld; + } + // As part of building a CallGraph, when opto finds a function ptr flowing // into a Call, link the Call and Fun. private static void linkCG(CodeGen code, TypeFunPtr tfp, CallNode call) { if( tfp.nargs() != call.nargs() ) return; // Error calls hit this - int[] fidxs = tfp.fidxs(); - // An unresolved forward call can still carry the infinite, inferred - // function set into Opto. There is no finite call graph to link yet; - // type checking will report the unresolved reference afterwards. This - // can yet be valid if the call is eventually dead. - // `if( false ) frefCall(args);` - if( XInt.isHigh(fidxs) ) return; - for( int fidx = XInt.next(fidxs,0); fidx >=0; fidx = XInt.next(fidxs,fidx) ) { - // unlinkStart deliberately leaves uncalled functions in a - // temporarily dead limbo. A newly discovered call revives them; - // do not use the cleanup lookup which removes dead linker entries. - FunNode fun = code._link(fidx); + for( long fidxs=tfp.fidxs(); fidxs!=0; fidxs=TypeFunPtr.nextFIDX(fidxs) ) { + int fidx = Long.numberOfTrailingZeros(fidxs); + FunNode fun = code.link(fidx); // null here means an external function; i.e. this Call // calls to an outside library and all its arguments escape. if( fun != null && !call.linked(fun) ) { @@ -120,48 +126,6 @@ abstract public class Opto { } } - // Start added some FIDXs, the same functions now can be reached from start - // and called by anybody. Add the CG edges. - private static void linkStart( CodeGen code, TypeTuple tt ) { - TypeMem tmem = (TypeMem)tt._types[1]; - int[] fidxs = tmem._escFs; - if( XInt.isHigh(fidxs) ) { - // A scalar BOTTOM can flow into memory while compiling an error - // program. It exposes all public functions, not private fields. - for( FunNode fun : code._linker ) - if( fun != null && code.publicFIDX(fun.sig().fidx()) ) - linkStart(code,fun); - return; - } - for( int fidx = XInt.next(fidxs,0); fidx >=0; fidx = XInt.next(fidxs,fidx) ) { - FunNode fun = code._linker.atX(fidx); - if( fun==null ) assert code._externFunc.containsKey(fidx); - else linkStart(code,fun); - } - } - - // Function is now reachable by unknown callers through Start. The - // default inputs must therefore use conservative declared types. - private static void linkStart( CodeGen code, FunNode fun ) { - assert !fun.isDead(); - StartCUNode start = fun._compunit._start; - if( fun.nIns() < 2 ) { - assert fun.nIns() < 2 || fun.in(1) != start; - assert !start.isDead(); - code.keepInCompUnit(fun); - // Function is reachable by any *remote* caller who gets the pointer! - fun.insertDef(1,start); - code._iter.add(fun); - for( Node p : fun._outputs ) - if( p instanceof ParmNode parm ) { - Node defalt = parm._idx==1 ? start.proj(1) : ConstantNode.seed(parm._declaredType).peephole(); - parm.insertDef(1,defalt); - code.add(parm); - } - } - } - - // The core SCCP algorithm private static void sccp( CodeGen code, Ary<Type> oldTypes ) { // Iter workList is empty at this point @@ -173,85 +137,47 @@ abstract public class Opto { count++; Type oval = n._type, nval = n.compute(); if( oval == nval ) continue; - assert oval.isa(nval) : "Non-monotonic SCCP: "+n.getClass().getSimpleName()+"#"+n._nid+" "+n+" old="+oval+" new="+nval; // Types start high and always fall - Type pesiVal = oldTypes.atX(n._nid); - assert pesiVal==null || nval.isa(pesiVal) : "SCCP below pessimistic type: "+n.getClass().getSimpleName()+"#"+n._nid+" "+n+" pessimistic="+pesiVal+" new="+nval; // Never fall worse than the pessimistic pass + assert oval.isa(nval); // Types start high and always fall + Type pesiVal = oldTypes.at(n._nid); + assert nval.isa(pesiVal); // Never fall worse than the pessimistic pass n._type = nval; - // Now we have a series of stanzas where we lazily create the Call - // Graph - adding graph edges between every call site and called - // function, including external (unknown) callers. These edges are - // not in from the start because they are *too many*; we would get - // O(n^2) edges. So all along we have been treating them as - // "virtual" CFG edges - a FunNode taking a Start input is treated - // as if every call on the planet might call it. Now we make these - // CG edges *concrete*, adding them back as we discover that a call - // calls a particular function. - - // If a TFP adds a new function input to a call, link to the new - // Fun. This adds new edges to the graph allowing argument values - // to flow from calls into functions. The added edges are - // effectively a refined Call Graph. + // If a TFP adds a new function input to a call, link to the new Fun if( nval instanceof TypeFunPtr tfp ) { for( Node use : n._outputs ) if( !use._type.isHigh() && use instanceof CallNode call && call.fptr() == n ) linkCG(code,tfp,call); } - // Link a Call which becomes alive. Like above, adds new Call - // Graph edges to the graph. - if( n instanceof CallNode call && nval == Type.CONTROL && call.fptr()._type instanceof TypeFunPtr ) + // Link a Call which becomes alive + if( n instanceof CallNode call && oval.isHigh() && !nval.isHigh() && call.fptr()._type instanceof TypeFunPtr ) linkCG(code,call.tfp(),call); - // If an otherwise-dead function pointer escapes, any future linked - // caller might find and call it. Force the function to be alive - // and called by Start. - if( n instanceof StartNode ) - linkStart(code,(TypeTuple)n._type); - // Since n._type changed, visit all output neighbors code._iter.addAll(n._outputs); n.moveDepsToWorklist(code._iter); - // Quadratic (expensive) small-step assert. - assert !CodeGen.expensiveAssert(count) || worklistCheck(code); + // Quadratic (expensive) small-step assert + assert check(code); } } - // Quadratic (expensive) fixed-point assert - public static boolean fixedPointCheck(CodeGen code) { - return fixedPointCheck(code,code._stop); - } - public static boolean fixedPointCheck(CodeGen code, Node root) { - root.walk( x -> { + // Quadratic (expensive) small-step assert + private static boolean check(CodeGen code) { + code._start.walk( x -> { Type xval = x.compute(); - assert xval == x._type; + assert xval == x._type || (x._type.isa( xval ) && code._iter._work.on( x )); return null; }); return true; } - // Quadratic (expensive) small-step assert for SCCP, where changed nodes - // may already be on the worklist. - private static boolean worklistCheck(CodeGen code) { - code._midAssert = true; - Node root = code._start; - root.walk( x -> { - Type xval = x.compute(); - assert xval == x._type || (x._type.isa( xval ) && code._iter._work.on( x )) - : "Non-monotonic or missed SCCP work: "+x.getClass().getSimpleName()+"#"+x._nid+" "+x+" old="+x._type+" new="+xval; - return null; - }); - code._midAssert = false; - return true; - } - // After SCCP add everything changed to the worklist and see if peepholes // can make more progress. private static void moveChangesToWorklist(CodeGen code, Ary<Type> oldTypes) { code._start.walk( x -> { assert x.compute() == x._type; // Hit the fixed point assert x._nid >= oldTypes._len || x._type.isa(oldTypes.at(x._nid)); // Hit at least the bottom-up type - code.add(x); + code._iter.add(x); return null; }); } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java deleted file mode 100644 index 7ce727fd..00000000 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java +++ /dev/null @@ -1,411 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.print.*; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.*; -import java.io.File; -import java.io.IOException; -import java.util.*; - -// Parse a given file, and all its dependencies -public abstract class ParseAll { - - static void parse(CodeGen code, String src) { - Parser.TYPES.clear(); - Parser.TYPES.putAll(Parser.INIT_TYPES); - if( src != null ) { - // No source file, just the source itself. - parseSource(code,code._srcName==null ? "Test" : code._srcName,src); - } else { - // Path from module root to source file - parsePath(code,code._srcName); - } - } - - // Worklist of symbols to search and files to parse - private static final Ary<CompUnit> WORK = new Ary<>(CompUnit.class); - - private static final Ary<CompUnit> NEEDS_LOAD = new Ary<>(CompUnit.class); - - // Return a compilation unit (cached or new). If new, it does not necessarily - // need to be compiled. - public static CompUnit makeCUnit( CodeGen code, CompUnit par, String name ) throws IOException { - return makeCUnit(code,par,name,null,false); - } - - private static CompUnit makeCUnit( CodeGen code, CompUnit par, String name, File obj ) throws IOException { - return makeCUnit(code,par,name,obj,obj!=null); - } - - private static CompUnit makeCUnit( CodeGen code, CompUnit par, String name, File obj, boolean external ) throws IOException { - // Slashed file name? Move leading parts into parent CUs. - int idx = name.indexOf("/"); - if( idx >= 0 ) { - String head = name.substring(0,idx); - CompUnit sub = makeCUnit(code,par,head,external ? externalObj(code,par,head) : null,external); - return makeCUnit(code,sub,name.substring(idx+1).intern(),obj,external); - } - - String fname = par==null ? name : (par._fname + "/" + name).intern(); - CompUnit cunit = code._compunits.get(fname); - if( cunit != null ) return cunit; - String cname = par==null ? name : (par._cname + "." + name).intern(); - code._compunits.put(fname, cunit = external ? new CompUnit(obj,par,fname,cname,name) : new CompUnit(par,fname,cname,name)); - return cunit; - } - - // Parse a top-level string, used for e.g. testing - static void parseSource( CodeGen code, String src ) { - parseSource(code,"Test",src); - } - - // Parse a top-level string with an explicit compilation-unit name. - static void parseSource( CodeGen code, String fname, String src ) { - CompUnit cunit = new CompUnit(fname,src); - code._compunits.put(cunit._fname,cunit); - parseAll(code,cunit); - } - - // module_root/.../src.smp is source file name. - static void parsePath(CodeGen code, String fname) { - try { - // Split and build the parent-path down to the source file. - CompUnit cu = makeCUnit( code, null, fname ); - // Compile this CompUnit - parseAll(code,cu); - } catch( IOException ioe ) { - throw new RuntimeException(ioe); - } - } - - private static void parseAll(CodeGen code, CompUnit cunit) { - WORK.clear(); // Cleanup from prior tests - NEEDS_LOAD.clear(); - // Prime the pump with the first CompUnit - WORK.add(cunit); - // Work through all unparsed compilation units and parse - while( !WORK.isEmpty() ) { - CompUnit cu = WORK.remove(0); - // No source code? Means we do not need to parse, but instead need to load the symbols as-if we parsed - if( cu._src==null || cu._src.isEmpty() ) - loadDeps(code,cu); // Load recursive dependents - else - parseOne( code, cu ); // Parse - } - - // Load all pre-compiled CompUnits - for( CompUnit cu : NEEDS_LOAD ) - loadCompUnit(code,cu); - - // Many types will have been declared cyclically either internally or - // across multiple compilation units. - - // Close over all recursive types, and upgrade TYPES - Ary<TypeStruct> ary = new Ary<>(TypeStruct.class); - for( Type t : Parser.TYPES.values() ) - if( t instanceof TypeStruct ts ) - ary.add(ts); - TypeStruct[] tss = Type.closeOver(ary.asAry(),Parser.TYPES); - for( TypeStruct ts : tss ) - Parser.TYPES.put(ts._name,ts); - for( CompUnit cu : code._compunits.values() ) - if( cu._ext==null ) - cu._clz = (TypeStruct)Parser.TYPES.get(Parser.addClzPrefix(cu._cname)); - - // Walk over all Nodes, and upgrade the internal constants to the - // closed-over types. - code._stop.walk( (Node n) -> { - n.upgradeType(Parser.TYPES); - if( n instanceof FRefNode fref ) { - boolean alloc = fref._name.endsWith(".<new>"); - String clzName = alloc - ? fref._name.substring(0,fref._name.length()-".<new>".length()).intern() - : fref._name; - TypeStruct clz = (TypeStruct)Parser.TYPES.get(clzName); - if( clz == null ) - return null; - Node con; - if( alloc ) { - Field ctor = clz.field("<new>"); - if( ctor == null || !(ctor._t instanceof TypeFunPtr tfp) ) { - String name = clzName.startsWith(Parser.CLZ) - ? clzName.substring(Parser.CLZ.length()) : clzName; - int dot = name.lastIndexOf('.'); - throw Parser.error("Unknown struct type '"+(dot==-1 ? name : name.substring(dot+1))+"'",fref._loc); - } - FunNode fun = code.link(tfp); - if( fun == null ) return null; - con = new FunPtrNode(tfp,code._start,fun.ret()).peephole(); - } else { - // Class pointer constant: - TypeMemPtr clzptr = TypeMemPtr.make((byte)2,clz,true); - con = code.con(clzptr); - } - // Replace and optimize - fref.addDef(con); - code.add(fref); - } - // Adds sort by NIDs, which just got shuffled - if( n instanceof AddNode add ) - code.add(add); - // All imported bodies are loaded; link them before Iter checks types. - if( n instanceof CallNode call && !NEEDS_LOAD.isEmpty() ) - code.add(call).idealize(); - return null; - } ); - - // The complete public interface is now known. Keep this independent - // of later graph/linker liveness so SCCP computations remain local and - // monotonic. - code.freezePublicInterface(); - } - - // Parse one Simple source code file. Add all the FRefs produced to the worklist. - private static void parseOne( CodeGen code, CompUnit cunit ) { - cunit.setStart(code); - // Parse a source file, return missing external references - Ary<FRefNode> frefs = code.P.parse(cunit); - - // Find all missing external references, or complain. This can trigger more parses. - while( !frefs.isEmpty() ) { - FRefNode fref = frefs.remove(0); - // Symbol not found in source code (or we would not be here). - // Search the module for the fref - try { - CompUnit xcunit = findCUnitModule(code,cunit,fref._name); - if( xcunit == null ) - xcunit = findCUnitExternalSimple(code,fref._name); - if( xcunit != null ) { - // Replace the FRef with the discovered class name. - fref._name = Parser.addClzPrefix(xcunit._cname); - cunit.addDep(xcunit); - } else { - throw new RuntimeException("Undefined name '" + fref._name +"'"); - } - } catch( IOException ioe ) { - throw new RuntimeException(ioe); - } - } - addRequiredTypes(code,cunit); - } - - private static void addRequiredTypes(CodeGen code, CompUnit cunit) { - LinkedHashSet<String> required = new LinkedHashSet<>(); - code._stop.walk(n -> { - TypeStruct ts = CodeGen.requiredStruct(n); - if( ts!=null && ts._fref ) required.add(ts._name); - return null; - }); - for( String typeName : required ) { - try { - CompUnit xcunit = findRequiredType(code,cunit,typeName); - if( xcunit != null ) - cunit.addDep(xcunit); - else - assert false : "expected to find required type"; - } catch( IOException ioe ) { - throw new RuntimeException(ioe); - } - } - } - - private static CompUnit findRequiredType(CodeGen code, CompUnit cunit, String typeName) throws IOException { - for( CompUnit cu = cunit; cu != null; cu = cu._par ) { - String symbol = typeName.startsWith(cu._cname+".") - ? typeName.substring(cu._cname.length()+1) - : typeName; - CompUnit xcunit = findCUnitModule(code,cu,symbol); - if( xcunit != null ) - return xcunit; - } - return findCUnitExternalSimple(code,typeName); - } - - // Load the public symbols for this CompUnit, and add dependent CompUnits to the NEEDS_LOAD list. - private static void loadDeps(CodeGen code, CompUnit cunit) { - // Load IR after parsing - if( NEEDS_LOAD.find(cunit) != -1 ) - return; // Already on the to-be-loaded list? - for( CompUnit loaded : NEEDS_LOAD ) - if( loaded._obj.equals(cunit._obj) ) return; // Another class in the same object. - try { - NEEDS_LOAD.add(cunit); - // Load dependent classes - ElfReader elf = ElfReader.load(cunit._obj,cunit); - elf.loadPublicSymbols(); - // Register bundled definitions before resolving dependencies or IR. - for( String fname : elf._units ) - makeCUnit(code,null,fname,cunit._obj); - for( String fname : elf._deps ) { - // Check CompUnit for being up to date - CompUnit sub = code._compunits.get(fname); - if( sub==null ) sub=makeCUnit(code,null,fname,depObj(code,cunit,fname)); - // If not seen before or out of date, parse the new nested cunit - if( sub._clz == null && WORK.find(sub)==-1 && NEEDS_LOAD.find(sub)==-1 ) - WORK.add(sub); - } - } catch( IOException ioe ) { - throw new RuntimeException(ioe); - } - } - - private static File depObj(CodeGen code, CompUnit cunit, String fname) { - if( cunit._smp != null ) - return null; - ElfReader elf = code.findExternalSimple(Parser.addClzPrefix(fname.replace('/','.'))); - if( elf == null ) - throw new RuntimeException("Cannot find external Simple dependency '"+fname+"'"); - return elf._file; - } - - private static File externalObj(CodeGen code, CompUnit par, String name) { - String cname = par==null ? name : par._cname + "." + name; - ElfReader elf = code.findExternalSimple(Parser.addClzPrefix(cname)); - return elf == null ? null : elf._file; - } - - private static void loadCompUnit(CodeGen code, CompUnit cunit) { - // Expecting a previously compiled object file - assert cunit._obj != null; - // Extract published symbols and put them in the internal tables; - // cross-references will pick them up there. - ElfReader elf = ElfReader.load(cunit._obj,cunit); - elf.loadSimple(code); - cunit._clz = (TypeStruct)Parser.TYPES.get(Parser.addClzPrefix(cunit._cname)); - StartNode lstart = (StartNode)elf._nodes.at(0); - StopNode lstop = ( StopNode)elf._nodes.last(); - - // Add all new loaded functions to the linker table - for( Node n : lstop._inputs ) { - StopCUNode stop = (StopCUNode)n; - StartCUNode start = stop.start(); - CompUnit cu = code._compunits.get(start._fname); - cu._start = start; - cu._stop = stop ; - - for( Node ret : stop._inputs ) { - FunNode fun = ((ReturnNode)ret).fun(); - code.link(fun); - } - } - - // Hook the new code into the existing code graph - for( Node lstopcu : lstop._inputs ) - code._stop.addDef(lstopcu); - lstart.subsume(code._start); - - // Add the new top-level loaded class - Parser.TYPES.put(cunit._clz._name,cunit._clz); - Field inst = cunit._clz.field(cunit._name); - if( inst != null && inst._t instanceof TypeFunPtr tfp && tfp._ret instanceof TypeMem mem ) - Parser.TYPES.put(cunit._cname,mem._t); - } - - // Symbol not found in source code (or we would not be here). - // Search the module for the fref - public static CompUnit findCompUnitOrThrow(CodeGen code, CompUnit cu, String symbol) { - CompUnit cu2 = findCompUnit(code,cu,symbol); - if( cu2 != null ) return cu2; - throw new RuntimeException("Undefined name '" + symbol +"'"); - } - - public static CompUnit findCompUnit(CodeGen code, CompUnit cu, String symbol) { - // Symbol not found in source code (or we would not be here). - // Search the module for the fref - try { - CompUnit local = findCUnitModule(code,cu,symbol); - if( local != null ) - return local; - return findCUnitExternalSimple(code,symbol); - } catch( IOException ioe ) { - throw new RuntimeException(ioe); - } - } - - // Find the symbol in module, starting from cunit. This searches up the nested - // class chain and sideways 1 step, so that any prefix of the class chain can - // be skipped as a shortcut. Symbol can have dotted names, which then go - // down into classes. e.g. - // A/X/Y exists. - // A/B contains X.Y, which is a shortcut for A.X.Y - - // However if A/B contains "Y", the search would be for A.B.Y then A.Y both of - // which do not exist. - private static CompUnit findCUnitModule( CodeGen code, CompUnit cunit, String symbol ) throws IOException { - if( cunit == null ) // Ran off top of module scope? - return null; // Failed search - - // Check self - if( cunit._name.equals(symbol) ) - return cunit; - - // If symbol is itself nested (has embedded dots), move the leading part - // over to the CompUnit path. So e.g.: - // CompUnit{A.B}, Symbol="X.Y" - // becomes - // CompUnit{A.B.X}, Symbol="Y" - int dotIdx = symbol.indexOf("."); - if( dotIdx != -1 ) { - String pre = symbol.substring(0,dotIdx); - String rest = symbol.substring(dotIdx+1 ); - String fname = cunit._fname + "/" + pre; - File f = new File( code._modDir + "/" + fname + ".smp" ); - if( f.exists() ) { - CompUnit sub = makeCUnit(code,cunit,pre); - // Note that "sub" itself is not put on the to-be-parsed-list. We're - // referring to a child class and do not need to parse the middles. - CompUnit dcunit = findCUnitModule(code,sub,rest); - if( dcunit != null ) - return dcunit; // Nested dot search found cunit - } - } - - // Look in mod / fname / name / symbol.smp - String fname = cunit._fname + "/" + symbol; - File f = new File( code._modDir + "/" + fname + ".smp" ); - if( f.exists() ) { - CompUnit sub = makeCUnit(code,cunit,symbol); - // If not seen before or out of date, parse the new nested cunit - if( sub._clz == null && WORK.find(sub) == -1 ) - WORK.add(sub); - return sub; - } - - // Look up the scope - return findCUnitModule( code, cunit._par, symbol ); - } - - // Search external Simple objects only. C linkage uses explicit "C" - // declarations, which produce ExternNodes and are resolved by the linker. - public static CompUnit findCUnitExternalSimple( CodeGen code, String symbol ) { - String clzName = symbol.startsWith(Parser.CLZ) ? symbol : Parser.addClzPrefix(symbol); - String cname = clzName.substring(Parser.CLZ.length()); - String fname = cname.replace('.','/'); - - CompUnit cunit = code._compunits.get(fname); - if( cunit != null ) - return cunit; - - ElfReader elf = code.findExternalSimple(clzName); - if( elf == null ) - return null; - - try { - cunit = makeCUnit(code,null,fname,elf._file); - } catch( IOException ioe ) { - throw new RuntimeException(ioe); - } - ElfReader typedElf = ElfReader.load(cunit._obj,cunit); - if( typedElf != null ) typedElf.loadPublicTypes(code); - TypeStruct clz = (TypeStruct)Parser.TYPES.get(clzName); - if( clz == null ) - Parser.TYPES.put(clzName,clz = TypeStruct.make(clzName,true)); - cunit._clz = clz; - if( WORK.find(cunit) == -1 && NEEDS_LOAD.find(cunit) == -1 ) - WORK.add(cunit); - return cunit; - } -} diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java index 48319006..777182dc 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java @@ -3,7 +3,7 @@ package com.seaofnodes.simple.codegen; // This Node rip-relative encodings has sizes that vary by delta public interface RIPRelSize { // delta is the distance from the opcode *start* to the target start. Each - // hardware target adjusts as needed. X86 measures from the opcode *end* + // hardware target adjusts as needed, X86 measures from the opcode *end* // and small jumps are 2 bytes, so they'll need to subtract 2. byte encSize(int delta); diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java index ac792988..565fa6ba 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java @@ -57,8 +57,6 @@ public class RegAlloc { public int _spills, _spillScaled; - private boolean _done; - // ----------------------- // Live ranges with self-conflicts or no allowed registers private final IdentityHashMap<LRG,String> _failed = new IdentityHashMap<>(); @@ -70,7 +68,6 @@ public class RegAlloc { _failed.put(lrg,""); } boolean success() { return _failed.isEmpty(); } - public boolean done() { return _done; } // ----------------------- @@ -79,16 +76,16 @@ public class RegAlloc { short _lrg_num; // Define a new LRG, and assign n - LRG newLRG( Node n, FunNode fun ) { + LRG newLRG( Node n ) { LRG lrg = lrg(n); if( lrg!=null ) return lrg; - lrg = new LRG(_lrg_num++, fun); + lrg = new LRG(_lrg_num++); LRG old = _lrgs.put(n,lrg); assert old==null; return lrg; } // LRG for n - public LRG lrg( Node n ) { + LRG lrg( Node n ) { LRG lrg = _lrgs.get(n); if( lrg==null ) return null; LRG lrg2 = lrg.find(); @@ -145,19 +142,18 @@ public class RegAlloc { } // Printable register number for node n - String reg( Node n ) { + String reg( Node n ) { return reg(n,null); } + String reg( Node n, FunNode fun ) { LRG lrg = _lrg(n); - return lrg==null ? null : reg(lrg); - } - String reg( LRG lrg ) { + if( lrg==null ) return null; // No register yet, use LRG if( lrg._reg == -1 ) return "V"+lrg._lrg; // Chosen machine register unless stack-slot and past RA String[] regs = _code._mach.regs(); - if( lrg._reg < regs.length || _code._phase.ordinal() <= CodeGen.Phase.RegAlloc.ordinal() || lrg._fun==null ) + if( lrg._reg < regs.length || _code._phase.ordinal() <= CodeGen.Phase.RegAlloc.ordinal() || fun==null ) return RegMask.reg(regs,lrg._reg); // Stack-slot past RA uses the frame layout logic - return "[rsp+"+lrg._fun.computeStackOffset(_code,lrg._reg)+"]"; + return "[rsp+"+fun.computeStackOffset(_code,lrg._reg)+"]"; } // ----------------------- @@ -183,7 +179,8 @@ public class RegAlloc { for( Node n : bb._outputs ) if( n instanceof NewNode nnn ) nnn.cacheRegs(_code); } - // Expensive assert: useful when chasing cross-function allocator edges. + + // Optional diagnostic for values accidentally shared across functions. //assert verifyFunctionLocalEdges(); // Top driver: repeated rounds of coloring and splitting. @@ -195,64 +192,21 @@ public class RegAlloc { round++; } postColor(); // Remove no-op spills - _done = true; } boolean verifyFunctionLocalEdges() { - for( CFGNode bb : _code._cfg ) - for( Node use : bb._outputs ) { - FunNode useFun = funOf(use); - for( int i=1; i<use.nIns(); i++ ) { - Node def = use.in(i); - if( def==null || def.isConst() || allowedCrossFunctionEdge(use,i,def) ) - continue; - FunNode defFun = funOf(def); - if( defFun != null && useFun != null && defFun != useFun ) - throw new AssertionError(crossFunctionMessage(def,defFun,use,useFun,i)); - } - } - return true; - } - - private static String crossFunctionMessage(Node def, FunNode defFun, Node use, FunNode useFun, int idx) { - StringBuilder sb = new StringBuilder("Cross-function value edge: "); - sb.append(def.uniqueName()).append("(").append(defFun.label()).append(") -> ") - .append(use.uniqueName()).append("(").append(useFun.label()).append(") input ").append(idx) - .append(" def=").append(def).append(" use=").append(use).append("\n all uses:"); - for( Node out : def.outs() ) { - FunNode outFun = funOf(out); - sb.append("\n ").append(out.uniqueName()).append("(") - .append(outFun==null ? "?" : outFun.label()).append(")"); + for( CFGNode bb : _code._cfg ) { + if( bb instanceof StartNode ) continue; + FunNode fun = bb.fun(); + for( Node use : bb._outputs ) + if( use instanceof MachNode mach && !(use instanceof ParmNode) ) + for( int i=1; i<use.nIns(); i++ ) { + Node def = use.in(i); + if( def==null || mach.regmap(i)==null || def.cfg0()==null || def.cfg0() instanceof StartNode ) continue; + assert def.cfg0().fun()==fun : "Cross-function register edge: "+def.uniqueName()+" -> "+use.uniqueName()+" input "+i; + } } - return sb.toString(); - } - - private static boolean allowedCrossFunctionEdge(Node use, int idx, Node def) { - // Function metadata/control edges are not value live ranges. - if( use instanceof FunNode || use instanceof StopNode || use instanceof StopCUNode ) - return true; - // Return-linked function pointers carry function identity/lifetime, not - // an allocator value flowing from the callee body into this function. - if( use instanceof FunPtrNode && def instanceof ReturnNode ) - return true; - // Parm defaults and call-linked argument edges are the entry protocol; - // BuildLRG also skips Parm input 1. - if( use instanceof ParmNode ) - return true; - // Linked CallEnd -> Return edges should be gone before RegAlloc, but - // diagnose allocator value edges rather than call graph metadata here. - if( use instanceof CallEndNode && def instanceof ReturnNode ) - return true; - return false; - } - - private static FunNode funOf(Node n) { - if( n instanceof ParmNode parm ) return parm.fun(); - if( n instanceof ReturnNode ret ) return ret.fun(); - if( n instanceof FunNode fun ) return fun; - if( n instanceof CFGNode cfg ) return cfg.fun(); - CFGNode cfg = n.cfg0(); - return cfg==null ? null : cfg.fun(); + return true; } private boolean graphColor(byte round) { @@ -298,7 +252,7 @@ public class RegAlloc { // Register mask when empty; split around defs and uses with limited // register masks. - if( lrg._mask.isEmpty() ) { + if( lrg._mask.isEmpty() && (!lrg._multiDef || lrg._1regUseCnt==1) ) { if( lrg._1regDefCnt <= 1 && lrg._1regUseCnt <= 1 && (lrg._1regDefCnt + lrg._1regUseCnt) > 0 && @@ -311,10 +265,6 @@ public class RegAlloc { if( splitEmptyMaskByUse(round,lrg) ) return true; } - if( lrg._mask.isEmpty() && lrg._multiDef && lrg._1regUseCnt > 1 && lrg._1regDefCnt <= 1 ) { - if( splitEmptyMaskSingleUses(round,lrg) ) - return true; - } // Generic split-by-loop depth. return splitByLoop(round,lrg); @@ -327,20 +277,18 @@ public class RegAlloc { // single-register. Split after the def and before the use. Does not // require a full pass. - // Compute def nOuts *before* replacing those uses with a split - int defNouts = ((Node)lrg._machDef).nOuts(); // Split just after def if( lrg._1regDefCnt==1 && !lrg._machDef.isClone() ) - // Force must-split, even if a prior split same block because register - // conflicts. Example: + // An earlier copy is reusable only if no intervening clobber. + // Being in the same block alone is insufficient. Example: // alloc // V1/rax - forced by alloc // alloc // V2/rax - kills prior RAX // st4 [V1],len - No good, must split around - insertAfterAndReplace( makeSplit("def/empty1",round,lrg), (Node)lrg._machDef, false/*true*/); + insertAfterAndReplace(makeSplit("def/empty1",round,lrg),(Node)lrg._machDef,false); // Split just before use - if( lrg._machUse!=null && (lrg._1regUseCnt==1 || (lrg._1regDefCnt==1 && defNouts==1)) ) + if( lrg._1regUseCnt==1 ) insertBefore((Node)lrg._machUse,lrg._uidx,"use/empty1",round,lrg); return true; } @@ -383,27 +331,6 @@ public class RegAlloc { return true; } - // Empty-mask live range with several direct single-register uses, often - // through a Phi with multiple defs. Split those uses directly instead of - // doing a loop-depth split, which might keep isolating the wrong edge. - boolean splitEmptyMaskSingleUses( byte round, LRG lrg ) { - findAllLRG(lrg); - if( _ns._len <= 5 ) - return false; - boolean progress = false; - for( Node n : _ns ) { - if( !(n instanceof MachNode mach) ) continue; - for( int i=1; i<n.nIns(); i++ ) { - RegMask use = mach.regmap(i); - if( use != null && use.size1() && lrg(n.in(i))==lrg ) { - insertBefore(n,i,"use/emptyN",round,lrg); - progress = true; - } - } - } - return progress; - } - // Narrowing preserves all earlier users of a class; new classes are disjoint. // Each use contains its first overlapping class, so one pass suffices. private static void putIntoRegClass( Ary<RegMask> rclass, RegMask rmask ) { @@ -512,11 +439,9 @@ public class RegAlloc { int min = (int)ld; int max = (int)(ld>>32); - boolean emptyMulti = lrg._mask.isEmpty() && lrg._multiDef && lrg._1regDefCnt <= 1 && - (lrg._1regUseCnt > 1 || _ns._len > 5); - // Multi-def empty-mask conflicts need the hot/deep side isolated from - // fixed register uses. Ordinary pressure splits keep the old shallow - // boundary behavior. + + // If the minLoopDepth is less than the maxLoopDepth: for-all defs and + // uses, if at minLoopDepth or lower, split after def and before use. for( Node n : _ns ) { if( n instanceof SplitNode split && !split.isNonStackSplit() && min!=max ) continue; // Ordinary splits already reach stack storage. if( n.isDead() ) continue; // Some Cloneable went dead by other spill changes @@ -527,7 +452,7 @@ public class RegAlloc { if( lrg(n)==lrg && // This is a LRG def // At loop boundary, or splitting in inner loop - (min==max || (emptyMulti ? n.cfg0().loopDepth() >= max : n.cfg0().loopDepth() <= min)) ) { + (min==max || n.cfg0().loopDepth() <= min) ) { // Cloneable constants will be cloned at uses, not after def if( !(n instanceof MachNode mach && mach.isClone()) && // Single user is already a split adjacent @@ -542,7 +467,7 @@ public class RegAlloc { // No split in front of a split if( !(n.in(i) instanceof SplitNode) && // splitting in inner loop or at loop border - (min==max || (emptyMulti ? phi.region().cfg(i).loopDepth() >= max : phi.region().cfg(i).loopDepth() <= min)) && + (min==max || phi.region().cfg(i).loopDepth() <= min) && // and not around the backedge of a loop (bad place to force a split, hard to remove) !(phi.region() instanceof LoopNode && i==2 && (phi.in(i) instanceof PhiNode pp && pp.region()==phi.region())) ) // Split before phi-use in prior block @@ -554,8 +479,7 @@ public class RegAlloc { // This is a LRG use // splitting in inner loop or at loop border if( lrgSame( n.in( i ), lrg ) && - (min == max || (n.in(i) instanceof MachNode mach && mach.isClone()) || - (emptyMulti ? n.cfg0().loopDepth() >= max : n.cfg0().loopDepth() <= min)) ) + (min == max || (n.in(i) instanceof MachNode mach && mach.isClone()) || n.cfg0().loopDepth() <= min) ) // Split before in this block insertBefore( n, i, "use/loop/use", round,lrg, false ); } @@ -604,8 +528,8 @@ public class RegAlloc { void findAllLRG( LRG lrg ) { _ns.clear(); int wd = 0; - if( lrg._machDef != null ) _ns.push((Node)lrg._machDef); - if( lrg._machUse != null ) _ns.push((Node)lrg._machUse); + if( lrg._machDef!=null ) _ns.push((Node)lrg._machDef); + if( lrg._machUse!=null ) _ns.push((Node)lrg._machUse); while( wd < _ns._len ) { Node n = _ns.at(wd++); if( lrg(n)!=lrg ) continue; @@ -666,12 +590,12 @@ public class RegAlloc { // Clone simple constants if possible Node split = def instanceof MachNode mach && mach.isClone() && (umask==null || mach.outregmap().overlap(umask)) ? mach.copy() - : _code._mach.split(lrg,kind,round); + : _code._mach.split(kind,round,lrg); _lrgs.put(split,lrg); return split; } private SplitNode makeSplit( String kind, byte round, LRG lrg ) { - SplitNode split = _code._mach.split(lrg,kind,round); + SplitNode split = _code._mach.split(kind,round,lrg); _lrgs.put(split,lrg); return split; } @@ -775,6 +699,6 @@ public class RegAlloc { if( clobbers(n,defreg) ) return false; // Clobbered } - throw Utils.TODO("Should not reach here"); + throw Utils.TODO(); } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/RegMask.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/RegMask.java index 9cb9ec16..303bba9e 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/RegMask.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/RegMask.java @@ -33,6 +33,7 @@ public class RegMask { public RegMask(long bits ) { _bits0 = bits; } public RegMask(long bits0, long bits1 ) { _bits0 = bits0; _bits1 = bits1; } public RegMask(RegMask r) { _bits0 = r._bits0; _bits1 = r._bits1; } + private RegMask() { _bits0 = _bits1 = 0; } // AND, with copy-on-write if changing RegMask and( RegMask mask ) { diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Serialize.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Serialize.java deleted file mode 100644 index 6a23252d..00000000 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Serialize.java +++ /dev/null @@ -1,672 +0,0 @@ -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.*; -import java.util.*; - -abstract public class Serialize { - - // Encoding/emulation need only machine code. File export also needs ideal - // IR for a later compiler to import; an explicit Serialize request stops here. - static void forTarget(CodeGen code, CodeGen.Phase target, boolean inMemory) { - if( target==CodeGen.Phase.Serialize || (target.ordinal()>=CodeGen.Phase.Export.ordinal() && !inMemory) ) - code.serialize(); - } - - static void serialize( CodeGen code ) { - - // Get all Nodes in a sane order - Ary<Node> nodes = nodeOrder(code); - - // Array of dependent file names - HashSet<String> xdeps = new HashSet<>(); - for( CompUnit cu : code._compunits.values() ) { - if( cu._deps != null ) - for( CompUnit cudep : cu._deps ) - xdeps.add(cudep._fname); - } - String[] deps = xdeps.toArray(new String[xdeps.size()]); - Arrays.sort(deps); - Ary<String> owned = new Ary<>(String.class); - for( CompUnit cu : code._compunits.values() ) - if( cu._src!=null ) owned.add(cu._fname); - String[] units = owned.asAry(); - Arrays.sort(units); - - // Top-level clazz being reported - String topName = code._srcName == null ? "Test" : code._srcName; - CompUnit top = code._compunits.get(topName); - TypeStruct clz = top._clz; - - // Preserve which same-named structs are the source-level declarations. - // Optimized graph types can carry sharper field states (e.g. len==0), - // and cannot be used to reconstruct this mapping during loading. - TreeMap<String,TypeStruct> named = new TreeMap<>(); - for( Type type : Parser.TYPES.values() ) - if( type instanceof TypeStruct ts ) - named.put(ts._name,ts); - TypeStruct[] pubTypes = named.values().toArray(new TypeStruct[0]); - - // Compress into bytes - BAOS baos = write(nodes, clz, pubTypes, deps, units, code._aliases, code._fidxs, code._rpcs, code._externFunc); - - // --- Expensive bijection assert - if( false ) { - // Inflate into POJOs; renumbers everything - ElfReader elf = new ElfReader(new BAOS(baos.toByteArray())); - readAll(code,elf, code._aliases, code._fidxs, code._rpcs); - BAOS baos2 = write(elf._nodes,elf._clz,elf._pubTypes,elf._deps,elf._units, code._aliases, code._fidxs, code._rpcs, code._externFunc); - - // Bi-jection - for( int i=0; i<baos.size(); i++ ) - assert baos.buf()[i]==baos2.buf()[i] : "bijection mismatch at "+i+": "+(baos.buf()[i]&0xFF)+" != "+(baos2.buf()[i]&0xFF); - assert baos.size()==baos2.size() : "bijection size mismatch: "+baos.size()+" != "+baos2.size(); - } - // --- Expensive bijection assert - - // Record serialized IR for later ELF writing - code._serial = baos; - } - - // -------------------------------------------------- - static BAOS write(Ary<Node> nodes, TypeStruct clz, TypeStruct[] pubTypes, String[] depobjs, String[] units, GlobalBits aliases, GlobalBits fidxs, GlobalBits rpcs, HashMap<Integer,String> externFunc) { - // Initialize the mapping from bits/tags to types - Type.TAGOFFS(); - - IdentityHashMap<Node,Integer> anodes = new IdentityHashMap<>(); - for( Node n : nodes ) anodes.put(n,anodes.size()+1); // 0 is null. - // The saved wrappers summarize this object's units only. Keep the - // live whole-program graph intact for subsequent lowering. - TypeTuple stopType = TypeTuple.STOP_HIGH; - for( Node n : nodes.last()._inputs ) - if( anodes.containsKey(n) ) stopType = (TypeTuple)stopType.meet(n._type); - Type startType = TypeTuple.make(Type.CONTROL,stopType.mem(),((StartNode)nodes.at(0)).arg()); - - BAOS baos = new BAOS(); - // A - Print the C0DE header. The format is not versioned yet. - baos.write('C').write('0').write('D').write('E'); - - // Count unique Types - var types = new HashMap<Type,Integer>(); - // Count published types *first*, so can align with published strings - types.put(Type.TOP,0); - types.put( clz, types.size() ); - // Have to visit the innards specially, since they did not get the - // normal recursive visit when first touching a type, because the - // published types are packed early to align with their string names so - // lookup in ElfReader can be fast. - for( int i=0; i<clz.nkids(); i++ ) - clz.at(i).gather(types); - // Gather types from all sources - for( Node n : nodes ) { - n._type.gather(types); - if( n instanceof CallEndNode cend ) cend._rpc.gather(types); - if( n instanceof FunNode fun ) fun.sig().gather(types); - if( n instanceof ParmNode parm ) parm._declaredType.gather(types); - if( n instanceof TypeNode con ) con._con .gather(types); - } - stopType.gather(types); - startType.gather(types); - // A defined class remains importable even when its initializer body dies. - for( TypeStruct ts : pubTypes ) - if( Parser.startsClzPrefix(ts._name) && - Arrays.binarySearch(units,ts._name.substring(Parser.CLZ.length()).replace('.','/'))>=0 ) - ts.gather(types); - - // Only record canonical declarations already reachable from this - // object's graph. Pulling unrelated parser types into this closure - // can include open or partially built types from other units. - Ary<TypeStruct> writtenPubs = new Ary<>(TypeStruct.class); - for( TypeStruct ts : pubTypes ) - if( types.containsKey(ts) ) - writtenPubs.add(ts); - // Radix sort by Type ID# - Type[] atypes = new Type[types.size()]; - for( Type t : types.keySet() ) - atypes[types.get(t)] = t; - - // Count all unique Strings - var strs = new HashMap<String,Integer>(); - strs.put("",0); // Null string is always 0 - // Find and count *published* Strings first - gather(strs,clz._name); - // Find and count dependent object file string names - if( depobjs != null ) - for( String fname : depobjs ) - gather(strs,fname); - for( String fname : units ) gather(strs,fname); - // Count strings from all types - for( Type t : atypes ) { - if( t instanceof Field fld ) gather(strs,fld._fname); - if( t instanceof TypeStruct ts ) gather(strs,ts . _name); - } - // Count strings used by local->global bit mappings. - aliases.gather(strs); - fidxs .gather(strs); - rpcs .gather(strs); - for( String extern : externFunc.values() ) - gather(strs,extern); - // Count strings from nodes - for( Node n : nodes ) - n.gather(strs); - // Radix sort by string ID# - String[] astrs = new String[strs.size()]; - for( String s : strs.keySet() ) - astrs[strs.get(s)] = s; - - // Check that published strings and types align - assert astrs[1] == ((TypeStruct)atypes[1])._name; - if( depobjs != null ) - for( int i=0; i<depobjs.length; i++ ) - assert astrs[1+i+1] == depobjs[i]; - - - // B - Write unique strings - baos.packed4(depobjs==null ? 0 : depobjs.length); - baos.packed4(strs.size()); - // Write in ID# order; the first depobjs.length of these are dependent strings - for( String s : astrs ) - baos.packedS(s); - - // Defined units are loader metadata, not initialization dependencies. - baos.packed4(units.length); - for( String fname : units ) baos.packed4(strs.get(fname)); - - // B.2 - Write the local->global mappings - aliases.packed(baos,strs); - fidxs .packed(baos,strs); - rpcs .packed(baos,strs); - ArrayList<Integer> externFidxs = new ArrayList<>(externFunc.keySet()); - Collections.sort(externFidxs); - baos.packed2(externFidxs.size()); - for( int fidx : externFidxs ) { - baos.packed2(fidx); - baos.packed2(strs.get(externFunc.get(fidx))); - } - - // C - Write unique Types - baos.packed4(atypes.length); - baos.packed2(writtenPubs._len); - for( TypeStruct ts : writtenPubs ) - baos.packed4(types.get(ts)); - // Write Types in ID# order, no children - for( Type t : atypes ) - t.packed(baos,strs); - // Write Types in ID# order, only child IDs - for( Type t : atypes ) { - int nkids = t.nkids(); - for( int i=0; i<nkids; i++ ) - baos.packed4(types.get(t.at(i))); - } - - - // D - Write the nodes. Since this is expected to be the bulk of the - // data, we might want to explore various options. - - // How many nodes? - baos.packed4(nodes._len); - - // First cut: 1 byte opcode, optional per-node info, includes variable nIns - for( Node n : nodes ) { - baos.packed1(n.serialTag().ordinal()); - baos.packed2(types.get(n==nodes.at(0) ? startType : n==nodes.last() ? stopType : n._type)); - n.packed(baos,strs,types,anodes); - } - // Write out the input indices packed. - for( Node n : nodes ) { - for( int j=0; j<n.nIns(); j++ ) { - if( !writeInput(n,j,anodes) ) continue; - Node def = n.in(j); - Integer idx = def==null ? Integer.valueOf(0) : anodes.get(def); - // Imported function constants retain their symbol, not a body. - if( idx==null && n instanceof FunPtrNode && j==1 ) idx=0; - assert idx != null : "Missing serialized input for "+n+" input "+j+" def "+def; - baos.packed2(idx); - } - if( n instanceof FunNode fun ) - baos.packed2(anodes.get(fun.ret())); - } - - return baos; - } - - // Omit cross-object call-graph links without unlinking the live IR. - // A parameter follows exactly the same retained callers as its function. - private static boolean writeInput(Node n, int i, IdentityHashMap<Node,Integer> nodes) { - if( n instanceof ParmNode parm ) n=parm.fun(); - if( (n instanceof FunNode || n instanceof CallEndNode) && i>0 ) - return nodes.containsKey(n.in(i)); - if( n instanceof StopNode ) return nodes.containsKey(n.in(i)); - return true; - } - - public static int nIns(Node n, IdentityHashMap<Node,Integer> nodes) { - int len=0; - for( int i=0; i<n.nIns(); i++ ) if( writeInput(n,i,nodes) ) len++; - return len; - } - public static <T> void gather( HashMap<T,Integer> strs, T s) { - if( s!=null && !strs.containsKey(s) ) - strs.put(s,strs.size()); - } - public static void gather( AryInt is, int s) { - if( is.atX(s)==0 ) { - int cnt=0; - for( int i=1; i<is._len; i++ ) - if( is._es[i] != 0 ) - cnt++; - is.setX(s,cnt+1); - } - } - - // Returned array of *public* strings only. - static void read_public_strs(ElfReader elf) { - BAOS bais = elf._bais; - Type.TAGOFFS(); - - // A - Read a header - if( bais.read()!='C' || bais.read()!='0' || bais.read()!='D' || bais.read()!='E' ) - throw new IllegalArgumentException("Unsupported serialized IR format; rebuild the object file"); - - // B - Packed read of #strings, then strings - int ndependents = bais.packed4(); // Number of dependent object files - int nstrs = bais.packed4(); // Total number of strings - String[] strs = new String[nstrs]; - for( int i=0; i<nstrs; i++ ) - strs[i] = bais.packedS(); - elf._strs = strs; - - // Collect dependent file names - elf._deps = new String[ndependents]; - for( int i=0; i<ndependents; i++ ) - elf._deps[i] = strs[1+i+1]; - elf._units = new String[bais.packed4()]; - for( int i=0; i<elf._units.length; i++ ) - elf._units[i] = strs[bais.packed4()]; - } - - // -------------------------------------------------- - record TypeRead(Type[] types, String[] strs, GlobalBits fileAliases, GlobalBits fileFidxs, GlobalBits fileRpcs) {} - - static TypeRead readTypes( CodeGen code, ElfReader elf, GlobalBits aliases, GlobalBits fidxs, GlobalBits rpcs ) { - BAOS bais = elf._bais; - // Initialize the mapping from bits/tags to types - Type.TAGOFFS(); - - // If not read the strings already, read them now - String[] strs = elf._strs; - if( strs == null ) { - // A - Read a header - if( bais.read() != 'C' || bais.read() != '0' || bais.read() != 'D' || bais.read() != 'E' ) - throw new IllegalArgumentException( "Unsupported serialized IR format; rebuild the object file" ); - - // B - Packed read of #strings, then strings - int ndependents = bais.packed4(); // Number of dependent object files - int nstrs = bais.packed4(); // Total number of strings - elf._strs = strs = new String[nstrs]; - for( int i = 0; i < nstrs; i++ ) - strs[i] = bais.packedS(); - - // Collect dependent file names - elf._deps = new String[ndependents]; - for( int i = 0; i < ndependents; i++ ) - elf._deps[i] = strs[1 + i + 1]; - elf._units = new String[bais.packed4()]; - for( int i=0; i<elf._units.length; i++ ) - elf._units[i] = strs[bais.packed4()]; - } - - // B.2 - Read the local->global mappings. Further down we will map - // Elf file-local -> global, then global -> CodeGen.CODE local - GlobalBits fileAliases = GlobalBits.packed(bais,strs); - GlobalBits fileFidxs = GlobalBits.packed(bais,strs); - GlobalBits fileRpcs = GlobalBits.packed(bais,strs); - int nexterns = bais.packed2(); - for( int i=0; i<nexterns; i++ ) { - int fileFidx = bais.packed2(); - String extern = strs[bais.packed2()]; - int fidx = fileFidx < GlobalBits.RESERVED ? fileFidx : fidxs.map(fileFidxs,fileFidx); - String old = code.externFunc(fidx); - if( old == null ) - code.externFunc(fidx,extern); - else - assert old.equals(extern); - } - - - // C - Packed read of #types, then types - int ntypes = bais.packed4(); - int npubTypes = bais.packed2(); - int[] pubTypeIds = new int[npubTypes]; - for( int i=0; i<npubTypes; i++ ) - pubTypeIds[i] = bais.packed4(); - Type[] types = Type.packed(bais,strs,ntypes, Parser.TYPES, - fileAliases,aliases, - fileFidxs ,fidxs, - fileRpcs ,rpcs); - - // aliases.set(0,0); - // Check and collect first published symbols map to TypeStructs - elf._clz = (TypeStruct)types[1/*skip zero*/]; - assert elf._clz._name==strs[1/*skip null ptr*/]; - for( Type type : types ) - if( type instanceof TypeStruct ts ) - Parser.TYPES.put(ts._name,ts); - for( Type type : types ) { - if( !(type instanceof TypeStruct ts) || !Parser.startsClzPrefix(ts._name) ) - continue; - String cname = ts._name.substring(Parser.CLZ.length()); - int dot = cname.lastIndexOf('.'); - String name = dot == -1 ? cname : cname.substring(dot+1); - Field inst = ts.field(name.intern()); - if( inst != null && inst._t instanceof TypeFunPtr tfp && tfp._ret instanceof TypeMem mem && mem._t instanceof TypeStruct its ) - Parser.TYPES.put(cname,its); - } - elf._pubTypes = new TypeStruct[npubTypes]; - for( int i=0; i<npubTypes; i++ ) { - TypeStruct ts = elf._pubTypes[i] = (TypeStruct)types[pubTypeIds[i]]; - Parser.TYPES.put(ts._name,ts); - } - return new TypeRead(types,strs,fileAliases,fileFidxs,fileRpcs); - } - - static void readAll( CodeGen code, ElfReader elf, GlobalBits aliases, GlobalBits fidxs, GlobalBits rpcs ) { - BAOS bais = elf._bais; - TypeRead tread = readTypes(code, elf, aliases, fidxs, rpcs); - Type[] types = tread.types(); - String[] strs = tread.strs(); - GlobalBits fileAliases = tread.fileAliases(); - - // D - Read the nodes - - // Number of nodes - int num = bais.packed4(); - // Packed array of nodes - Ary<Node> nodes = new Ary<>(Node.class); - CompUnit cu = null; - for( int i=0; i<num; i++ ) { - Node.Tag tag = Node.Tag.VALS[bais.packed1()]; - Type t = types[bais.packed2()]; - Node n = tag.make(bais,strs,types,fileAliases,aliases); - n._type = t; - nodes.push(n); - // A little extra for FunNodes - if( n instanceof StartCUNode startcu ) { - cu = code._compunits.get(startcu._fname==null ? (code._srcName==null ? "Test" : code._srcName) : startcu._fname); - assert cu != null; - } - if( n instanceof StopCUNode stopcu ) { - cu = null; - } - if( n instanceof FunNode fun ) { - assert cu != null; - fun._compunit = cu; - } - if( n instanceof ExternNode ext && ext._con instanceof TypeFunPtr tfp && tfp.isConstant() ) { - String old = code.externFunc(tfp.fidx()); - if( old == null ) - code.externFunc(tfp.fidx(), ext._extern); - else - assert old.equals(ext._extern); - } - } - - // Node edges - for( Node n : nodes ) { - int len = n.nIns(); - for( int i=0; i<len; i++ ) { - int idx = bais.packed2(); - Node def = idx==0 ? null : nodes.at(idx-1); - // MemMerge precise aliases are encoded by their input slot. - // Remap populated file-local slots into this compilation's - // local alias numbering; null holes carry no identity. - int local = i; - if( n instanceof MemMergeNode && i >= GlobalBits.RESERVED ) { - if( def == null ) continue; - local = aliases.map(fileAliases,i); - } - n.setDefX(local,def); - // Call's output[0] is always the CallEnd - if( n instanceof CallEndNode cend && i==0 ) { - var outs = cend.call()._outputs; - outs.swap(0,outs._len-1); - } - } - // A little extra for FunNodes - if( n instanceof FunNode fun ) { - // Return is hooked like an edge - ReturnNode ret = (ReturnNode)nodes.at(bais.packed2()-1); - fun.setRet(ret); - ret._fun = fun; - } - } - // Add edge from Start to Stop - StartNode start = (StartNode)nodes.at(0); - StopNode stop = (StopNode)nodes.last(); - start.setDef(1,stop); - - assert check(code,nodes); - elf._nodes = nodes; - } - - // Check loaded types and nodes are consistent - private static boolean check( CodeGen code, Ary<Node> nodes ) { - // Loaded nodes are complete, unlike nodes still being built by the - // parser. Recompute them with post-parse semantics. The compilation- - // unit Stop is the exception: its escape type depends on the complete - // program's public interface, which ParseAll freezes only after every - // compilation unit has been loaded. - boolean good = true; - CodeGen.Phase phase = code._phase; - code._phase = CodeGen.Phase.Opto; - try { - for( Node n : nodes ) { - if( n instanceof StopCUNode && !code.publicInterfaceFrozen() ) - continue; - // Cross-object calls are relinked only after every body has - // loaded. Their saved return types cannot be recomputed yet. - if( n instanceof CallEndNode end && end.call().fptr()._type instanceof TypeFunPtr fp && - end.nIns()<=fp.nfcns() ) continue; - Type nval = n.compute(); - if( nval != n._type ) { - System.err.println("Node "+n+" read in as "+n._type+" but computes as "+nval); - good = false; - } - } - } finally { - code._phase = phase; - } - return good; - } - - // -------------------------------------------------- - public static Ary<Node> nodeOrder( CodeGen code, CompUnit cu ) { - BitSet visit = code.visit(); - Ary<Node> nodes = new Ary<>(Node.class); - visit.set(code._stop._nid); - // Walk all listed functions, in this single compilation unit - for( FunNode fun : code._linker ) - if( fun!=null && !fun.isDead() && fun._compunit == cu ) - _funWalk(fun,nodes,visit); - - // Just constants used by the listed functions - Ary<Node> cons = new Ary<>(Node.class); - cons.add(code._start); - for( Node n : code._start._outputs ) { - if( n instanceof ConstantNode con && !visit.get(con._nid) ) { - for( Node use : con.outs() ) { - // PtrToInt is another zero-code value rooted at a global - // constant. Its array-body Add is the value used by the - // function, so include that extra link in the ownership - // test as well. - if( use instanceof PtrToIntNode ptr ) - for( Node add : ptr.outs() ) { - for( Node addUse : add.outs() ) - conUsed(add,addUse,visit,cons); - conUsed(ptr,add,visit,cons); - } - // Constant used by somebody, so flag for output - conUsed(con,use,visit,cons); - } - } - } - // StartCU and memory projection - cons.add(cu._start); - for( Node n : cu._start._outputs ) - if( n instanceof ProjNode ) cons.add(n); - // All the innards of functions - cons.addAll(nodes); - // StopCU, Stop - cons.add(cu._stop); - cons.add(code._stop); - visit.clear(); - return cons; - } - - private static void conUsed( Node con, Node use, BitSet visit, Ary<Node> cons ) { - if( use == null || !visit.get(use._nid) || visit.get(con._nid) ) return; - cons.add(con); - visit.set(con._nid); - } - - // -------------------------------------------------- - public static Ary<Node> nodeOrder( CodeGen code ) { - Ary<Node> nodes = new Ary<>(Node.class); - BitSet visit = code.visit(); - nodes.add(code._start); - - // All the global constants - for( Node n : code._start._outputs ) { - if( n.isConst() ) { - visit.set( n._nid ); - nodes.add( n ); - // Special for Cast:BOT - for( Node cast : n._outputs ) - if( cast != null && cast.nIns() == 2 && cast.in( 0 ) == null ) { - visit.set( cast._nid ); - nodes.add( cast ); - // Array pointer conversion adds the body offset after - // the zero-code PtrToInt. Both nodes are global-value - // descendants of the constant and otherwise have no - // function-local root from which to walk them. - if( cast instanceof PtrToIntNode ) - for( Node add : cast.outs() ) - if( add instanceof AddNode && !visit.get(add._nid) ) { - visit.set(add._nid); - nodes.add(add); - } - } - } - } - - // All the CompUnits - for( CompUnit cu : code._compunits.values() ) { - // Whole CompUnits have no users, went dead - if( cu._src!=null && cu._start != null && !cu._start.isDead() ) { - nodes.add(cu._start); - // A memory-free, nonreturning unit can lose its memory projection. - ProjNode mem=cu._start.proj(1); - if( mem!=null ) nodes.add(mem); - // All the functions, including internal ones, in comp-unit order - for( FunNode fun : code._linker ) - if( fun!=null && !fun.isDead() && fun._compunit == cu ) - _funWalk(fun,nodes,visit); - nodes.add(cu._stop); - } - } - - nodes.add(code._stop); - visit.clear(); - return nodes; - } - - // Walk and print whole functions at a time, in CG order - private static void _funWalk( FunNode fun, Ary<Node> rpo, BitSet visit ) { - int start = rpo._len; - _funRPO(fun,rpo,visit); - // A nonreturning function still owns a Return, even before LoopTree - // inserts synthetic exits. No loop-depth ordering is needed on disk. - _funRPO(fun.ret(),rpo,visit); - int len = rpo._len - start; - for( int i=0; i< len>>1; i++ ) - rpo.swap(start+i,start+len-1-i); - } - - // Walk and gather RPO nodes. - private static void _funRPO(Node n, Ary<Node> rpo, BitSet visit ) { - if( visit.get(n._nid) ) return; // Been there, done that - visit.set(n._nid); // Stop recursion - // Walk outputs ordered - if( n instanceof CFGNode ncfg ) { - if( n instanceof IfNode iff ) { - CProjNode c0 = iff.cproj(0); - CProjNode c1 = iff.cproj(1); - _funRPO(c0,rpo,visit); - _funRPO(c1,rpo,visit); - } else if( n instanceof StopNode ) { - // nothing - } else { - // CFG to CFG before CFG to data - for( Node use : n._outputs ) - if( use instanceof CFGNode usecfg && !ncfg.skip(usecfg) ) // Do not walk from Call to Fun - _funRPO(use,rpo,visit); - } - // Walk CFG to data eventually - for( Node use : n._outputs ) - if( !(use instanceof CFGNode) ) - _funRPO(use,rpo,visit); - } else { - // Do not walk from a non-CFG to a CFG; CFGs walk to CFGs to - // preserve CFG order. Do not walk *into* Phis; wait for the - // Region to walk the Phis (otherwise you visit some of the - // Phis ahead of others forcing the Phis to be spread around). - for( Node use : n._outputs ) { - if( use!=null && !(use instanceof CFGNode) && !(use instanceof PhiNode) ) - _funRPO(use,rpo,visit); - } - } - - // If parent is a Multi, do not add (yet). - // If self is a Multi, add self and children. - if( multiChild(n) || n instanceof CallEndNode ) { - // nothing; delay until Multi-head or CallNode - } else if( n instanceof MultiNode ) { - // Now lump all multi/projections together - printMulti(n,rpo); - } else if( n instanceof CallNode call ) { - printMulti(call.cend(),rpo); - rpo.add(call); - } else if( n instanceof RegionNode ) { - // Now lump all Phis together - int old = rpo._len; - for( Node use : n._outputs ) - if( use instanceof PhiNode ) - rpo.add(use); - // Sort by label? Want Phis before control outputs - Arrays.sort( rpo._es, old, rpo._len, (x,y) -> y.label().compareTo(x.label()) ); - rpo.add(n); - } else if( n instanceof StopNode ) { - // nothing - } else { - rpo.add(n); // Post-order add - } - } - - private static boolean multiChild(Node n) { - return n instanceof Proj || n instanceof PhiNode; - } - - private static void printMulti(Node n, Ary<Node> rpo) { - // Now lump all multi/projections together - for( Node use : n._outputs ) - rpo.add(use); - // Sort by projection order - if( !(n instanceof StartNode) ) - Arrays.sort( rpo._es,rpo._len-n.nOuts(),rpo._len, - (x,y) -> x instanceof ProjNode px && y instanceof ProjNode py ? (py.idx() - px.idx()) : (y._nid - x._nid) ); - rpo.add(n); - } - -} diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/SourceError.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/SourceError.java deleted file mode 100644 index f2f46146..00000000 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/SourceError.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.util.SB; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; - -/** Format one parser byte offset. Source is read and text built only on error. */ -public abstract class SourceError { - public static String format(CompUnit cu, int pos, String message) { - String file=cu.sourceFile(); - String name=file==null ? cu._fname+".smp" : file; - byte[] source; - try { - source=cu._src!=null ? cu._src.getBytes(StandardCharsets.UTF_8) - : file==null ? null : Files.readAllBytes(Path.of(file)); - } catch( IOException ex ) { source=null; } - if( source==null || pos<0 || pos>source.length ) - return name+": error: "+message+" (source unavailable at byte offset "+pos+")"; - - int line=1, start=0; - for( int i=0; i<pos; i++ ) - if( source[i]=='\n' ) { line++; start=i+1; } - int end=pos; - while( end<source.length && source[end]!='\n' ) end++; - if( end>start && source[end-1]=='\r' ) end--; - int point=Math.min(pos,end); - String before=new String(source,start,point-start,StandardCharsets.UTF_8); - String text=new String(source,start,end-start,StandardCharsets.UTF_8); - int column=before.codePointCount(0,before.length())+1; - SB sb=new SB().p(name).p(':').p(line).p(':').p(column).p(": error: ").p(message).p("\n "); - int width=0, caret=0; - for( int i=0; i<text.length(); ) { - if( i==before.length() ) caret=width; - int ch=text.codePointAt(i); - if( ch=='\t' ) { - int spaces=8-(width%8); - for( int j=0; j<spaces; j++ ) sb.p(' '); - width+=spaces; - } else { sb.p(new String(Character.toChars(ch))); width++; } - i+=Character.charCount(ch); - } - if( before.length()==text.length() ) caret=width; - sb.p("\n "); - for( int i=0; i<caret; i++ ) sb.p('-'); - return sb.p('^').toString(); - } -} diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/StaticData.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/StaticData.java deleted file mode 100644 index 616753d4..00000000 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/StaticData.java +++ /dev/null @@ -1,177 +0,0 @@ -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; - } - - 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/codegen/TypeCheck.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java index 9b4b0995..bc698e19 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java @@ -5,47 +5,29 @@ import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.util.Ary; -// Reject invalid optimized programs before lowering erases their source-level -// obligations. Queue surviving CLInits during the same walk for Lower's Iter. abstract class TypeCheck { static void run(CodeGen code) { - // Check for class-init cycles first; such a cycle can prevent other - // things from resolving types. The dependency graph is temporary. - String clzInitCycle = new ClassInitDependencies(code).check(); - if( clzInitCycle != null ) - throw Parser.error(clzInitCycle,null); - Ary<Node> errs = new Ary<>(Node.class); - code._stop.walk( n -> { - if( n.err() != null ) - errs.add(n); - if( n instanceof CLInitNode ) code.add(n); - return null; - }); + code._stop.walk(n -> { + if( n.err()!=null ) errs.add(n); + return null; + }); + if( errs.isEmpty() ) return; - if( !errs.isEmpty() ) { - // Prefer an originating error over a secondary error caused by a - // BOTTOM input. If all errors are downstream-poisoned, retain - // the old first-error behavior. - Node min = null; - for( Node n : errs ) - if( noBottomInputs(n) ) - { min = n; break; } - if( min==null ) - min = errs.at(0); - for( Node n : errs ) - if( n!=min && - noBottomInputs(n) && - ((n instanceof MemOpNode nmop && - min instanceof MemOpNode mmop && - nmop._loc.before(mmop._loc)) || - (n instanceof CFGNode ncfg && - min instanceof CFGNode mincfg && - ncfg.idepth() < mincfg.idepth())) ) - min = n; - if( code._obs != null ) code._obs.error(min, min.err().getMessage()); - throw min.err(); - } + // A bad Load now produces BOTTOM. Report the bad access before a + // Convert or arithmetic use that merely received its failed result. + Node first = errs.at(0); + for( Node n : errs ) + if( noBottomInputs(n) ) { first=n; break; } + for( Node n : errs ) + if( noBottomInputs(n) && + ((n instanceof MemOpNode nm && first instanceof MemOpNode fm && + nm._loc!=null && nm._loc.before(fm._loc)) || + (n instanceof CFGNode nc && first instanceof CFGNode fc && nc.idepth()<fc.idepth())) ) + first=n; + Parser.ParseException err = first.err(); + if( code._obs!=null ) code._obs.error(first,err.getMessage()); + throw err; } private static boolean noBottomInputs(Node n) { diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Unlink.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Unlink.java deleted file mode 100644 index fb1c4a6f..00000000 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Unlink.java +++ /dev/null @@ -1,112 +0,0 @@ -package com.seaofnodes.simple.codegen; - -import com.seaofnodes.simple.node.*; - -// Discard call-graph edges after LoopTree. Machine selection keeps only this -// object's bodies; ideal evaluation keeps imports so their calls can execute. -abstract class Unlink { - private static void unlinkImports(CodeGen code) { - // Imported function constants keep their symbolic identity, not an - // embedded copy of the other object's Return and body. - for( Node n : code._start._outputs.asAry() ) - if( n instanceof FunPtrNode ptr && ptr.ret()!=null && !code.owns(ptr.fun()) ) { - ptr.liftType(ptr._type); - ptr.setDef(1,null); - } - for( FunNode fun : code._linker ) { - if( fun==null || fun.isDead() || code.owns(fun) ) - continue; - for( int i=1; i<fun.nIns(); i++ ) - if( fun.in(i) instanceof CallNode call ) - call.unlink(fun,i--); - } - for( int i=0; i<code._stop.nIns(); i++ ) { - StopCUNode stop=(StopCUNode)code._stop.in(i); - CompUnit cu=code._compunits.get(stop.start()._fname); - if( cu._src==null ) code._stop.delDef(i--); - } - // The whole-object wrappers now summarize only the emitted units. - code._stop ._type=code._stop .compute(); - code._start._type=code._start.compute(); - } - - static void run(CodeGen code, boolean removeImports) { - if( removeImports ) unlinkImports(code); - - // The remaining passes assume all calls are unlinked; i.e. we are throwing - // away the Call Graph here. Functions only reachable from internal calls - // need to be re-hooked to stop/start less they go dead. - for( FunNode fun : code._linker ) { - // Already linked to start, not going dead - if( fun==null || fun.isDead() ) - continue; - - // Imported functions are not emitted by this object. Ideal - // evaluation retains them so their calls can actually execute. - if( removeImports && !code.owns(fun) ) { - while( fun.nIns() > 1 ) { - if( fun.in(1) instanceof CallNode call ) - call.unlink(fun,1); - else - fun.removeDeadPath(1); - } - StartCUNode start = fun._compunit._start; - StopCUNode stop = fun._compunit._stop; - // Unhook the Stop->Return edge also - ReturnNode ret = fun.ret(); - int idx = stop._inputs.find(ret); - if( idx != -1 ) { - stop._inputs.del(idx); - ret.delUse(stop); - } - // - if( stop.nIns()==0 ) { - idx = code._stop._inputs.find(stop); - if( idx != -1 ) - code._stop.delDef(idx); - if( start.nIns() > 0 && start.in(0) != null ) - start.setDef(0,null); - if( start.nIns() > 1 && start.in(1) != null ) - start.setDef(1,null); - } - continue; - } - - // Insert the post-Opto code-generation hook. Unlike an Opto-time - // Start input, this no longer carries unknown-caller semantics. - ReturnNode ret = fun.ret(); - StartNode start = fun._compunit._start; - assert start!=null; - if( fun.nIns() <= 1 || fun.in(1) != start ) { - fun.insertDef(1,start); - for( Node use : fun._outputs ) - if( use instanceof ParmNode parm ) - parm.insertDef(1,ConstantNode.raw(parm._type)); - if( fun._compunit._stop._inputs.find(ret) == -1 ) - fun._compunit._stop.addDef(ret); - } - // Unlink from Call - for( int i=2; i<fun.nIns(); i++ ) - ((CallNode)fun.in(i)).unlink(fun,i--); - if( fun.rpc()==null ) { // Ensure valid RPC - // First make sure a valid RPC; single-call into multi-fun will have each - // function having a single call site, so the RPC becomes a constant and - // folds - but since multiple targets, the Call never inlines. Recreate a - // valid RPC so codegen (and Eval2) understands the calling convention. - ParmNode rpc = new ParmNode( "$rpc", 0, ret.rpc()._type, fun ); - rpc.addDef(ret.rpc()); - ret.setDef( 3, rpc.init() ); - } - } - - code._stop.walk(n -> { - if( n instanceof CallEndNode cend ) - for( int i=1; i<cend.nIns(); i++ ) - if( cend.in(i) instanceof ReturnNode ) - cend.delDef(i--); - if( n instanceof CallNode call ) - call.unlink_all(); - return null; - }); - } -} diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/AddFNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/AddFNode.java new file mode 100644 index 00000000..0547e29c --- /dev/null +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/AddFNode.java @@ -0,0 +1,32 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFloat; + +import java.util.BitSet; + +public class AddFNode extends ArithFNode { + public AddFNode(Node lhs, Node rhs) { super(lhs, rhs); } + public AddFNode(ArithNode n) { super(n); } + + @Override public String label() { return "AddF"; } + + @Override public String glabel() { return "+"; } + + @Override double doOp( double x, double y ) { return x+y; } + + @Override + public Node idealize() { + // Add of 0. + Type t2 = in(2)._type; + if ( t2.isConstant() && t2 instanceof TypeFloat i && i.value()==0 ) + return in(1); + + // Move constants to RHS: con+arg becomes arg+con + if ( in(1)._type.isConstant() && !t2.isConstant() ) + return swap12(); + + return null; + } + @Override Node copy(Node lhs, Node rhs) { return new AddFNode(lhs,rhs); } +} diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/AddNode.java index a29ba73b..aa0ee024 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -7,7 +7,6 @@ public class AddNode extends ArithNode { @Override boolean allowFloat() { return true; } public AddNode(Node lhs, Node rhs) { super(null, lhs, rhs); } public AddNode(Node lhs, Node rhs, byte mode) { super(null, lhs, rhs, mode); } - @Override public Tag serialTag() { return Tag.Add; } @Override public String label() { return "Add"; } @Override public String op() { return "+"; } @@ -42,7 +41,7 @@ public class AddNode extends ArithNode { // Add of same to a multiply by 2 if( lhs==rhs && _mode==1 ) - return new ShlNode(null,lhs,CodeGen.CODE.con(1)); + return new ShlNode(null,lhs,com.seaofnodes.simple.Parser.con(1)); // Goal: a left-spine set of adds, with constants on the rhs (which then fold). diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/AndNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/AndNode.java index f02007bc..2bb289e9 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/AndNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/AndNode.java @@ -6,7 +6,6 @@ import com.seaofnodes.simple.type.TypeInteger; public class AndNode extends ArithNode { public AndNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs, (byte)1); } - @Override public Tag serialTag() { return Tag.And; } @Override public String op() { return "&"; } @Override long doOp( long x, long y ) { return x & y; } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ArithFNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ArithFNode.java new file mode 100644 index 00000000..7d219215 --- /dev/null +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ArithFNode.java @@ -0,0 +1,31 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFloat; + +public abstract class ArithFNode extends Node { + public ArithFNode(Node lhs, Node rhs) { super(null, lhs, rhs); } + protected ArithFNode(ArithNode n) { super(n); } + + @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + return p.open().n(in(1)).p(glabel()).n(in(2)).close(); + } + + abstract double doOp(double x, double y); + + @Override + public Type compute() { + Type t1 = in(1)._type, t2 = in(2)._type; + if( t1.isHigh() || t2.isHigh() ) + return Type.TOP; + if( t1 instanceof TypeFloat f1 && + t2 instanceof TypeFloat f2 ) { + if( f1.isConstant() && f2.isConstant() ) + return TypeFloat.constant(doOp(f1.value(),f2.value())); + } + return t1.meet(t2); + } + +} diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ArithNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ArithNode.java index 92ebd587..5f0862b2 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/ArithNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ArithNode.java @@ -5,7 +5,6 @@ import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.BAOS; import com.seaofnodes.simple.util.Utils; import java.util.HashMap; @@ -19,9 +18,7 @@ public abstract class ArithNode extends Node implements ModeNode { public ArithNode( Parser.Lexer loc, Node lhs, Node rhs) { super(null, lhs, rhs); _loc = loc; } public ArithNode( Parser.Lexer loc, Node lhs, Node rhs, byte mode) { this(loc,lhs,rhs); _mode=mode; } - @Override public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node,Integer> anodes) { - baos.packed1(_mode); - } + abstract String op(); @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { @@ -98,8 +95,6 @@ public abstract class ArithNode extends Node implements ModeNode { return null; if( unsupportedFloat || n instanceof BoolNode ) return Parser.error("Cannot '"+op+"' "+bad,loc); - if( n.nIns()==3 ) - return Parser.error("Cannot "+n.in(1)._type+" "+op+" "+n.in(2)._type,loc); return Parser.error("Cannot '"+op+"' "+bad,loc); } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/BoolNode.java index 73e786e3..02421e43 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -5,7 +5,6 @@ import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.BAOS; import com.seaofnodes.simple.util.Utils; import java.util.HashMap; import java.util.IdentityHashMap; @@ -18,9 +17,7 @@ abstract public class BoolNode extends Node implements ModeNode { public BoolNode(Node lhs, Node rhs) { super(null, lhs, rhs); } public BoolNode(Node lhs, Node rhs, byte mode) { super(null, lhs, rhs); _mode = mode; } - @Override public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node,Integer> anodes) { - baos.packed1(_mode); - } + abstract public String op(); // String opcode name @@ -73,7 +70,7 @@ abstract public class BoolNode extends Node implements ModeNode { public Node idealize() { // Compare of same if( in(1)==in(2) ) - return this instanceof LT ? CodeGen.CODE.ZERO : ConstantNode.make(TRUE); + return this instanceof LT ? com.seaofnodes.simple.Parser.ZERO : ConstantNode.make(TRUE); // Can we decide int vs flt? Type t1 = in(1)._type, t2 = in(2)._type; if( t1==null || t2==null ) @@ -119,8 +116,7 @@ abstract public class BoolNode extends Node implements ModeNode { public static class EQ extends BoolNode { public EQ(Node lhs, Node rhs) { super(lhs,rhs); } public EQ(Node lhs, Node rhs, byte mode) { super(lhs,rhs, mode); } - @Override public Tag serialTag() { return Tag.EQ; } - public String op() { return "=="; } + public String op() { return "=="; } TypeInteger doOp(TypeInteger i1, TypeInteger i2) { if( i1==i2 && i1.isConstant() ) return TRUE; if( i1._max < i2._min || i1._min > i2._max ) return FALSE; @@ -133,8 +129,7 @@ abstract public class BoolNode extends Node implements ModeNode { public static class NE extends BoolNode { public NE(Node lhs, Node rhs) { super(lhs,rhs); } public NE(Node lhs, Node rhs, byte mode) { super(lhs,rhs,mode); } - @Override public Tag serialTag() { return Tag.NE; } - public String op() { return "!="; } + public String op() { return "!="; } TypeInteger doOp(TypeInteger i1, TypeInteger i2) { if( i1==i2 && i1.isConstant() ) return TRUE; if( i1._max < i2._min || i1._min > i2._max ) return FALSE; @@ -147,8 +142,7 @@ abstract public class BoolNode extends Node implements ModeNode { public static class LT extends BoolNode { public LT(Node lhs, Node rhs) { super(lhs,rhs); } public LT(Node lhs, Node rhs, byte mode) { super(lhs,rhs, mode); } - @Override public Tag serialTag() { return Tag.LT; } - public String op() { return "<" ; } + public String op() { return "<" ; } TypeInteger doOp(TypeInteger i1, TypeInteger i2) { if( i1._max < i2._min ) return TRUE; if( i1._min >= i2._max ) return FALSE; @@ -161,8 +155,7 @@ abstract public class BoolNode extends Node implements ModeNode { public static class LE extends BoolNode { public LE(Node lhs, Node rhs) { super(lhs,rhs); } public LE(Node lhs, Node rhs, byte mode) { super(lhs,rhs,mode); } - @Override public Tag serialTag() { return Tag.LE; } - public String op() { return "<="; } + public String op() { return "<="; } TypeInteger doOp(TypeInteger i1, TypeInteger i2) { if( i1._max <= i2._min ) return TRUE; if( i1._min > i2._max ) return FALSE; @@ -179,8 +172,7 @@ abstract public class BoolNode extends Node implements ModeNode { super(lhs,rhs,(byte)1); assert mode==1; } - @Override public Tag serialTag() { return Tag.ULT; } - public String op() { return "u<" ; } + public String op() { return "u<" ; } TypeInteger doOp(TypeInteger i1, TypeInteger i2) { if( Long.compareUnsigned(i1._max,i2._min) < 0 ) return TRUE; if( Long.compareUnsigned(i1._min,i2._max) >= 0 ) return FALSE; @@ -188,4 +180,23 @@ abstract public class BoolNode extends Node implements ModeNode { } Node copy(Node lhs, Node rhs) { return new ULT(lhs,rhs); } } + public static class EQF extends EQ { + public EQF(Node lhs, Node rhs) { super(lhs,rhs,(byte)2); } + @Override boolean doOp(double lhs, double rhs) { return lhs == rhs; } + @Override public boolean isFloat() { return true; } + @Override Node copy(Node lhs, Node rhs) { return new EQF(lhs,rhs); } + } + public static class LTF extends LT { + public LTF(Node lhs, Node rhs) { super(lhs,rhs,(byte)2); } + @Override boolean doOp(double lhs, double rhs) { return lhs < rhs; } + @Override public boolean isFloat() { return true; } + @Override Node copy(Node lhs, Node rhs) { return new LTF(lhs,rhs); } + } + public static class LEF extends LE { + public LEF(Node lhs, Node rhs) { super(lhs,rhs,(byte)2); } + @Override boolean doOp(double lhs, double rhs) { return lhs <= rhs; } + @Override public boolean isFloat() { return true; } + @Override Node copy(Node lhs, Node rhs) { return new LEF(lhs,rhs); } + } + } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java index c6a6ff3b..8399ed09 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java @@ -1,15 +1,9 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.GlobalBits; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeMem; -import com.seaofnodes.simple.util.BAOS; -import com.seaofnodes.simple.util.Utils; - import java.util.BitSet; -import java.util.HashMap; -import java.util.IdentityHashMap; /** * A Phi for bulk memory. It covers all aliases except {@link #_aliases}; @@ -21,53 +15,24 @@ public class BulkMemPhiNode extends PhiNode { private static final BitSet EMPTY = new BitSet(); // Excluded Aliases; empty means NO exclusions. - // Directly visible by design. Empty sets share EMPTY; splitAlias performs - // copy-on-first-write and removes the node from GVN before mutation. - public BitSet _aliases; + // Each split clones the exclusions; published sets are never mutated. + public final BitSet _aliases; public BulkMemPhiNode(String label, Node... inputs) { this(label,EMPTY,inputs); } private BulkMemPhiNode(String label, BitSet aliases, Node... inputs) { - super(label, inputs); + super(label,inputs); _aliases = aliases; } - public BulkMemPhiNode(BulkMemPhiNode phi) { - super(phi,phi._label); - _aliases = phi._aliases.isEmpty() ? EMPTY : (BitSet)phi._aliases.clone(); - } - - public BulkMemPhiNode(RegionNode r, Node sample) { - super(r,sample); - _aliases = EMPTY; - } - public boolean isSplit(int alias) { return _aliases.get(alias); } - @Override public Tag serialTag() { return Tag.BulkMemPhi; } - - @Override - public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, - IdentityHashMap<Node,Integer> anodes) { - super.packed(baos,strs,types,anodes); - baos.packed2(_aliases.cardinality()); - for( int alias = _aliases.nextSetBit(0); alias >= 0; alias = _aliases.nextSetBit(alias+1) ) - baos.packed2(alias); - } + public BulkMemPhiNode(BulkMemPhiNode phi) { super(phi); _aliases = phi._aliases; } - static Node make(BAOS bais, String[] strs, Type[] types, GlobalBits fileAliases, GlobalBits aliases) { - Node[] ins = new Node[bais.packed1()]; - String label = strs[bais.packed2()]; - BitSet bits = new BitSet(); - for( int i=bais.packed2(); i>0; i-- ) { - int alias = bais.packed2(); - if( alias >= GlobalBits.RESERVED ) alias = aliases.map(fileAliases,alias); - bits.set(alias); - } - return new BulkMemPhiNode(label,bits.isEmpty() ? EMPTY : bits,ins); - } + @Override public boolean isMem() { return true; } + @Override public String label() { return "BulkPhi"+_aliases; } @Override public Type compute() { @@ -87,7 +52,7 @@ public class BulkMemPhiNode extends PhiNode { // "Peek through" a MemMerge that covers this alias set on its default for( int i=1; i<nIns(); i++ ) if( in(i) instanceof MemMergeNode mmm && canPeek(mmm) ) { - setDef(i,mmm.in(1)); + setDef(i,CodeGen.CODE.add(mmm.in(1))); return this; } @@ -99,8 +64,8 @@ public class BulkMemPhiNode extends PhiNode { return slice(alias); } - for( int i=0; i<nOuts(); i++ ) { - int alias = outputAlias(out(i)); + for( int i=0; i<_outputs.size(); i++ ) { + int alias = outputAlias(_outputs.get(i)); if( alias!=0 ) return slice(alias); } @@ -125,22 +90,19 @@ public class BulkMemPhiNode extends PhiNode { // True if the MemMerge has no precise slice still covered by this Phi. private boolean canPeek(MemMergeNode mmm) { - return missingAlias(mmm)==0; + return mmm.in(1)!=null && missingAlias(mmm)==0; } // Return an alias required by an input but still covered by this Phi, or 0. private int inputAlias(Node n) { return switch(n) { - case ParmNode p -> { assert p._idx==1; yield 0; } case ProjNode proj -> 0; - case ConstantNode con -> { - assert !(con._con instanceof TypeMem tmem && tmem._alias != 1); - yield 0; - } + case ParmNode parm -> 0; + case ConstantNode con -> 0; case MemMergeNode mmm -> missingAlias(mmm); case MemOpNode mem -> unsplit(mem._alias); case BulkMemPhiNode bulk -> missingAlias(bulk); - default -> throw Utils.TODO("Should not reach here"); + default -> throw new AssertionError("Unexpected memory input "+n); }; } @@ -150,18 +112,16 @@ public class BulkMemPhiNode extends PhiNode { // Normal graph-neighbor enqueueing flows from defs to uses, so retain // an explicit forward dependency to revisit this Phi when the user // sharpens. - use = addDepForwards(use); + addDepForwards(use); return switch(use) { case ScopeNode scope -> 0; - case ParmNode parm -> 0; case MemMergeNode mmm -> missingAlias(mmm); case BulkMemPhiNode bulk -> missingAlias(bulk); case MemOpNode mem -> unsplit(mem._alias); case MemPhiNode phi -> unsplit(phi._alias); - case EscapeNode esc -> esc.pub()==this ? unsplit(esc.fld()._alias) : 0; default -> { - assert use instanceof ReturnNode || use instanceof CallNode || use instanceof CLInitNode - : "Unexpected bulk-memory user "+use.getClass().getSimpleName()+"#"+use._nid+": "+use; + assert use instanceof ReturnNode || use instanceof CallNode || use instanceof ParmNode + : "Unexpected bulk-memory user "+use; yield 0; } }; @@ -183,9 +143,9 @@ public class BulkMemPhiNode extends PhiNode { return 0; } - // First explicit MemMerge slice still covered by this Phi. A user's slice - // may point to this Phi after a precise Phi folds; splitting is still valid. - private int missingAlias( MemMergeNode mmm ) { + // Every explicit slot requests that alias, including a partial allocation + // input whose slot still points to this bulk Phi. + private int missingAlias(MemMergeNode mmm) { for( int alias=2; alias<mmm.nIns(); alias++ ) if( mmm.in(alias)!=null && mmm.alias(alias)!=mmm.in(1) && @@ -201,7 +161,6 @@ public class BulkMemPhiNode extends PhiNode { // parallel precise Phi already exists, reuse it instead of building a // duplicate for the same Region/alias pair. Node mem = precisePhi(alias); - assert ((TypeMem)mem._type)._alias==alias; BitSet aliases = ((BitSet)_aliases.clone()); aliases.set(alias); @@ -216,7 +175,8 @@ public class BulkMemPhiNode extends PhiNode { // Do not peephole bphi here, as BulkMemPhi can recursively start a // second bulk rewrite before this one has finished. Let the worklist // discover any further splits - Node bulk = bphi.init(); + bphi.setType(TypeMem.BOT); + Node bulk = bphi; CodeGen.CODE.add(bulk); CodeGen.CODE.add(mem); return aggregate(bulk,alias,mem); @@ -226,19 +186,11 @@ public class BulkMemPhiNode extends PhiNode { // which the bulk has already excluded. The named alias is newly supplied // (or replaces its old slice). MemMergeNode aggregate(Node bulk, int alias, Node precise) { - MemMergeNode mmm = new MemMergeNode(null,bulk); + MemMergeNode mmm = new MemMergeNode(bulk); for( int old = _aliases.nextSetBit(0); old >= 0; old = _aliases.nextSetBit(old+1) ) { assert old != alias; mmm.alias(old,precisePhi(old));} mmm.alias(alias,precise); assert checkMerge(mmm); - mmm.init(); - // Slicing changes representation, not the set of values in memory. - // Newly exposed precise inputs can carry conservative bulk escape - // summaries; do not let those invent escapes absent from this Phi. - TypeMem old = (TypeMem)_type; - TypeMem mem = (TypeMem) mmm._type; - mmm._type = TypeMem.make(mem._alias,mem._t,mem._one,mem._clz, - mem._final,old._escFs,old._escAs); return mmm; } @@ -250,14 +202,14 @@ public class BulkMemPhiNode extends PhiNode { mphi = new MemPhiNode("$"+alias,alias); mphi.addDef(region()); // Due to cycles, must set before calling peephole - mphi.setType(TypeMem.BOT.makeFrom(alias)); + mphi.setType(TypeMem.make(alias,Type.BOTTOM)); for( int i=1; i<nIns(); i++ ) mphi.addDef(CodeGen.CODE.add(preciseInput(in(i),alias))); return CodeGen.CODE.add(mphi); } MemPhiNode _findPhi(int alias) { - for( Node use : region().outs() ) + for( Node use : region()._outputs ) if( use instanceof MemPhiNode mphi && mphi._alias==alias ) return mphi; return null; diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CFGNode.java index 4f225fbe..6805c1ef 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/CFGNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CFGNode.java @@ -1,11 +1,13 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.*; import com.seaofnodes.simple.util.Ary; - +import com.seaofnodes.simple.util.Utils; import java.util.BitSet; -import java.util.IdentityHashMap; +import java.util.HashMap; +import java.util.HashSet; /** Control Flow Graph Nodes * <p> @@ -101,79 +103,11 @@ public abstract class CFGNode extends Node { return lhs; } - // Safe read of the control input of a data node. - public static CFGNode safeCFG(Node n) { - if( n==null ) return null; - if( !(n.in(0) instanceof CFGNode cfg) ) return null; - return cfg._safeCFG(); - } - private CFGNode _safeCFG() { - return switch(this) { - case CLInitNode init when init.bypass() -> init.cfg0()._safeCFG(); - case CallNode call when call.cend()._folding -> call.cfg0()._safeCFG(); - case CallEndNode cend when cend._folding -> cend.cfg(1)._safeCFG(); - case FunNode fun when fun._folding -> fun.cfg(1)._safeCFG(); - case ReturnNode ret when ret.fun()._folding -> ret.cfg0()._safeCFG(); - default -> this; - }; - } - - // True when this node dominates `sub` in the current idom tree. - public boolean dominates(CFGNode sub, Node dep) { - for( CFGNode cfg=sub; cfg!=null; cfg=cfg.idom() ) - if( cfg == this ) - return true; - else if( dep != null ) dep.addDep(cfg); - return false; - } - - public static CFGNode deepest(CFGNode a, CFGNode b) { - if( a == null ) return b; - if( b == null ) return a; - return b.idepth() > a.idepth() ? b : a; - } - - public static CFGNode earlyCFG(Node n) { - return earlyCFG(n,new IdentityHashMap<>(),new BitSet()); - } - - public static CFGNode earlyCFG(Node n, IdentityHashMap<Node,CFGNode> cache, BitSet active) { - if( n == null ) return null; - CFGNode cached = cache.get(n); - if( cached != null ) return cached; - if( active.get(n._nid) ) - return safeCFG(n); - active.set(n._nid); - CFGNode early = null; - for( Node x : n._inputs ) - early = deepest(early,defCFG(x,cache,active)); - active.clear(n._nid); - cache.put(n,early); - return early; - } - - private static CFGNode defCFG(Node n, IdentityHashMap<Node,CFGNode> cache, BitSet active) { - if( n == null || n.isConst() ) return null; // no impact on early cfg - return switch( n ) { - case PhiNode phi -> phi.region(); - case CFGNode cfg -> cfg; - case ProjNode projNode -> safeCFG( n ); - default -> earlyCFG( n, cache, active ); - }; - } - - public boolean sameFun(CFGNode that) { - FunNode fun = fun(); - return fun != null && fun == that.fun(); - } - // Find nearest enclosing FunNode public FunNode fun() { CFGNode cfg = this; - while( !(cfg instanceof FunNode fun) ) { - if( cfg==null ) return null; + while( !(cfg instanceof FunNode fun) ) cfg = cfg.idom(); - } return fun; } @@ -184,13 +118,13 @@ public abstract class CFGNode extends Node { public LoopTree _ltree; public int _pre; // Pre-order numbers for loop tree finding - public static class LoopTree { - public LoopTree _par; - public final LoopNode _head; - public int _depth; + static class LoopTree { + LoopTree _par; + final LoopNode _head; + int _depth; LoopTree(LoopNode head) { _head = head; } - @Override public String toString() { return label(_head); } - public int depth() { + @Override public String toString() { return "LOOP"+_head._nid; } + int depth() { return _depth==0 ? (_par==null ? 0 : (_depth = _par.depth()+1)) : _depth; } } @@ -199,29 +133,23 @@ public abstract class CFGNode extends Node { // Tag all CFG Nodes with their containing LoopNode; LoopNodes themselves // also refer to *their* containing LoopNode, as well as have their depth. // Start contains the function bodies at depth 0; real loops add depth. - public void buildLoopTree( Ary<FunNode> funs, StopNode stop) { - _ltree = stop._ltree = CodeGen.CODE.XCTRL._ltree = new LoopTree((StartNode)this); - Ary<FunNode> work = new Ary<>(FunNode.class); - - BitSet post = new BitSet(); - int pre = 1; - _pre = pre++; - for( FunNode fun : funs ) { - if( fun!=null && !fun.isDead() && !fun._type.isHigh() ) { - work.push(fun); - if(fun.in(1) instanceof StartCUNode start && start._pre==0 ) { - start._pre = pre++; - start._ltree = start.cfg(1)._ltree = _ltree; - post.set(start._nid); - } - }; + public void buildLoopTree(StartNode start, StopNode stop) { + // Unlink all linked calls. This can remove RPC constants which + // shuffled the StartNode outputs so requires a while loop. + boolean done=false; + while(!done) { + done = true; + for( Node use : start._outputs ) + if( use instanceof FunNode fun ) + for( Node c : fun._inputs ) + if( c instanceof CallNode call ) + { call.unlink_all(); done=false; } } - for( int i=0; i<work._len; i++ ) - pre = work.at(i)._bltWalk(pre,work.at(i),stop, post, work); + _ltree = stop._ltree = Parser.XCTRL._ltree = new LoopTree((StartNode)this); + _bltWalk(2,null,stop, new BitSet()); } - - int _bltWalk( int pre, FunNode fun, StopNode stop, BitSet post, Ary<FunNode> work ) { + int _bltWalk( int pre, FunNode fun, StopNode stop, BitSet post ) { // Pre-walked? if( _pre!=0 ) return pre; _pre = pre++; @@ -235,12 +163,8 @@ public abstract class CFGNode extends Node { } // Pre-walk for( Node use : _outputs ) - if( use instanceof CFGNode usecfg ) { - if( skip( usecfg ) ) { - if( usecfg._pre==0 && usecfg instanceof FunNode fun2 ) work.add( fun2 ); - } else - pre = usecfg._bltWalk( pre, use instanceof FunNode fuse ? fuse : fun, stop, post, work ); - } + if( use instanceof CFGNode usecfg && !skip( usecfg ) ) + pre = usecfg._bltWalk( pre, use instanceof FunNode fuse ? fuse : fun, stop, post ); // Post-order work: find innermost loop LoopTree inner = null, ltree; @@ -283,7 +207,7 @@ public abstract class CFGNode extends Node { return pre; } - public boolean skip(CFGNode usecfg) { + private boolean skip(CFGNode usecfg) { // Only walk control users that are alive. // Do not walk from a Call to linked Fun's. return usecfg instanceof XCtrlNode || @@ -291,13 +215,8 @@ public abstract class CFGNode extends Node { (this instanceof ReturnNode && usecfg instanceof CallEndNode); } - public static String label( CFGNode target ) { - return switch(target) { - case StartNode start -> start.label(); - case FunNode fun -> fun.label(); - case LoopNode loop -> "LOOP"+target._nid; - default -> "L"+target._nid; - }; - } + public String label( CFGNode target ) { + return (target instanceof LoopNode ? "LOOP" : "L")+target._nid; + } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/CLInitNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CLInitNode.java deleted file mode 100644 index 7bc17c75..00000000 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/CLInitNode.java +++ /dev/null @@ -1,172 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.CompUnit; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.BAOS; -import java.util.*; - -// Track class initializers - the code just after this point expects the named -// class to be initialized. If this code path dies, then the dependency dies -// also - dead code does not force class init. -// -public class CLInitNode extends CFGNode implements MultiNode { - public Parser.Lexer _loc; // For error reporting - - // Inputs: control, whole memory, class pointer, loaded $init flag - // Results: control, whole memory, known-true flag alias memory. - // For field references, the ptr might be the dst clz ptr or not. - public CLInitNode(Parser.Lexer loc, Node ctrl, Node mem, Node ptr, Node ready) { - super(ctrl,mem,ptr,ready); - _loc = loc; - } - - CLInitNode(BAOS bais, String[] strs) { super(null,null,null,null); _loc=Parser.Lexer.unpack(bais,strs); } - @Override public Tag serialTag() { return Tag.CLInit; } - @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { - Parser.Lexer.packed(_loc,baos,strs); - } - @Override public void gather(HashMap<String,Integer> strs) { Parser.Lexer.gather(_loc,strs); } - - public CLInitNode(CLInitNode init) { - super(init); - _loc = init._loc; - } - - public Node mem() { return in(1); } - public Node ptr() { return in(2); } - public Node ready() { return in(3); } - - // The field/alias can be unresolved until forward references are loaded. - public Field flag() { - return ptr()._type instanceof TypeMemPtr tmp && Parser.startsClzPrefix(tmp._obj._name) - ? tmp._obj.field("$init") : null; - } - @Override public String label() { return "CLInit"; } - - // Best effort dst compunit, based on ptr - or null - public CompUnit cu() { - if( !(ptr()._type instanceof TypeMemPtr ptr && Parser.startsClzPrefix(ptr._obj._name)) ) - return null; - // Convert "class:sys.ary" into "sys/ary" and lookup CompUnit - String fname = ptr._obj._name.substring(6).replace(".","/"); - return CodeGen.CODE._compunits.get(fname); - } - - @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - FunNode fun = fun(); - CompUnit cu = cu(); - return p.p("CLInit ").p(fun._name).p(" -> ").p(cu==null ? "---" : cu._cname); - } - - // Block head is Start, Region, CProj, but not e.g. If, Return, Stop - public boolean blockHead() { return true; } - - private boolean instance() { - return ptr()._type==Type.NIL || - (ptr()._type instanceof TypeMemPtr tmp && tmp._obj!=TypeStruct.BOT && - !tmp._obj.isHigh() && !tmp._obj._open && !tmp._obj._fref && - !Parser.startsClzPrefix(tmp._obj._name)); - } - - public boolean bypass() { return instance() || ready()._type==TypeInteger.TRUE; } - - @Override public Type compute() { - if( in(0)._type.isHigh() || ptr()._type.isHigh() ) - return TypeTuple.make(Type.XCONTROL,TypeMem.TOP,TypeMem.TOP); - if( instance() ) - return TypeTuple.make(in(0)._type,mem()._type,mem()._type); - Field flag = flag(); - Type slice = flag==null ? TypeMem.BOT : TypeMem.make(flag._alias,TypeInteger.TRUE); - // The initializer may write arbitrary public memory. Only its flag - // has a guaranteed value on the normal continuation. - Type bulk = ready()._type.isHigh() ? TypeMem.TOP : - ready()._type==TypeInteger.TRUE ? mem()._type : TypeMem.BOT; - return TypeTuple.make(in(0)._type,bulk,slice); - } - - @Override public Node pcopy(int idx, Node dep) { - dep.addDep(ptr()); - dep.addDep(ready()); - if( instance() ) return idx==0 ? in(0) : mem(); - if( ready()._type!=TypeInteger.TRUE ) return null; - if( idx==0 ) return in(0); - if( idx==1 ) return mem(); - // Already established on the incoming path; this is a memory fact, - // not a new store to the class object. - return CodeGen.CODE.con(((TypeTuple)_type)._types[2]); - } - - @Override public Node idealize() { - if( CodeGen.CODE._phase!=CodeGen.Phase.Lower || bypass() || in(0)._type.isHigh() ) return null; - if( CodeGen.CODE._midAssert ) return this; - // Until entry arguments become arrays, ordinary initializers receive zero. - return expand(CodeGen.CODE.con(TypeInteger.ZERO)); - } - - // Called only after cycle/type checking. The initialized arm skips the - // call; both arms rejoin with whole memory and a known-true flag slice. - private Node expand(Node arg) { - FunNode target = CodeGen.CODE.link(cu()); - assert target!=null && !target.isDead(); - keep(); - Node ctrl = in(0); - Node memory = mem(); - - // A child body may assume its parent has finished. Establish that - // requirement first, then test the child's flag in the resulting memory. - CompUnit parent = cu()._par; - if( parent!=null ) { - Node ptr = CodeGen.CODE.con(TypeMemPtr.make((byte)2,parent._clz,true)); - Field flag = parent._clz.field("$init"); - Node off = CodeGen.CODE.con(parent._clz.offset(parent._clz.find("$init"))); - Node parentReady = CodeGen.CODE.add(new LoadNode(_loc,"$init",flag._alias,ctrl,memory,ptr,off).init()); - CLInitNode init = CodeGen.CODE.add(new CLInitNode(_loc,ctrl,memory,ptr,parentReady).init()); - ctrl = CodeGen.CODE.add(new CProjNode(init,0,ScopeNode.CTRL).init()); - memory = CodeGen.CODE.add(new ProjNode(init,1,ScopeNode.MEM0).init()); - } - - Node childOff = CodeGen.CODE.con(cu()._clz.offset(cu()._clz.find("$init"))); - // Read the child's flag after establishing its parent's memory effects. - Node ready = CodeGen.CODE.add(new LoadNode(_loc,"$init",flag()._alias,ctrl,memory,ptr(),childOff).init()); - - IfNode iff = CodeGen.CODE.add(new IfNode(ctrl,ready).init()); - Node done = CodeGen.CODE.add(new CProjNode(iff,0,"True").init()); - Node cold = CodeGen.CODE.add(new CProjNode(iff,1,"False").init()); - - Node fptr = new FunPtrNode(target.sig(),CodeGen.CODE._start,target.ret()).peephole(); - CallNode call = CodeGen.CODE.add(new CallNode(_loc,cold,memory,ptr(),arg,fptr).init()); - CallEndNode end = new CallEndNode(call,TypeRPC.constant(CodeGen.CODE.rpc(_loc._cu._cname))); - // This is a lowered call, after interprocedural analysis. Do not add - // new parameter flows to a body already optimized by SCCP. - CodeGen.CODE.add(end.init()); - Node called = CodeGen.CODE.add(new CProjNode(end,0,ScopeNode.CTRL).init()); - Node after = CodeGen.CODE.add(new ProjNode(end,1,ScopeNode.MEM0).init()); - RegionNode region = CodeGen.CODE.add(new RegionNode(_loc,null,done,called).init()); - Node merged = CodeGen.CODE.add(new BulkMemPhiNode(ScopeNode.MEM0,region,memory,after).init()); - - // Replace the individual outputs; no tuple-pack node or second - // representation of CLInit is needed while the projections collapse. - for( Node use : _outputs.asAry() ) { - if( use==null ) continue; // Our temporary keep. - assert use instanceof Proj; - int idx = ((Proj)use).idx(); - use.subsume(idx==0 ? region : idx==1 ? merged : CodeGen.CODE.con(((TypeTuple)_type)._types[2])); - } - CodeGen.CODE.invalidateIDepthCaches(); - return unkeep(); // Iter removes this now-unused node. - } - - // Resolve a precise flag slice even when the parser did not yet know the - // class/alias and could only install the whole-memory projection. - static Node slice(Node mem, int alias, Node dep) { - if( !(mem instanceof ProjNode proj && proj._idx==1 && proj.in(0) instanceof CLInitNode init) ) - return mem; - dep.addDep(init.ptr()); - Field flag = init.flag(); - if( flag==null || flag._alias!=alias ) return mem; - return new ProjNode(init,2,Parser.memName(alias)).peephole(); - } -} diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/CProjNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CProjNode.java index 073e4658..c62de840 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/CProjNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CProjNode.java @@ -2,16 +2,13 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.Serialize; +import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeMem; import com.seaofnodes.simple.type.TypeTuple; -import com.seaofnodes.simple.util.BAOS; +import com.seaofnodes.simple.util.Utils; -import java.util.HashMap; -import java.util.IdentityHashMap; - -public class CProjNode extends CFGNode implements Proj { +public class CProjNode extends CFGNode { // Which slice of the incoming multipart value public int _idx; @@ -25,14 +22,6 @@ public class CProjNode extends CFGNode implements Proj { _label = label; } public CProjNode(CProjNode c) { super(c); _idx = c._idx; _label = c._label; } - @Override public Tag serialTag() { return Tag.CProj; } - @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { - baos.packed1(_idx); - baos.packed2(_label==null ? 0 : strs.get(_label)); - } - static Node make( BAOS bais, String[] strs ) { - return new CProjNode(null, bais.packed1(), strs[bais.packed2()] ); - } @Override public String label() { return _label; } @@ -52,17 +41,17 @@ public class CProjNode extends CFGNode implements Proj { public Node idealize() { if( ctrl()._type instanceof TypeTuple tt ) { if( tt._types[_idx]==Type.XCONTROL ) - return CodeGen.CODE.XCTRL; // We are dead + return Parser.XCTRL; // We are dead if( ctrl() instanceof IfNode && tt._types[1-_idx]==Type.XCONTROL ) // Only true for IfNodes return ctrl().in(0); // We become our input control } // Flip a negating if-test, to remove the not - if( ctrl() instanceof IfNode iff && iff.pred() != null && addDep(iff.pred()) instanceof NotNode not ) + if( ctrl() instanceof IfNode iff && addDep(iff.pred()) instanceof NotNode not ) return new CProjNode(new IfNode(iff.ctrl(),not.in(1)).peephole(),1-_idx,_idx==0 ? "False" : "True"); // Copy of some other input - return ((MultiNode)ctrl()).pcopy(_idx,this); + return ((MultiNode)ctrl()).pcopy(_idx); } // Only called during basic-block layout, inverts a T/F CProj @@ -77,9 +66,4 @@ public class CProjNode extends CFGNode implements Proj { @Override int hash() { return _idx; } - @Override public int idx() { return _idx; } - - @Override public void gather( HashMap<String,Integer> strs ) { - Serialize.gather(strs,_label); - } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndMach.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndMach.java index 05707132..d26ea33b 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndMach.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndMach.java @@ -14,15 +14,13 @@ public class CallEndMach extends CallEndNode implements MachNode { _retMask = code._mach.retMask(call().tfp()); // Kill mask is all caller-saves, and any mirror stack slots for args // in registers. - RegMaskRW kills = (call().external() ? code._cCallerSave : code._callerSave).copy(); + RegMaskRW kills = code._callerSave.copy(); // Start of stack slots int maxReg = code._mach.regs().length; // Incoming function arg slots, all low numbered in the RA int fslot = fun()._maxArgSlot; // Killed slots for this calls outgoing args - int xslot = call().external() - ? code._mach.maxArgSlot(call().tfp(),code._cCallingConv) - : code._mach.maxArgSlot(call().tfp()); + int xslot = code._mach.maxArgSlot(call().tfp()); _xslot = (maxReg+fslot)+xslot; for( int i=0; i<xslot; i++ ) kills.set((maxReg+fslot)+i); diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java index 36a012e1..aa586b17 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java @@ -2,13 +2,9 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; -import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.*; import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.BAOS; - -import java.util.HashMap; -import java.util.IdentityHashMap; /** * CallEnd @@ -16,48 +12,17 @@ import java.util.IdentityHashMap; public class CallEndNode extends CFGNode implements MultiNode { // When set true, this Call/CallEnd/Fun/Return is being trivially inlined - boolean _folding; - // +2: must clone, so await all cleanup but OK - // +1: trivial now, so can inline without cloning and without waiting for cleanup - // 0: take a look, - // -1: somethings wrong, but might improve - // -2: never and stop asking, - private byte _inline; + private boolean _folding; + public boolean folding() { return _folding; } public final TypeRPC _rpc; - public CallEndNode(CallNode call, TypeRPC rpc) { - super(new Node[]{call}); - _rpc = rpc; - } + public CallEndNode(CallNode call) { super(new Node[]{call}); _rpc = TypeRPC.constant(_nid); } public CallEndNode(CallEndNode cend) { super(cend); _rpc = cend._rpc; } - public CallEndNode(TypeRPC rpc) { - super(new Node[0]); - _rpc = TypeRPC.constant(CodeGen.CODE._rpcs.next(rpc.rpc())); - } - private CallEndNode(int nIns, TypeRPC rpc) { - super(new Node[nIns]); - _rpc = rpc; - } - @Override public Tag serialTag() { return Tag.CallEnd; } - public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { - baos.packed1(Serialize.nIns(this,anodes)); - // Linked CallEnds depend on Return types which depend on CallEnds; - // break the cycle - baos.packed2(types.get(_type)); - baos.packed2(types.get(_rpc)); - } - static Node make( BAOS bais, Type[] types ) { - int nIns = bais.packed1(); - Type type = types[bais.packed2()]; - Node cend = new CallEndNode(nIns,(TypeRPC)types[bais.packed2()]); - cend._type = type; - return cend; - } + @Override public String label() { return "CallEnd"; } @Override public boolean blockHead() { return true; } public CallNode call() { return (CallNode)in(0); } - public boolean folding() { return _folding; } @Override public CFGNode idom(Node dep) { // Folding the idom is the one inlining Return @@ -76,261 +41,192 @@ public class CallEndNode extends CFGNode implements MultiNode { @Override public Type compute() { if( !(in(0) instanceof CallNode call) || call._type != Type.CONTROL ) - return TypeTuple.STATE.dual(); + return TypeTuple.RET.dual(); + // Mid-fold, just take the one single callers' return type + if( _folding ) return in(1)._type; // Grab the TFP and use the functions declared return type. - // If the call.fptr() is a FRef, return will be BOTTOM. Type ftype = addDep(call.fptr())._type; - if( !(ftype instanceof TypeFunPtr tfp) ) - return ftype.isHigh() ? TypeTuple.STATE.dual() : TypeTuple.STATE; - // Mid-fold, just take the one single callers' return type - if( _folding ) { - TypeTuple tt = (TypeTuple)in(1)._type; - return tt.makeFrom(2,tt.ret()); - } - - // Here, if I can figure out I've linked *all* callers, then I can meet - // across the linked returns and join with the function return type. - - // External functions have an exact FIDX but deliberately no local - // FunNode/Return edge. They conservatively crush public memory and - // produce their declared return type. A missing non-external target - // is still an unresolved/error call. - int externs = 0; - // The signature bounds widening (e.g. an i32 result stays i32). - // Use the callee signature, not FunPtrNode's live recursive range. - Type bound = tfp.ret(); - if( !XInt.isHigh(tfp.fidxs()) ) { - bound = Type.TOP; - for( int fidx = XInt.next(tfp.fidxs(),0); fidx >= 0; fidx = XInt.next(tfp.fidxs(),fidx) ) { - if( CodeGen.CODE.externFunc(fidx)!=null ) - externs++; - FunNode fun = CodeGen.CODE._link(fidx); - bound = bound.meet(fun==null ? tfp.ret() : addDep(fun).sig().ret()); + Type ret = ftype.isHigh() ? Type.TOP : Type.BOTTOM; + if( ftype instanceof TypeFunPtr tfp ) { + ret = tfp.ret(); + // Here, if I can figure out I've found *all* callers, then I can meet + // across the linked returns and join with the function return type. + if( 1+tfp.nfcns() == nIns() ) { // A linked function for every concrete function + ret = Type.TOP; + for( int i=1; i<nIns(); i++ ) { + Type tret = in(i)._type instanceof TypeTuple rtt ? rtt.ret() : in(i)._type; + ret = ret.meet(tret); + } + // Recursion is a loop in disguise. For f(x-1)+1, the result + // flows Return -> CallEnd -> Add -> Phi -> Return, without + // crossing ParmNode.compute's widening point. Widen here too, + // using the loop rule in PhiNode.compute. Keep constants and + // above-center ranges narrow, and stay within the return type. + Type oldret = _type instanceof TypeTuple tt ? tt.ret() : Type.TOP; + if( ret instanceof TypeInteger ti && ret != oldret && + !ti.isHigh() && !ti.isConstant() && + (!(oldret instanceof TypeInteger oldi) || ti._widen <= oldi._widen) ) + ret = ti.same_but_slightly_wider_than(TypeInteger.BOT).join(tfp.ret()); } } - boolean hasExtern = externs != 0; - - // If we have an error-call, e.g. wrong args, then we never link. - // Pre-Opto this looks like a missing target fcn, and we assume it will - // appear later - meanwhile, we use a conservative approx of memory - // effects. - if( (nIns()-1)+externs < tfp.nfcns() ) { - // If during Opto, assume call won't be called and thus won't return anything. - // Lowered calls deliberately have no links into optimized bodies. - boolean opto = CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Opto.ordinal() && - CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Lower.ordinal(); - Type tmem = opto ? TypeMem.TOP : TypeMem.BOT; - Type ret = opto ? Type.TOP : tfp.ret(); - // Use the same widening before and after linking a callee. - return TypeTuple.make(Type.CONTROL,tmem,widenResult(ret,bound)); - } - - // A linked function for every concrete function - TypeTuple state = hasExtern - ? TypeTuple.make(Type.CONTROL,TypeMem.BOT,tfp.ret()) - : TypeTuple.STATE.dual(); - for( int i=1; i<nIns(); i++ ) - state = (TypeTuple)state.meet(in(i)._type); - // A reachable call whose presently linked exits are all unreachable - // keeps a conservative continuation. In particular, do not let a - // transient no-return result tear down the caller before/during SCCP. - // If the call remains out-of-line, its continuation can conservatively - // contribute class-init dependencies even though it never executes. - if( state.ctl()==Type.XCONTROL ) - return TypeTuple.make(Type.CONTROL,TypeMem.TOP,Type.TOP); - // Join before widening: FunPtrNode follows the live Return type, so - // joining it afterward would undo the widening. - return state.makeFrom(2,widenResult(state.ret().join(tfp.ret()),bound)); - } - - private static Type widenResult(Type ret, Type bound) { - // Recursion is a loop in disguise. For f(x-1)+1, the result - // flows Return -> CallEnd -> Add -> Phi -> Return, without - // crossing ParmNode.compute's widening point. Widen here too, - // using the loop rule in PhiNode.compute. Keep constants and - // above-center ranges narrow. - if( ret instanceof TypeInteger ti && !ti.isHigh() && !ti.isConstant() ) - ret = ti.same_but_slightly_wider_than().join(bound); - return ret; + return TypeTuple.make(call._type,TypeMem.BOT,ret); } @Override public Node idealize() { - return null; - } - public byte maybeInline() { - byte inline = inlineCandidate(); - if( CodeGen.CODE._midAssert ) - return inline; - _inline = inline; - if( _inline > 0 ) - doInline(); + // Trivial inlining: call site calls a single function; single function + // is only called by this call site. + if( !_folding && nIns()==2 && in(0) instanceof CallNode call ) { + Node fptr = call.fptr(); + if( fptr.nOuts() == 1 && // Only user is this call + fptr instanceof ConstantNode && // We have an immediate call + // Function is being called, and its not-null + fptr._type instanceof TypeFunPtr tfp && tfp.notNull() && + // Arguments are correct + call.err()==null ) { + ReturnNode ret = (ReturnNode)in(1); + FunNode fun = ret.fun(); + // Expecting Start, and the Call + if( fun.nIns()==3 ) { + assert fun.in(1) instanceof StartNode && fun.in(2)==call; + // Disallow self-recursive inlining (loop unrolling by another name). + // Disallow if still folding other things, as it makes other + // dependency checks carry long chains of half-folded calls. + // Dead control may end the walk before an enclosing function. + CFGNode idom = call; + while( idom!=null ) { + idom = idom.idom(); + if( idom instanceof FunNode ) break; + if( idom instanceof CallEndNode cend && cend._folding ) break; + } + // Inline? + if( idom instanceof FunNode fun2 && fun2 != fun && !fun2._folding ) { + // Trivial inline: rewrite + _folding = true; + // Rewrite Fun so the normal RegionNode ideal collapses + fun._folding = true; + fun.setDef(1,Parser.XCTRL); // No default/unknown StartNode caller + fun.setDef(2,call.ctrl()); // Bypass the Call; + fun.ret().setDef(3,null); // Return is folding also + CodeGen.CODE.addAll(fun._outputs); + // Repeat defs 1 layer down, for users of Parm (Phis) + for( Node parm : fun._outputs ) + if( parm instanceof ParmNode ) + CodeGen.CODE.addAll(parm._outputs); + + // Inlining immediately blows all cache idepth fields past the inline point. + // Bump the global version number invalidating them en-masse. + CodeGen.CODE.invalidateIDepthCaches(); + return this; + } else if( idom!=null ) { + addDep(idom); + } + } else { + addDep(fun); + } + } else { // Function ptr has multiple users (so maybe multiple call sites) + addDep(fptr); + } + } - return _inline; + return null; } - // Check if candidate right now, sets no flags - private byte inlineCandidate() { - if( _inline < -2 ) return -2; // Never - if( _folding ) return -2; // No, and will collapse anyways - if( !(in(0) instanceof CallNode call) ) return -2; // malformed because mid-death - - if( nIns()!=2 ) return -1; // Not exactly 1 link, but can change - - // Have exactly 1 linked function + // Clone only tiny, straight-line boilerplate, after ordinary peeps settle. + // Larger bodies still use the existing single-caller, non-cloning inline. + public boolean inlineSmall() { + if( isDead() || _folding || nIns()!=2 || !(in(0) instanceof CallNode call) ) return false; + Node fptr = call.fptr(); + if( !(fptr instanceof ConstantNode) || + !(fptr._type instanceof TypeFunPtr tfp && tfp.notNull() && tfp.isConstant()) ) + { addDep(fptr); return false; } + if( call.err()!=null ) return false; ReturnNode ret = (ReturnNode)in(1); FunNode fun = ret.fun(); + if( fun._folding ) return false; + int len = smallBody(fun); + if( len==0 ) return false; + // All callers consume the three normal return projections. + for( Node use : _outputs ) + if( !((use instanceof ProjNode p && p._idx>=0 && p._idx<3) || + (use instanceof CProjNode cp && cp._idx==0)) ) return false; + + // Substitute control and arguments directly; no new function identity, + // parameter Phis, or return address is needed for this straight line. + Node[] copy = new Node[len]; + for( int i=0; i<len; i++ ) { + Node n = TINYBODY[i]; + copy[i] = n==fun ? call.ctrl() : + n instanceof ParmNode parm ? (parm._idx==0 ? null : call.arg(parm._idx)) : + n==ret ? null : n.copyEmpty(); + } + for( int i=0; i<len; i++ ) { + Node n = TINYBODY[i]; + if( n instanceof StoreNode || n instanceof MemMergeNode ) { + for( Node in : n._inputs ) copy[i].addDef(mapped(in,copy)); + CodeGen.CODE.add(copy[i]); + } + } + Node[] result = new Node[3]; + for( int i=0; i<3; i++ ) result[i] = mapped(ret.in(i),copy).keep(); + keep(); + call.unlink_all(); + CodeGen.CODE.add(fun); + CodeGen.CODE.addAll(fun._outputs); + // subsume kills each projection; retain CallEnd until all are replaced. + while( nOuts()>1 ) { + Node use = out(0)==null ? out(1) : out(0); + int idx = use instanceof ProjNode p ? p._idx : ((CProjNode)use)._idx; + CodeGen.CODE.addAll(use._outputs); + use.subsume(result[idx]); + } + unkill(); + for( Node n : result ) n.unkill(); + CodeGen.CODE.invalidateIDepthCaches(); + return true; + } - // Preserve initializer boundaries until post-Opto cycle validation. - if( fun.isClz() ) return -2; - - // Heuristic forced inlining turned off via name - if( fun._name != null && - fun._name.endsWith("_noInline") ) - return -2; // Never, stop asking - - // Disallow self-recursive inlining (loop unrolling by another name). - for( int i=1; i < fun.nIns(); i++ ) - if( fun.cfg(i).fun()==fun ) // Check for linked call input inside "fun" - { addDep(fun); return -1; } // No, but can lose the recursive edge - - // Things that might improve, but not now a candidate - Node fptr = call.fptr(); - // Constant Function is being called, and its not-null - if( !(fptr._type instanceof TypeFunPtr tfp && tfp.notNull() && tfp.isConstant()) ) - { addDep(fptr); return -1; } - - // Arguments are correct - if( call.nargs()!=tfp.nargs() || !goodArgs(call,tfp) ) - { addDep(fptr); return -1; } - - // Encouraged inlining because small size and constructor. - int maxSize = fun.inlineLimit(); - // A large parse can leave only a tiny initializer. Recognize that - // and await cleanup to shrink the body. - if( fun._approxUIDs >= maxSize ) { - if( !smallBody(fun) ) return -1; - } else if( fun.body().cardinality() >= maxSize ) // Expensive size check - return -1; // Try later (if cleanup makes the real body small enough) - - // Can be cloned but not trivial - if( fun.nIns() > 2 ) { addDep(fun); return 2; } - - // A trivial candidate right now - return 1; + private static Node mapped(Node n, Node[] copy) { + for( int i=0; i<copy.length; i++ ) + if( TINYBODY[i]==n ) return copy[i]; + return n; // Constants outside the body are shared. } - // Bounded, conservative recognition of straight-line initializer boilerplate. - // Walk uses from the entry and stop at Return, never following callers or - // constant inputs. This also avoids scanning sparse MemMerge input arrays. - // Watch only this small prefix, including an unsupported node that may fold. + // Bounded recognition of initializer boilerplate. Walk uses from the entry + // and stop at Return, never following callers or constant inputs. This also + // avoids scanning sparse MemMerge input arrays while a large body shrinks. + // Watch this small prefix, including an unsupported node that may fold. private static final Node[] TINYBODY = new Node[15]; // Tiny worklist - private boolean smallBody(FunNode fun) { + private int smallBody(FunNode fun) { ReturnNode ret = fun.ret(); for( Node in : ret._inputs ) if( in!=null ) addDep(in); - if( ret.ctrl()!=fun ) return false; + if( ret.ctrl()!=fun ) return 0; TINYBODY[0] = fun; int len=1; for( int i=0; i<len; i++ ) { Node n = TINYBODY[i]; addDep(n); if( n==ret ) continue; - if( n instanceof ParmNode parm && parm.region()!=fun ) return false; + if( n instanceof ParmNode parm && parm.region()!=fun ) return 0; if( n!=fun && !(n instanceof ParmNode) && !(n instanceof StoreNode) && !(n instanceof MemMergeNode) ) - return false; // Something unexpected - if( n.nOuts()>TINYBODY.length ) return false; // Too big + return 0; + if( n.nOuts()>TINYBODY.length ) return 0; for( Node use : n._outputs ) { if( use==null ) continue; int j=0; while( j<len && TINYBODY[j]!=use ) j++; - if( j<len ) continue; // Already in TINYBODY - if( len==TINYBODY.length ) { addDep(use); return false; } // Too big - TINYBODY[len++] = use; // Add to worklist + if( j<len ) continue; + if( len==TINYBODY.length ) { addDep(use); return 0; } + TINYBODY[len++] = use; } } - return true; - } - - public void doInline() { - assert _inline > 0; // Only called when possible to inline - - ReturnNode ret = (ReturnNode)in(1); - FunNode fun = ret.fun(); - CallNode call = call(); - assert fun.sig().isa(call.fptr()._type); - - // Clone first - if( _inline==2 ) { - assert !CodeGen.CODE._midAssert; // Triggered inlining - // Remove the existing function linkage - call.unlink_all(); - // Clone the function body - FunNode fun2 = fun.copyBody(); - // Call uses the unique new function - Node fptr2 = new FunPtrNode(fun2.sig(),CodeGen.CODE._start,fun2.ret()).peephole(); - call.setDef(call.nIns()-1,fptr2); - // Link to the new function - call.link(fun2); - fun = fun2; - } - - // Trivial inlining: call site calls a single function; single function - // is only called by this call site. - doTrivialInlining(fun); - } - - boolean goodArgs( CallNode call, TypeFunPtr tfp ) { - for( int i=0; i<tfp.nargs(); i++ ) { - Node arg = call.arg(i+2); - // The graph's function-pointer type can still contain open forward - // references here. Compare against its recursively upgraded form, - // so deep mutability requirements participate before inlining. - Type formal = tfp.arg(i).upgradeType(Parser.TYPES); - if( !arg._type.isa(formal) || !arg._type.accessISA(formal) ) - { addDep(arg); return false; } - } - return true; + return len; } - - // Do trivial inlining. Inlining does not need to clone code, merely - // triggers folding the Call/Fun and Return/CallEnd away. - private boolean doTrivialInlining( FunNode fun ) { - assert !CodeGen.CODE._midAssert; // Triggered inlining - // Trivial inline: rewrite - _folding = true; - // Rewrite Fun so the normal RegionNode ideal collapses - fun._folding = true; - fun.setDef(1,call().ctrl());// Bypass the Call; - fun.ret().setDef(3,null); // Return is folding also - - CodeGen.CODE.addAll(fun._outputs); - // Repeat defs 1 layer down, for users of Parm (Phis) - for( Node parm : fun._outputs ) - if( parm instanceof ParmNode ) - CodeGen.CODE.addAll(parm._outputs); - - // Inlining immediately blows all cache idepth fields past the inline point. - // Bump the global version number invalidating them en-masse. - CodeGen.CODE.invalidateIDepthCaches(); - // More peeps - CodeGen.CODE.add(fun); - CodeGen.CODE.add(this); - CodeGen.CODE.addAll(_outputs); - CodeGen.CODE.addAll(_inputs); - return true; // Always return true - } - - - @Override public Node pcopy(int idx, Node dep) { + @Override public Node pcopy(int idx) { return _folding ? in(1).in(idx) : null; } - - @Override public Node copy() { - return _folding ? super.copy() : new CallEndNode(_rpc); - } - } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/CallNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CallNode.java index 71f6d146..b2708531 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/CallNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CallNode.java @@ -2,17 +2,12 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; +import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeFunPtr; -import com.seaofnodes.simple.type.TypeMem; -import com.seaofnodes.simple.type.TypeRPC; -import com.seaofnodes.simple.type.XInt; -import com.seaofnodes.simple.util.BAOS; import com.seaofnodes.simple.util.Utils; -import java.util.HashMap; -import java.util.IdentityHashMap; /** * Call @@ -22,18 +17,10 @@ public class CallNode extends CFGNode { // Source location for late reported errors public final Parser.Lexer _loc; - public CallNode(Parser.Lexer loc, Node... nodes) { super(nodes); _loc=loc; } + public CallNode(Parser.Lexer loc, Node... nodes) { super(nodes); _loc = loc; } public CallNode(CallNode call) { super(call); _loc = call._loc; } - @Override public Tag serialTag() { return Tag.Call; } - public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { - baos.packed1(nIns()); - Parser.Lexer.packed(_loc,baos,strs); - } - @Override public void gather(HashMap<String,Integer> strs) { Parser.Lexer.gather(_loc,strs); } - static Node make( BAOS bais, String[] strs ) { - int nIns = bais.packed1(); - return new CallNode(Parser.Lexer.unpack(bais,strs),new Node[nIns]); - } + + @Override public String label() { return "Call"; } @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { String fname = name(); @@ -48,7 +35,7 @@ public class CallNode extends CFGNode { if( fptr()._type instanceof TypeFunPtr tfp && tfp._isConstant() && tfp.notNull() ) { FunNode fun = CodeGen.CODE._link(tfp); if( fun !=null ) return fun._name; - if( fptr() instanceof ExternNode ex ) return ex._extern; + return CodeGen.CODE.externFunc(tfp); } return null; } @@ -101,9 +88,6 @@ public class CallNode extends CFGNode { @Override public Node idealize() { - // Lowering may introduce calls after the call graph has been analyzed. - // These use the callee interface without reopening its parameter flow. - if( CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Lower.ordinal() ) return null; CallEndNode cend = cend(); if( cend==null ) return null; // Still building @@ -113,11 +97,14 @@ public class CallNode extends CFGNode { Node progress = null; if( fptr()._type instanceof TypeFunPtr tfp && tfp.nargs() == nargs() ) { // If fidxs is negative, then infinite unknown functions - int[] fidxs = tfp.fidxs(); - if( !XInt.isHigh(fidxs) ) { - // Walk the bits and link - for( int fidx = XInt.next(fidxs,0); fidx > 0; fidx = XInt.next(fidxs,fidx) ) { - FunNode fun = CodeGen.CODE.link(fidx); + long fidxs = tfp.fidxs(); + if( fidxs > 0 ) { + // Wipe out the return which matching in the linker table + // Walk the (63 max) bits and link + for( ; fidxs!=0; fidxs = TypeFunPtr.nextFIDX(fidxs) ) { + int fidx = Long.numberOfTrailingZeros(fidxs); + TypeFunPtr tfp0 = tfp.makeFrom(fidx); + FunNode fun = CodeGen.CODE.link(tfp0); if( fun!=null && !fun._folding && !linked(fun) ) progress = link(fun); } @@ -138,58 +125,35 @@ public class CallNode extends CFGNode { // Link so this calls fun public Node link( FunNode fun ) { assert !linked(fun); - CodeGen.CODE.add(fun).addDef(this); + fun.addDef(this); for( Node use : fun._outputs ) - if( use instanceof ParmNode parm ) { - Node def = parm._idx==0 ? ConstantNode.seed(cend()._rpc).peephole() : arg(parm._idx); - CodeGen.CODE.add(parm).addDef(def); - // Linking can happen during post-SCCP inlining. Keep an RPC - // Parm's cached finite set synchronized with its newly added - // input instead of waiting for the next worklist visit. - if( parm._idx==0 && parm._type instanceof TypeRPC ) - parm._type = parm._type.meet(def._type); - } + if( use instanceof ParmNode parm ) + parm.addDef(parm._idx==0 ? new ConstantNode(cend()._rpc).peephole() : arg(parm._idx)); // Call end points to function return CodeGen.CODE.add(cend()).addDef(fun.ret()); return this; } - // Unlink a single function - public void unlink( FunNode fun, int path ) { - assert linked(fun); - for( Node use : fun._outputs ) - if( use instanceof ParmNode ) - use.delDef(path); - fun.delDef(path); - CodeGen.CODE.add(fun); - CallEndNode cend = cend(); - ReturnNode ret = fun.ret(); - if( cend != null ) { - ret.keep(); - cend.delDef(cend._inputs.find(ret)); - CodeGen.CODE.add(cend); - ret.unkeep(); - } - assert !linked(fun); - } - - // Unlink all linked functions public void unlink_all() { for( int i=0; i<_outputs._len; i++ ) if( out(i) instanceof FunNode fun ) { - int path = fun._inputs.find(this); - unlink(fun,path); + assert linked(fun); + int idx = fun._inputs.find(this); + for( Node use : fun._outputs ) + if( use instanceof ParmNode ) + use.delDef(idx); + fun.delDef(idx); + cend().delDef(cend()._inputs.find(fun.ret())); + assert !linked(fun); i--; } } @Override public Parser.ParseException err() { - if( fptr()._type == Type.BOTTOM ) - return null; // Wrong, but failing earlier if( !(fptr()._type instanceof TypeFunPtr tfp) ) - return Parser.error("Expected a function, but found " + fptr()._type, _loc); + throw Utils.TODO(); if( !tfp.notNull() ) return Parser.error( "Might be null calling "+tfp, _loc); if( nargs() != tfp.nargs() ) @@ -198,13 +162,11 @@ public class CallNode extends CFGNode { // Check for args for( int i=0; i<tfp.nargs(); i++ ) if( !arg(i+2)._type.isa(tfp.arg(i)) || !arg(i+2)._type.accessISA(tfp.arg(i)) ) { - // Constructors return private memory and carry hidden self and - // selfMem arguments ahead of the source-level arguments. - int argno = i - (tfp.ret() instanceof TypeMem ? 2 : 0); - return Parser.error( "Argument #" + argno + " isa " + arg(i+2)._type + ", but must be a " + tfp.arg(i), _loc ); + cend().addDep(arg(i+2)); // Upgrading arg type may allow inlining + return Parser.error( "Argument #" + i + " isa " + arg(i+2)._type + ", but must be a " + tfp.arg(i), _loc ); } - if( tfp.isHigh() ) + if( tfp.fidxs() < 0 ) throw Utils.TODO(); // Infinite unknown TFPs? Should be fairly precise CG return null; diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/CastMach.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CastMach.java new file mode 100644 index 00000000..e45898f5 --- /dev/null +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CastMach.java @@ -0,0 +1,19 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.Encoding; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.util.SB; + +/** + * Cast a pointer to read-only + */ +public class CastMach extends CastNode implements MachNode { + public CastMach( CastNode n ) { super(n); } + @Override public String op() { return label(); } + @Override public RegMask regmap(int i) { assert i==1; return RegMask.FULL; } + @Override public RegMask outregmap() { return RegMask.FULL; } + @Override public int twoAddress( ) { return 1; } + @Override public void encoding( Encoding enc ) { } + @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(in(1))); } +} diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/CastNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CastNode.java new file mode 100644 index 00000000..fa7c207d --- /dev/null +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CastNode.java @@ -0,0 +1,79 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.type.TypeMemPtr; +import com.seaofnodes.simple.util.Utils; + +// Upcast (join) the input to a t. Used after guard test to lift an input. +// Can also be used to make a type-assertion if ctrl is null. +public class CastNode extends Node { + public Type _t; + public CastNode(Type t, Node ctrl, Node in) { + super(ctrl, in); + _t = t; + setType(compute()); + } + + public CastNode(CastNode c) { + super(c.in(0), c.in(1)); // Call parent constructor + this._t = c._t; // Copy the Type field + setType(compute()); // Ensure type is recomputed + } + + @Override public String label() { return "("+_t.str()+")"; } + + @Override boolean _upgradeType(java.util.HashMap<String,Type> types) { + Type t = _t.upgradeType(types); + if( t==_t ) return false; + unlock(); + _t = t; + return true; + } + + + @Override public boolean isConst() { return true; } + + @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + return p.p(label()).n(in(1)); + } + + @Override + public Type compute() { + // Cast array to int + Type t1 = in(1)._type; + if( _t == TypeInteger.BOT && t1 instanceof TypeMemPtr tmp && tmp._obj.isAry() ) + return _t; + // Freeze if the join is high but both inputs are low + Type tj = t1.join(_t); + if( tj.isHigh() && !t1.isHigh() ) + return _type==null ? _t : _type; + + return tj; + } + + @Override + public Node idealize() { + return in(1)._type.isa(_t) && in(1)._type.accessISA(_t) ? in(1) : null; + } + + @Override + public boolean eq(Node n) { + CastNode cast = (CastNode)n; // Contract + return _t==cast._t; + } + + @Override + int hash() { return _t.hashCode(); } + + @Override + public Parser.ParseException err() { + // Has a condition to test, so OK + if( in(0) != null ) return null; + // No condition to test, so this must optimize away + return Parser.error( "Type " + in(1)._type.str() + " is not of declared type " + _t.str(), null ); + } +} 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 284220ea..afdd29be 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java @@ -2,14 +2,11 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; -import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; import com.seaofnodes.simple.type.TypeStruct; import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; -import java.util.HashMap; /** * A field offset. This becomes a constant *after* other optimizations which @@ -18,61 +15,27 @@ import java.util.HashMap; */ public class ConFldOffNode extends ConstantNode { - public TypeStruct _ts; // Struct holding field + public final String _name; // Struct name public final String _fname; // Field name - public ConFldOffNode( TypeStruct ts, String fname ) { super(TypeInteger.BOT); _ts = ts; _fname = fname; } - @Override public Tag serialTag() { throw Utils.TODO("should not reach here"); } + public ConFldOffNode( String name, String fname ) { super(TypeInteger.BOT); _name = name; _fname = fname; } @Override public String label() { - return _fname == " len" ? "sizeof("+_ts._name+")" : "#"+_fname; + return _fname == " len" ? "sizeof("+_name+")" : "#"+_fname; } @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p(label()); } - @Override public Node idealize() { - // Some array offsets are known early - if( _ts.isAry() ) { - // Length always at offset zero - if( _fname==" len" ) - return CodeGen.CODE.ZERO; - // The shift depends on the element - if( _fname=="<<" ) - return Node.con(_ts.aryScale()); - // The base depends on the layout (alignment for large elements may - // involve padding) and MAY depend on the length if I go for - // smaller array lengths. - } - return null; - } - // Convert field offset to an integer - 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(); + public Node asOffset(TypeStruct ts) { + return new ConstantNode(TypeInteger.constant(ts.offset(_fname==" len" ? ts._fields.length : ts.find(_fname)))).peephole(); } - // Upgrade the internal type - @Override boolean _upgradeType( HashMap<String,Type> TYPES) { - boolean progress = super._upgradeType(TYPES); - TypeStruct ts = (TypeStruct)_ts.upgradeType(TYPES); - if( ts == _ts ) return progress; - _ts = ts; - return true; - } @Override public boolean eq(Node n) { ConFldOffNode off = (ConFldOffNode)n; // Invariant - return _ts._name==off._ts._name && _fname.equals(off._fname) && super.eq(n); - } - @Override int hash() { return super.hash() * _ts._name.hashCode() * _fname.hashCode(); } - - @Override public Parser.ParseException err() { - return _ts._fref ? Parser.error("Unknown struct type '"+_ts._name+"'",null) : null; + return _name.equals(off._name) && _fname.equals(off._fname) && super.eq(n); } + @Override int hash() { return super.hash() * _name.hashCode() * _fname.hashCode(); } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ConstantNode.java index 0569f352..8d783be7 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/ConstantNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ConstantNode.java @@ -5,10 +5,7 @@ import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeFunPtr; -import com.seaofnodes.simple.util.BAOS; - -import java.util.HashMap; -import java.util.IdentityHashMap; +import com.seaofnodes.simple.util.SB; /** * A Constant node represents a constant value. @@ -20,60 +17,29 @@ import java.util.IdentityHashMap; * The Constant's value is the value stored in it. */ -public class ConstantNode extends TypeNode { +public class ConstantNode extends Node { + public Type _con; public ConstantNode( Type type ) { - this(type,false); - } - protected ConstantNode( Type type, boolean raw ) { - super(type,new Node[]{CodeGen.CODE._start}); - assert raw || !(type instanceof TypeFunPtr tfp && tfp.isConstant()) : - "Unique function identities require a Return-linked FunPtrNode"; - _type = type; - } - public ConstantNode( ConstantNode con ) { super(con); } - @Override public Tag serialTag() { return Tag.Con; } - @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { - baos.packed2(types.get(_con)); // NPE if fails lookup + super(new Node[]{CodeGen.CODE._start}); + _con = _type = type; } - static Node make( BAOS bais, Type[] types) { return raw(types[bais.packed2()]); } + public ConstantNode( ConstantNode con ) { super(con); _con = con._type; } - // Semantic constant construction through Lower. A unique internal - // function identity carries a lifetime edge to the function's Return. public static Node make( Type type ) { if( type==Type. CONTROL ) return new CtrlNode(); if( type==Type.XCONTROL ) return new XCtrlNode(); - if( CodeGen.CODE._phase != null && - CodeGen.CODE._phase.ordinal() > CodeGen.Phase.Lower.ordinal() ) - return raw(type); - if( type instanceof TypeFunPtr tfp && tfp.isConstant() ) { - FunNode fun = CodeGen.CODE._link(tfp); - if( fun != null ) - return new FunPtrNode(tfp,CodeGen.CODE._start,fun.ret()).init(); - // TODO: A singleton FIDX can also emerge after merging and - // guarding several function pointers. Preserve that identity - // even if its internal Return is no longer available. - return raw(type); + if( type instanceof TypeFunPtr tfp && tfp.isConstant() && tfp.notNull() ) { + FunNode fun = CodeGen.CODE.link(tfp); + if( fun!=null && !fun.isDead() ) return new FunPtrNode(tfp,fun.ret()); } return new ConstantNode(type); } - // Exact construction for deserialization and graph-incomplete machine - // selection. No semantic classification, compute, idealize, or GVN. - public static ConstantNode raw( Type type ) { return new ConstantNode(type,true); } - public static ConstantNode raw( ConstantNode con ) { return new ConstantNode(con); } - - // Analysis input created from whole cloth, rather than a first-class - // runtime value. In particular, a constructor call descriptor does not - // own the constructor function; the class FunPtr does. - public static ConstantNode seed( Type type ) { return new ConstantNode(type,true); } - @Override protected String repeatName() { return _con==null || _con.toString().length()<=32 ? null : uniqueName(); } @Override public String label() { return "Con"; } - @Override public Node copy() { return raw(this); } - @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { @@ -88,4 +54,14 @@ public class ConstantNode extends TypeNode { @Override public boolean isConst() { return true; } @Override public Type compute() { return _con; } @Override public Node idealize() { return null; } + @Override public boolean eq(Node n) { return _con==((ConstantNode)n)._con; } + @Override int hash() { return _con.hashCode(); } + + @Override boolean _upgradeType(java.util.HashMap<String,Type> types) { + Type next = _con.upgradeType(types); + if( next==_con ) return false; + unlock(); + _con=next; + return true; + } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ConvertNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ConvertNode.java index cdc890a7..c5be0123 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/ConvertNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ConvertNode.java @@ -77,13 +77,7 @@ public class ConvertNode extends Node { return null; } - @Override boolean _upgradeType(HashMap<String,Type> TYPES) { - Type dst = _dst.upgradeType(TYPES); - if( dst==_dst ) return false; - unlock(); - _dst = dst; - return true; - } + @Override public Parser.ParseException err() { if( val()._type==Type.BOTTOM || @@ -98,4 +92,12 @@ public class ConvertNode extends Node { @Override public boolean eq(Node n) { return _dst==((ConvertNode)n)._dst; } @Override int hash() { return _dst.hashCode(); } + + @Override boolean _upgradeType(java.util.HashMap<String,Type> types) { + Type next = _dst.upgradeType(types); + if( next==_dst ) return false; + unlock(); + _dst=next; + return true; + } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/CtrlNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CtrlNode.java index 62140873..cf630ad4 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/CtrlNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CtrlNode.java @@ -8,7 +8,6 @@ import com.seaofnodes.simple.type.Type; public class CtrlNode extends CFGNode { public CtrlNode() { super(CodeGen.CODE._start); } - @Override public Tag serialTag() { throw com.seaofnodes.simple.util.Utils.TODO(); } @Override public String label() { return "Ctrl"; } @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p("Cctrl"); } @Override public boolean isConst() { return true; } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/DivFNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/DivFNode.java new file mode 100644 index 00000000..df1dae4e --- /dev/null +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/DivFNode.java @@ -0,0 +1,27 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFloat; + +import java.util.BitSet; + +public class DivFNode extends ArithFNode { + public DivFNode(Node lhs, Node rhs) { super(lhs, rhs); } + public DivFNode(ArithNode n) { super(n); } + + @Override public String label() { return "DivF"; } + + @Override public String glabel() { return "/"; } + + @Override double doOp( double x, double y ) { return x/y; } + + @Override + public Node idealize() { + // Div of constant + if( in(2)._type instanceof TypeFloat f && f.isConstant() ) + return new MulFNode(in(1),new ConstantNode(TypeFloat.constant(1.0/f.value())).peephole()); + + return null; + } + @Override Node copy(Node lhs, Node rhs) { return new DivFNode(lhs,rhs); } +} diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/DivNode.java index f173f0ef..d973ffa7 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -9,7 +9,6 @@ public class DivNode extends ArithNode { @Override boolean allowFloat() { return true; } public DivNode(Node lhs, Node rhs) { super(null, lhs, rhs); } public DivNode(Node lhs, Node rhs, byte mode) { super(null, lhs, rhs, mode); } - @Override public Tag serialTag() { return Tag.Div; } @Override public String op() { return _mode==1 ? "//" : "/"; } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/EscapeNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/EscapeNode.java deleted file mode 100644 index 3e95fffb..00000000 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/EscapeNode.java +++ /dev/null @@ -1,116 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.print.ExprPrinter; - -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.*; -import java.util.HashMap; -import java.util.IdentityHashMap; - -/** - * Merge public and private aliases. - * 0 - control - * 1 - private pointer - * 2 - private alias - * 3 - public alias - */ -public class EscapeNode extends TypeNode { - - public EscapeNode(Field fld, Node self, Node priv, Node pub ) { - super(fld,null,self,priv,pub); - } - public EscapeNode(EscapeNode esc ) { - super(esc); // Copy constructor - // Except we do not want/need self ptr past Opto. - // *NOTE* the weird direct stomp; this is correct because this - // constructor does NOT set the backedges, because it is used to - // copy-construct an entire graph and ALL edges will get rewritten. - _inputs.set(1,null); - _con = esc._con; - } - - // Pointer to some private (unescaped) memory from a NewNode - public Node self() { return in(1); } - public Node priv() { return in(2); } - public Node pub () { return in(3); } - public Field fld() { return (Field)_con; } - - @Override public Tag serialTag() { return Tag.Escape; } - @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { - baos.packed2(types.get(_con)); - } - static Node make( BAOS bais, Type[] types) { - return new EscapeNode((Field)types[bais.packed2()], null,null,null); - } - - @Override - protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - p.p("Esc#").p(fld()._alias).p(" {"); - if( self()==null ) p.p("---"); - else p.n(self()).p(","); - p.n(priv()).p("}, "); - p.n(pub ()); - return p; - } - - @Override - public Type compute() { - if( priv()._type.isHigh() ) return TypeMem.TOP; - if( pub ()._type.isHigh() ) return TypeMem.TOP; - TypeMem mpriv = (TypeMem)priv()._type; - TypeMem mpub = (TypeMem)pub ()._type; - // Private memory comes from an allocation or other non-aliased source. - // Public memory comes from anywhere, might be bulk memory with a very weak mem type. - Field fld = fld(); - assert mpriv._alias==1 || mpriv._alias==fld._alias; - assert mpub ._alias==1 || mpub ._alias==fld._alias; - Type tpriv = mpriv._t instanceof TypeStruct ts && ts.findAlias(fld._alias)!=-1 - ? ts.at(ts.findAlias(fld._alias))._t - : mpriv._t; - assert !(mpub._t instanceof TypeStruct); // Never expect this, but if it starts to happen, need to optimize - Type tpub = mpub._t; - - // t is meet of pub & priv. Pub memory can be very weak (e.g. alias 1) - // and we're no worse than the declared field type - Type t = tpriv.meet(tpub).join(fld._t); - int[] fidxes = XInt.meet(mpriv._escFs, mpub._escFs); - int[] aliases = XInt.meet(mpriv._escAs, mpub._escAs); - return TypeMem.make(fld._alias, t, false, false, fld._final, fidxes, aliases).escapesFrom(t); - } - - @Override public Node idealize() { - // Sharpen private memory input - if( priv() instanceof MemMergeNode mmm ) { - setDef(2,mmm.alias(fld()._alias)); - return this; - } - // Sharpen public memory input - if( pub() instanceof MemMergeNode mmm ) { - setDef(3,mmm.alias(fld()._alias)); - return this; - } - // New allocation is dead - if( self()._type.isHigh() ) - if( pub()._type.isa(_type) ) return pub(); - else addDep(pub()); - return null; - } - -// Upgrade the internal type -@Override boolean _upgradeType( HashMap<String,Type> TYPES) { - Type t = _con.upgradeType(TYPES); - if( t == _con ) return false; - unlock(); // Unlock before changing _con - _con = t; - return true; -} - - @Override public boolean eq(Node n) { - if( fld()._alias!=((EscapeNode)n).fld()._alias ) - return false; - assert _con == ((EscapeNode)n)._con; - return true; - } - - @Override int hash() { return _con.hashCode(); } -} diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ExternNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ExternNode.java index 978298fc..7a26e2e9 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/ExternNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ExternNode.java @@ -1,38 +1,25 @@ package com.seaofnodes.simple.node; -import com.seaofnodes.simple.codegen.Serialize; +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.util.BAOS; +import com.seaofnodes.simple.type.TypeFunPtr; import com.seaofnodes.simple.util.SB; - import java.util.BitSet; -import java.util.HashMap; -import java.util.IdentityHashMap; /** - A constant with external linkage. + A constant with external linkage. */ public class ExternNode extends ConstantNode { public final String _extern; - // An exact external function identity has no local FunNode/Return to own. - // Its lifetime and eventual relocation are carried by the extern symbol - // and CodeGen's FIDX-to-extern mapping instead. - public ExternNode(Type t, String ex) { super(t,true); _extern = ex; } - @Override public Tag serialTag() { return Tag.Extern; } - @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { - baos.packed2(types.get(_con )); - baos.packed2( strs.get(_extern)); - } - static Node make( BAOS bais, String[] strs, Type[] types) { - return new ExternNode(types[bais.packed2()], strs[bais.packed2()]); + public ExternNode(Type t, String ex) { + super(t); + _extern = ex; + if( t instanceof TypeFunPtr tfp ) CodeGen.CODE.externFunc(tfp,ex); } @Override public String label() { return "#"+_con+":"+_extern; } @Override public boolean eq(Node n) { return this==n; } - @Override public void gather( HashMap<String,Integer> strs ) { - Serialize.gather(strs,_extern); - } - } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ExternOffsetNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ExternOffsetNode.java deleted file mode 100644 index 409ce5d1..00000000 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/ExternOffsetNode.java +++ /dev/null @@ -1,19 +0,0 @@ -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/FRefNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/FRefNode.java index 644f1434..e7333c23 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/FRefNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/FRefNode.java @@ -10,32 +10,17 @@ import com.seaofnodes.simple.type.Type; * A Forward Reference. Its any final constant, including functions. When * the Def finally appears its plugged into the forward reference, which then * peepholes to the Def. - - * FRefs can be external references, to other Simple defined values in ELF - * files, or external FFI/C libs. If external, a file system lookup needs to - * find it in another file, and the name (one time) change to match the found - * location. - * - * Example, a ambiguous name "foo" is looked up externally; it might resolve - * to "A.B.foo" or "libc.foo" or the like; the name is changed to match. */ public class FRefNode extends ConstantNode { public static final Type FREF_TYPE = Type.BOTTOM; - public String _name; - public final Parser.Lexer _loc; // Source location - - public FRefNode( String name, Parser.Lexer loc ) { this(name,loc,FREF_TYPE); } - public FRefNode( String name, Parser.Lexer loc, Type type ) { - super(type); - _name = name; - _loc = loc; - } + public final Var _n; + public FRefNode( Var n ) { super(FREF_TYPE); _n = n; } - @Override public String label() { return "FRef"+_name; } + @Override public String label() { return "FRef"+_n; } @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - return p.p("FRef_").p(_name); + return p.p("FRef_").p(_n); } @Override public Node idealize() { @@ -43,8 +28,8 @@ public class FRefNode extends ConstantNode { return nIns()==1 ? null : in(1); } - public Parser.ParseException err() { return Parser.error("Undefined name '"+_name+"'",_loc); } + public Parser.ParseException err() { return Parser.error("Undefined name '"+_n._name+"'",_n._loc); } @Override public boolean eq(Node n) { return this==n; } - @Override int hash() { return _name.hashCode(); } + @Override int hash() { return _n._idx; } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/FldOffNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/FldOffNode.java index a9f8d692..7605d74f 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/FldOffNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/FldOffNode.java @@ -1,54 +1,24 @@ package com.seaofnodes.simple.node; -import com.seaofnodes.print.ExprPrinter; - import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; -import java.util.HashMap; - -/** - * A field offset. This becomes a constant *after* other optimizations which - * determine field sizes - some fields might be dead, or hold only zeros or - * small values. - */ - -public class FldOffNode extends TypeNode { - public final String _fname; // Field name - public final Parser.Lexer _loc; // - public FldOffNode( Node base, String fname, Parser.Lexer loc ) { super(TypeInteger.BOT,base); _fname = fname; _loc=loc; } - @Override public Tag serialTag() { throw Utils.TODO("should not reach here"); } - - @Override public String label() { - return _fname == " len" ? "sizeof()" : "#"+_fname; - } - - @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - return p.p(label()); - } - - @Override public Type compute() { - return Type.BOTTOM; - } +/** A field name whose receiver's shape is not known while parsing. */ +public class FldOffNode extends Node { + public final String _name; + public FldOffNode(Node ptr, String name) { super(null,ptr); _name=name; } + @Override public String label() { return "#"+_name; } + @Override public Type compute() { return TypeInteger.BOT; } @Override public Node idealize() { - // When base() gets typed, this xforms into a ConFldOffNode - if( in(0)._type instanceof TypeMemPtr tmp ) - return new ConFldOffNode(tmp._obj,_fname); + if( in(1)._type instanceof TypeMemPtr ptr && !ptr._obj._open ) { + if( _name.equals("<<") && ptr._obj.isAry() ) return con(ptr._obj.aryScale()); + if( ptr._obj.field(_name)!=null ) return new ConFldOffNode(ptr._obj._name,_name); + } return null; } - - @Override public boolean eq(Node n) { - FldOffNode off = (FldOffNode)n; // Invariant - return _fname.equals(off._fname) && super.eq(n); - } - @Override int hash() { return super.hash() * _fname.hashCode(); } - @Override public Parser.ParseException err() { - if( in(0)._type instanceof TypeMemPtr tmp && tmp._obj._fref ) - return Parser.error("Unknown struct type '"+tmp._obj._name+"'",null); - return null; + return Parser.error("Cannot resolve field '"+_name+"'",null); } + @Override public boolean eq(Node n) { return _name.equals(((FldOffNode)n)._name); } + @Override int hash() { return _name.hashCode(); } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/FunNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/FunNode.java index edde247a..1ba57e85 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/FunNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/FunNode.java @@ -1,21 +1,21 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.BAOS; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.Encoding; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFunPtr; +import com.seaofnodes.simple.type.TypeTuple; import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; -import java.util.Arrays; import java.util.BitSet; -import java.util.HashMap; -import java.util.IdentityHashMap; import static com.seaofnodes.simple.codegen.CodeGen.CODE; public class FunNode extends RegionNode { // When set true, this Call/CallEnd/Fun/Return is being trivially inlined - public boolean _folding; + boolean _folding; public boolean folding() { return _folding; } private TypeFunPtr _sig; // Initial signature @@ -23,45 +23,20 @@ public class FunNode extends RegionNode { public String _name; // Debug name - public int _approxUIDs; // Approximate function size, used as a inlining heuristic - public CompUnit _compunit; // Defined in source file - public final boolean _extern; // External function; no code, name only - - private FunNode( Parser.Lexer loc, Node[] nodes, TypeFunPtr sig, String name, CompUnit compunit, boolean ext ) { - super(loc,nodes); - _name = name; - _sig = sig; - _extern = ext; - _compunit = compunit; - } - public FunNode( Parser.Lexer loc, TypeFunPtr sig, String name, CompUnit compunit, Node... nodes ) { this(loc,nodes,sig,name,compunit,false); } + public FunNode( Parser.Lexer loc, TypeFunPtr sig, String name, Node... nodes ) { super(loc,nodes); _name=name; _sig = sig; } public FunNode( FunNode fun ) { - super( fun, fun._loc ); - _sig = fun.sig(); - _name = fun._name; - _compunit = fun._compunit; - _extern = fun._extern; - } - @Override public Tag serialTag() { return Tag.Fun; } - @Override public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node,Integer> anodes ) { - assert !_folding; - baos.packed1(Serialize.nIns(this,anodes)); // Object-local linked calls - baos.packed2(types.get(_sig)); // NPE if fails lookup - baos.packed2(_name==null ? 0 : strs.get(_name)); - baos.packed2(_approxUIDs); - // _compunit set from context on loading, do not write - } - static Node make( BAOS bais, String[] strs, Type[] types) { - Node[] ins = new Node[bais.packed1()]; - TypeFunPtr sig = (TypeFunPtr)types[bais.packed2()]; - String name = strs[bais.packed2()]; - FunNode fun = new FunNode(null,sig,name,null,ins); - fun._approxUIDs = bais.packed2(); - // _compunit set from context on loading, do not read - return fun; + super( fun, fun==null ? null : fun._loc ); + if( fun!=null ) { + _sig = fun.sig(); + _name = fun._name; + } else { + _sig = TypeFunPtr.BOT; + _name = ""; + } } - @Override public String label() { return _name == null ? "$fun"+_sig.fidx() : _name; } + @Override + public String label() { return _name == null ? "$fun"+_sig.fidx() : _name; } // Find the one CFG user from Fun. It's not always the Return, but always // the Return *is* a CFG user of Fun. @@ -73,63 +48,42 @@ public class FunNode extends RegionNode { return null; } - public ParmNode parm(int idx) { - ParmNode pidx = null; + public ParmNode rpc() { + ParmNode rpc = null; for( Node n : _outputs ) - if( n instanceof ParmNode parm && parm._idx==idx ) - { assert pidx==null; pidx=parm; } - return pidx; + if( n instanceof ParmNode parm && parm._idx==0 ) + { assert rpc==null; rpc=parm; } + return rpc; } - public ParmNode rpc() { return parm(0); } // Cannot create the Return and Fun at the same time; one has to be first. // So setting the return requires a second step. public void setRet(ReturnNode ret) { _ret=ret; } public ReturnNode ret() { assert _ret!=null; return _ret; } + // Direct calls are known; any other use of an address may have unknown callers. + public boolean hasPtr() { + for( Node use : ret()._outputs ) + if( use instanceof FunPtrNode ) + for( Node user : use._outputs ) + if( !(user instanceof CallNode call) || call.fptr()!=use || + call._inputs.find(use)!=call.nIns()-1 ) return true; + return false; + } + // Signature can improve over time public TypeFunPtr sig() { return _sig; } public void setSig( TypeFunPtr sig ) { - assert sig.isa(_sig) || resolvesForwardSelf(sig,_sig); + assert sig.isa(_sig); if( _sig != sig ) { CODE.add(this); - // Changing the signature can allow more inlining - if( _ret != null ) - for( Node use : ret().outs() ) - if( use instanceof CallEndNode || use instanceof FunPtrNode ) - CODE.add(use); - unlock(); _sig = sig; } } - // A class initializer starts parsing with an open singleton receiver and - // closes that same receiver after discovering its fields. Function - // arguments are contravariant, so ordinary isa does not describe this - // parser-time forward-reference resolution even though every other part - // of the signature sharpens normally. - private static boolean resolvesForwardSelf(TypeFunPtr sig, TypeFunPtr old) { - if( !(sig.arg(0) instanceof TypeMemPtr now) || - !(old.arg(0) instanceof TypeMemPtr prior) || - now._obj._name != prior._obj._name || - !prior._obj._open || now._obj._open ) - return false; - return sig.makeFrom(old.arg(0),0).isa(old); - } - - @Override boolean _upgradeType( HashMap<String,Type> TYPES) { - TypeFunPtr sig = (TypeFunPtr)_sig.upgradeType(TYPES); - if( sig == _sig ) return false; - unlock(); - _sig = sig; - return true; - } - public void setName( String name ) { - if( _name==null ) - _name = _compunit==null || _compunit._cname==null || _compunit._cname.indexOf('.')==-1 - ? name - : (_compunit._cname+"."+name).intern(); + if( _name==null ) _name=name; + else _name += "."+name; } @Override @@ -138,10 +92,10 @@ public class FunNode extends RegionNode { if( unknownCallers() ) return Type.CONTROL; Type t = Type.XCONTROL; - for( int i = 1; i < nIns(); i++) { - // Since there are no unknown callers, the Start input is only a - // fake hook and will be a Tuple with XControl, so ignore it. Need - // to be called from someplace other than Start. + for (int i = 1; i < nIns(); i++) { + // Since no unknown callers, we are not main and the Start input + // will be a Tuple with XControl, so ignore it. Need to be called + // from someplace other than Start if( !(in(i) instanceof StartNode) ) t = t.meet(in(i)._type); } @@ -151,38 +105,28 @@ public class FunNode extends RegionNode { @Override public Node idealize() { - // Some linked path dies, except unknown caller hooks never kill Start. + // Some linked path dies, except main never kills Start Node progress = deadPath(unknownCallers()); if( progress!=null ) { - if( nIns()==2 && in(1) instanceof CallNode call ) - CODE.add(call.cend()); // If no Start and one call, check for inline + if( nIns()==3 && in(2) instanceof CallNode call ) + CODE.add(call.cend()); // If Start and one call, check for inline return progress; } // Upgrade inferred or user-written return type to actual - if( _ret!=null && _ret._type instanceof TypeTuple tt && - tt.ret() != _sig.ret() && tt.ret().isa(_sig.ret()) ) { + if( _ret!=null && _ret._type instanceof TypeTuple tt && tt.ret() != _sig.ret() ) { setSig(_sig.makeFrom(tt.ret())); return this; } - // Attempt to get rid of the unknown caller. - // - Must be past pessimistic Iter, which discovers and links calls - // through class-field loads and other initially imprecise values. - // - FIDX is not in the escape set - // - Not called anywhere else; post Opto called-not-escaped functions need to still be hooked to start - // Opaque CLInit obligations need their bodies even without Call edges. - if( !isClz() && in(1) instanceof StartCUNode start ) { - boolean preOpto = CodeGen.CODE._phase == null || - CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal(); - if( !preOpto && !start.escapedFIDX(_sig.fidx()) && nIns()<=2 && _ret!=null ) { - _compunit._stop.delDef(_compunit._stop._inputs.find(ret())); - return removeDeadPath(1); - } else { - addDep(start); - } - } + // When can we assume no callers? Or no other callers (except main)? + // In a partial compilation, we assume Start gets access to any/all + // top-level public structures and recursively what they point to. + // This in turn is valid arguments to every callable function. + // + // In a total compilation, we can start from Start and keep things + // more contained. // If no default/unknown caller, use the normal RegionNode ideal rules // to collapse @@ -201,230 +145,62 @@ public class FunNode extends RegionNode { } // Bypass Region idom, always assume depth == 1, one more than Start, - // unless folding + // unless folding then just a ID on input#1 @Override public int idepth() { return _folding ? super.idepth() : 1; } - // Bypass Region idom, always assume idom is Start - @Override public CFGNode idom(Node dep) { - return _folding ? cfg(1) : null; - } + @Override public CFGNode idom(Node dep) { return _folding && nIns()==3 ? cfg(2) : (nIns()>1 ? cfg(1) : null); } // Always in-progress until we run out of unknown callers public boolean unknownCallers() { return nIns()>=2 && in(1) instanceof StartNode; } - public boolean isModInit( ) { - // The one top-level <clinit> with no internal dots: - // "sys.<clinit>" is a module, but "sys.io.<clinit>" is not. - return isClz() && _name.indexOf('.')==_name.lastIndexOf('.'); - } - public boolean isClz ( ) { return isClz (_name); } - public boolean isInit( ) { return isInit(_name); } - public boolean isInstance() { return isInstance(_name); } - public static boolean isClz (String name ) { return name!=null && name.endsWith(".<clinit>"); } - public static boolean isInit(String name ) { return name!=null && name.endsWith("init>"); } - public static boolean isInstance(String name ) { return name!=null && name.endsWith(".<init>"); } - - // Allocation factories and instance initializers carry extra memory boilerplate. - public int inlineLimit() { - return isInstance() || (_name!=null && _name.endsWith(".<new>")) ? 200 : 100; - } + @Override public boolean inProgress() { return unknownCallers(); } - // Function is public (callable from Start directly). - public boolean isPublic( ) { - // Never true for anonymous functions - if( _name == null ) return false; - // Only public for <clinit> and <init>, all others - // are found via field loads. The <init> Start hook also supplies its - // conservative pre-Opto construction inputs; StopCU separately keeps - // its private-memory return from escaping as a public result. - if( !isInit() ) return false; - // Private class, has to be found via program text, not linker - if( _name.charAt(0)=='_' ) return false; - - // No instances of "...clz1._clz2..." where the class name path - // includes a private class. - if( _name.indexOf("._") != -1 ) return false; - - // Public class <cl/init> - return true; - } + // Add a new function exit point. + public void addReturn(Node ctrl, Node mem, Node rez) { _ret.addReturn(ctrl,mem,rez); } - @Override public boolean inProgress() { - return unknownCallers() && CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal(); - } - - // Build the function body set + // Build the function body public BitSet body() { - // Iter drains ordinary peeps between inlines; body discovery precedes - // this inline's folding, so function boundaries are stable. - assert !_folding; - // Forward control includes paths that never reach Return. + + // Reverse up (stop to start) CFG only, collect bitmap. BitSet cfgs = new BitSet(); - walkCFG(this,cfgs); - cfgs.set(ret()._nid); + cfgs.set(_nid); + walkUp(ret(),cfgs ); // Top down (start to stop) all flavors. CFG limit to bitmap. // If data use bottoms out in wrong CFG, returns false - but tries all outputs. // If any output hits an in-CFG use (e.g. phi), then keep node. BitSet body = new BitSet(); - // The function owns its Return even when it has no executable exit. - body.set(ret()._nid); - BitSet visit = new BitSet(); - int old; - do { - old = body.cardinality(); - visit.clear(); - walkDown(this, cfgs, body, visit); - } while( old != body.cardinality() ); + walkDown(this, cfgs, body, new BitSet()); return body; } - private static void walkCFG(CFGNode n, BitSet cfgs) { + private static void walkUp(CFGNode n, BitSet cfgs) { if( cfgs.get(n._nid) ) return; cfgs.set(n._nid); - if( n instanceof ReturnNode ) return; - for( Node use : n._outputs ) { - if( !(use instanceof CFGNode cfg) || cfg instanceof FunNode ) continue; - if( cfg instanceof RegionNode ? cfg._inputs.find(n)>0 : cfg.in(0)==n ) - walkCFG(cfg,cfgs); - } + if( n instanceof RegionNode r ) + for( int i=1; i<n.nIns(); i++ ) + walkUp(n.cfg(i),cfgs); + else walkUp(n.cfg0(),cfgs); } private static boolean walkDown( Node n, BitSet cfgs, BitSet body, BitSet visit ) { - if( n==null ) return false; if( visit.get(n._nid) ) return body.get(n._nid); visit.set(n._nid); - // Visit self as CFG outside the function + if( n instanceof CFGNode && !cfgs.get(n._nid) ) return false; - // Pretend the NewNode is a CFG, so its projections stay with it in loops - if( n instanceof NewNode nnn ) cfgs.set(n._nid); - // Visit n.cfg() outside of function if( n.in(0)!=null && !cfgs.get(n.in(0)._nid) ) return false; - // Phis inside the function must have their body flag set BEFORE - // recursion walks around a loop and finds them again. - if( n instanceof PhiNode phi && cfgs.get(phi.in(0)._nid) ) - body.set(phi._nid); // Find data cycles boolean in = n.in(0)!=null || n instanceof CFGNode; - for( Node use : n._outputs ) { - // Will hit a backedge, so 'n' MUST be in the function, and must be - // set before the cyclic walk finds 'n' again. - if( use instanceof PhiNode puse && puse.in(0) instanceof LoopNode loop && puse.in(2)==n ) - body.set(n._nid); // Set before walk + for( Node use : n._outputs ) in |= walkDown(use,cfgs,body,visit); - } if( in ) body.set(n._nid); return in; } - // Clone function body. Give function a new FIDX. - FunNode copyBody() { - // Build the function body BitSet - BitSet body = body(); - assert body.cardinality() < inlineLimit(); - // Walk the body, cloning - IdentityHashMap<Node,Node> map = new IdentityHashMap<>(); - BitSet visit = CodeGen.CODE.visit(); - bodyCopy( visit, body, map, this ); - bodyCopy( visit, body, map, _ret ); // May be disconnected from executable control. - visit.clear(); - bodyEdge( visit, body, map, this ); - bodyEdge( visit, body, map, _ret ); - visit.clear(); - assert map.size()==body.cardinality(); - - // New function/return cross-link each other - FunNode fun2 = (FunNode )map.get(this); - ReturnNode ret2 = (ReturnNode)map.get(_ret); - fun2._ret = ret2; - ret2._fun = fun2; - BitSet body2 = new BitSet(); - for( Node n : map.values() ) - body2.set(n._nid); - - // Remove non-body callers. Body-local call edges were cloned along - // with their CallEnds and must stay linked so their cloned types remain - // monotonic during the post-Opto Iter. - for( int i=1; i<fun2.nIns(); i++ ) - if( !(fun2.in(i) instanceof CallNode call) || !body2.get(call._nid) ) - fun2.removeDeadPath(i--); - - // Linking adds graph edges and worklist entries; identity-map order - // would make inlining depend on the host JVM's object hashes. - Node[] olds = map.keySet().toArray(new Node[0]); - Arrays.sort(olds, (x,y) -> Integer.compare(x._nid,y._nid)); - for( Node old : olds ) - if( old instanceof CallNode oldCall ) - relinkClonedCall(oldCall,(CallNode)map.get(oldCall),map); - - // Flip to a new FIDX to avoid confusion with the old one. - // This is a non-monotonic (sideways) type move, only applicable - // because fun2 is new. The fidx is also a purely local fiction, - // since this body will inline and the fidx goes dead. - fun2._sig = _sig.makeFrom(CodeGen.CODE._fidxs.nextInline()); - return fun2; - } - - private static void relinkClonedCall( CallNode oldCall, CallNode call, IdentityHashMap<Node,Node> map ) { - CallEndNode oldCend = oldCall.cend(); - if( oldCend==null || oldCend._folding ) - return; - for( int i=1; i<oldCend.nIns(); i++ ) { - ReturnNode oldRet = (ReturnNode)oldCend.in(i); - FunNode oldFun = oldRet.fun(); - FunNode fun = (FunNode)map.get(oldFun); - if( fun==null ) fun = oldFun; - if( !call.linked(fun) ) - call.link(fun); - } - CallEndNode cend = call.cend(); - if( cend.nIns() > 1 ) - cend._type = cend.compute(); - } - - // Clone small function - private void bodyCopy( BitSet visit, BitSet body, IdentityHashMap<Node,Node> map, Node n ) { - if( n==null ) return; - if( !body.get(n._nid) ) return; // Not part of the body - if( visit.get(n._nid) ) return; // Been there, done that - visit.set(n._nid); - Node m = n.copy(); - CodeGen.CODE.add(m); - map.put(n,m); - m._type = n._type; - for( Node x : n._outputs ) - bodyCopy(visit,body,map,x); - } - private void bodyEdge( BitSet visit, BitSet body, IdentityHashMap<Node,Node> map, Node n ) { - if( n==null ) return; - if( !body.get(n._nid) ) return; // Not part of the body - if( visit.get(n._nid) ) return; // Been there, done that - visit.set(n._nid); - Node m = map.get(n); - for( Node e : n._inputs ) { - Node x = e; - if( e != null ) { - if( n instanceof CallEndNode cend && e != cend.call() && - (!(e instanceof ReturnNode ret) || map.get(ret.fun()) == null) ) - continue; - x = map.get(e); - if( x == null ) { - if( n instanceof CallEndNode cend && e != cend.call() ) - continue; - x = e; - } - } - m.addDef(x); - } - for( Node x : n._outputs ) - bodyEdge(visit,body,map,x); - } - - - // no 2 FunNodes are ever GVN'able + // FunNodes must match signature (equivalent: no 2 FunNodes are ever GVN'able) @Override public boolean eq( Node n ) { - return this==n; + return _sig == ((FunNode)n)._sig; } @Override public int hash() { @@ -478,7 +254,12 @@ public class FunNode extends RegionNode { return slotRotate*8; } - @Override public void gather( HashMap<String,Integer> strs ) { - Serialize.gather(strs,_name); + + @Override boolean _upgradeType(java.util.HashMap<String,Type> types) { + TypeFunPtr next = (TypeFunPtr)_sig.upgradeType(types); + if( next==_sig ) return false; + unlock(); + _sig=next; + return true; } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java index 82053fd8..43242c32 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java @@ -1,68 +1,12 @@ package com.seaofnodes.simple.node; -import com.seaofnodes.print.ExprPrinter; +import com.seaofnodes.simple.type.TypeFunPtr; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.BAOS; - -import java.util.HashMap; -import java.util.IdentityHashMap; - -// Constant function pointer whose type follows the callee signature. -public class FunPtrNode extends TypeNode { - public FunPtrNode(TypeFunPtr tfp, StartNode start, ReturnNode ret) { - super(tfp,start,ret); - } - public FunPtrNode(FunPtrNode fptr) { super(fptr); } - @Override public Tag serialTag() { return Tag.FunPtr; } - @Override public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node,Integer> anodes) { - baos.packed2(types.get(_con)); - } - static Node make(BAOS bais, Type[] types) { - return new FunPtrNode((TypeFunPtr)types[bais.packed2()],null,null); +// A function address keeps its body alive, even without a linked Call. +public class FunPtrNode extends ConstantNode { + public FunPtrNode(TypeFunPtr tfp, ReturnNode ret) { + super(tfp); + addDef(ret); } public ReturnNode ret() { return (ReturnNode)in(1); } - public FunNode fun() { return ret().fun(); } - - @Override public String label() { return "#"+_con; } - @Override public Node copy() { - FunPtrNode fptr = new FunPtrNode((TypeFunPtr)_con,null,null); - fptr._type = _type; - return fptr; - } - - @Override - protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - FunNode fun = CodeGen.CODE._link(((TypeFunPtr)_con).fidx()); - return fun!=null && fun._name!=null - ? p.p("{ ").p(fun._name).p("}") - : p.p(_con); - } - - @Override public boolean isConst() { return true; } - @Override public Type compute() { - ReturnNode ret = ret(); - if( ret == null ) return _con; - if( !(ret._type instanceof TypeTuple tt) ) return ret._type.oob(); - TypeFunPtr tfp = ret.fun().sig(); - // Both the sig and the return value must be true, so JOIN both - Type tret = tfp._ret.join(tt.ret()); - return tfp.makeFrom(tret); - } - @Override public Node idealize() { - ReturnNode ret = ret(); - if( ret==null ) return null; - // Function died (never executed), but fcn ptr is alive. - // Can be checked for null, or for unequals another FunPtr. - FunNode fun = addDep(ret.fun()); - TypeFunPtr sig = fun.sig(); - if( sig != _con ) - { liftType(sig); return this; } - // TODO: If the function body dies but this value remains live only as - // a null/equality sentinel, preserve the identity without the Return. - if( fun.isDead() ) - { setDef(1,null); liftType(sig.makeFrom(Type.TOP)); return this; } - return null; - } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/GuardNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/GuardNode.java index 0ce504d9..f808c276 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/GuardNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/GuardNode.java @@ -4,7 +4,6 @@ import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeScalar; -import com.seaofnodes.simple.util.BAOS; import com.seaofnodes.simple.util.Utils; import java.util.HashMap; @@ -25,11 +24,7 @@ public class GuardNode extends Node { _nonZero = guard._nonZero; } - @Override public Tag serialTag() { return Tag.Guard; } - @Override public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node,Integer> anodes) { - baos.write(_nonZero ? 1 : 0); - } - static Node make(BAOS bais) { return new GuardNode(bais.read()!=0,null,null); } + @Override public String label() { return _nonZero ? "(!=0)" : "(==0)"; } @@ -51,6 +46,7 @@ public class GuardNode extends Node { // choosing generic ptr here can be weaker than a later concrete type. if( t==TypeScalar.BOT || t==TypeScalar.TOP ) return t; Type t2 = _nonZero ? t.nonZero() : t.makeZero(); + if( t2!=null ) t2 = t.join(t2); return t2==null ? (_type==null ? Type.TOP : _type) : t2; // No sane answer for e.g. not-zero of a zero } @@ -58,6 +54,8 @@ public class GuardNode extends Node { if( in(0)._type != Type.CONTROL && in(0)._type != Type.BOTTOM ) return null; if( in(1)._type==Type.BOTTOM || in(1)._type==Type.TOP ) return null; + Type t = in(1)._type; + if( _nonZero ? !t.makeZero().isa(t) : t==t.makeZero() ) return in(1); if( _type.isHighOrConst() && in(1)._type.isa(_type) ) return in(1); return null; diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/IfNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/IfNode.java index c83c81d0..3f21417e 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/IfNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/IfNode.java @@ -13,7 +13,9 @@ public class IfNode extends CFGNode implements MultiNode { CodeGen.CODE.add(this); // Because idoms are complex, just add it } public IfNode(IfNode iff) { super(iff); } - @Override public Tag serialTag() { return Tag.If; } + + @Override + public String label() { return "If"; } @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { @@ -44,7 +46,7 @@ public class IfNode extends CFGNode implements MultiNode { // If constant is 0 then false branch is reachable // Else true branch is reachable if( t.isConstant() ) - return (t==Type.NIL || t==TypeInteger.ZERO || (t instanceof TypeFunPtr tfp && tfp._fidxs== XInt.EMPTY) ) ? TypeTuple.IF_FALSE : TypeTuple.IF_TRUE; + return (t==Type.NIL || t==TypeInteger.ZERO || (t instanceof TypeFunPtr tfp && tfp._fidxs==0) ) ? TypeTuple.IF_FALSE : TypeTuple.IF_TRUE; // If adding a zero makes a difference, the predicate must not have a zero/null if( !t.makeZero().isa(t) ) return TypeTuple.IF_TRUE; @@ -57,14 +59,17 @@ public class IfNode extends CFGNode implements MultiNode { // Hunt up the immediate dominator tree. If we find an identical if // test on either the true or false branch, that side wins. if( !pred()._type.isHighOrConst() ) - for( CFGNode dom = idom(), prior=this; dom!=null; prior = dom, dom = dom.idom() ) - if( addDep(dom) instanceof IfNode iff && - prior instanceof CProjNode prj && + for( CFGNode dom = idom(), prior=this; dom!=null; prior = dom, dom = dom.idom() ) { + if( addDep(dom) instanceof IfNode iff && prior instanceof CProjNode prj && (addDep(iff.pred())==pred() || - pred() instanceof GuardNode cast && cast._nonZero && iff.pred()==cast.in(1) ) ) { + (pred() instanceof GuardNode guard && guard._nonZero && iff.pred()==guard.in(1))) ) { setDef(1,con( prj._idx==0 ? 1 : 0 )); return this; } + if( dom instanceof RegionNode r ) + for( Node c : r._inputs ) + if( c!= null ) addDep(c); // If region loses input, idom changes + } return null; } 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 c58bba39..24a2f9b8 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -2,11 +2,9 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; -import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.GlobalBits; +import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.BAOS; import com.seaofnodes.simple.util.Utils; import java.util.BitSet; @@ -24,14 +22,11 @@ public class LoadNode extends MemOpNode { * @param ptr The ptr to the struct base from where we load a field * @param off The offset inside the struct base */ - public LoadNode(Parser.Lexer loc, String name, int alias, Node ctrl, Node mem, Node ptr, Node off) { - super(loc, name, alias, true, Type.BOTTOM, ctrl, mem, ptr, off); + public LoadNode(Parser.Lexer loc, String name, int alias, Node mem, Node ptr, Node off) { + // The field declaration will supply storage width once the receiver + // is known. A parser guess must not bound the value of this Load. + super(loc, name, alias, true, Type.BOTTOM, mem, ptr, off); } - LoadNode( BAOS bais, String[] strs, Type[] types, GlobalBits fileAliases, GlobalBits aliases ) { - super(bais,strs,types,fileAliases,aliases,true); - _con = Type.BOTTOM; // Serialized slot retained while MemOps share TypeNode - } - @Override public Tag serialTag() { return Tag.Load; } @Override boolean canDrop(MemOpNode other, Node dep) { return super.canDrop(other,dep) && !clobbered(dep); @@ -47,8 +42,7 @@ public class LoadNode extends MemOpNode { dep.addDepForwards(use); if( use instanceof StoreNode || use instanceof PhiNode || use instanceof MemMergeNode || - use instanceof NewNode || use instanceof EscapeNode || use instanceof CallNode || - use instanceof CLInitNode ) return true; + use instanceof NewNode || use instanceof CallNode ) return true; } return false; } @@ -60,105 +54,61 @@ public class LoadNode extends MemOpNode { @Override public Type compute() { - Type mem0 = mem()._type; - Type ptr0 = ptr()._type; - // Validate argument types - if( ptr0.isHigh() ) - return TypeScalar.TOP; - if( !(mem0 instanceof TypeMem mem) ) - return mem0.isHigh() ? TypeScalar.TOP : TypeScalar.BOT; - if( mem._t == Type.TOP ) - return TypeScalar.TOP; - if( ptr0 == Type.NIL ) - return Type.BOTTOM; // Load from nil is error - if( ptr0 == Type.BOTTOM ) - return Type.BOTTOM; // Error in is error out - if( !(ptr0 instanceof TypeMemPtr tmp) ) - return scalar(mem._t); - if( tmp.nullable() ) - return Type.BOTTOM; // Load from nil-able is error - - // Load field from object - Field pfld = tmp._obj.field(_name); - // No field? Open objects might yet get the field when falling; - // closed objects with missing field are an error. - if( pfld == null ) - return tmp.isHigh() || tmp._obj.isHigh() ? TypeScalar.TOP : TypeScalar.BOT; - - Type t = pfld._t; - boolean readonly = t instanceof TypeMemPtr view && view._ro; - // Load member of constant array - if( t instanceof TypeConAry ary ) - t = ary.elem(); // TODO: if offset is known, can peek the constant - if( mem._alias==_alias ) - t = t.join(mem._t); - - // A deeply read-only base produces a deeply read-only value. The - // generic declared field type must not cast this information away. - if( ptr0.isFinal() || readonly ) - t = t.makeRO(); - return scalar(t); - } - - // Loads produce ordinary scalar values. Field/memory joins can still - // expose the enclosing global lattice bounds through an uninitialized - // Type.TOP/BOTTOM field; keep those results inside the scalar envelope. - private static Type scalar(Type t) { + Type p = ptr()._type; + Type m = mem()._type; + if( p.isHigh() || m.isHigh() ) return TypeScalar.TOP; + if( p==Type.NIL || p==Type.BOTTOM ) return Type.BOTTOM; + if( !(p instanceof TypeMemPtr ptr) ) return TypeScalar.BOT; + if( ptr.nullable() ) return Type.BOTTOM; + Field field = ptr._obj.field(_name); + if( field==null ) return TypeScalar.BOT; + Type t = field._t; + if( t instanceof TypeConAry ary ) t=ary.elem(); + if( _alias!=1 ) t=t.join(MemMergeNode.contents(mem(),_alias,this)); + if( ptr.isFinal() ) t=t.makeRO(); return t==Type.TOP ? TypeScalar.TOP : t==Type.BOTTOM ? TypeScalar.BOT : t; } @Override public Node idealize() { - Node mem = mem(); - Node ptr = ptr(); - - // Null checks are handled by types. Array elements retain control for - // range checks, but reading an array's length has no index to check. - if( in(0)!=null && ptr._type instanceof TypeMemPtr tmp && - (!tmp._obj.isAry() || _name.equals("#")) ) { - setDef(0,null); - return this; + if( ptr()._type instanceof TypeMemPtr ptr && !ptr._obj._open ) { + Field field = ptr._obj.field(_name); + // Storage width comes from the declaration, not from a constant + // or range inferred for this particular object's current value. + Type declared = Parser.TYPES.get(ptr._obj._name); + if( declared instanceof TypeMemPtr shape && shape._obj.field(_name)!=null ) + field=shape._obj.field(_name); + Type storage = field==null ? Type.BOTTOM : field._t instanceof TypeConAry ary ? ary.elem() : field._t; + if( field!=null && (_alias!=field._alias || _declaredType!=storage) ) { + unlock(); + _alias=field._alias; + _declaredType=storage; + return this; + } } + if( _alias==1 ) return null; - // Forward-ref loads eventually sharpen to a declared type - Field fld; - if( ptr._type instanceof TypeMemPtr tmp && - (fld=tmp._obj.field(_name)) != null && - _alias != fld._alias) { - assert _alias==1 || _alias == fld._alias; - assert !tmp._obj._open && !tmp._obj._fref; - unlock(); // Alias participates in GVN semantics - _alias = fld._alias; + if( mem() instanceof CastNode cast ) { + setDef(1,cast.in(1)); return this; } - // Must sharpen alias first - if( _alias == 1 ) - return null; - - Node slice = CLInitNode.slice(mem,_alias,this); - if( slice!=mem ) { setDef(1,slice); return this; } - - if( mem instanceof MemMergeNode merge ) { - setDef(1,merge.alias(_alias)); - // In support of uplifting control to a prior dominating load, if - // we move our memory, recheck other loads to see if they can hoist. - for( Node use : in(1).outs() ) - if( use instanceof LoadNode ld && use != this && ptr == ld.ptr() ) - CodeGen.CODE.add(ld); - + if( mem() instanceof MemMergeNode merge ) { + setDef(1,CodeGen.CODE.add(merge.alias(_alias))); return this; } + Node ptr = ptr(); + Node mem = mem(); + + Node win = find(mem,ptr,null,null); + if( win!=null ) return folded(win); // Uplift control to a prior dominating load. for( Node memuse : mem._outputs ) // Find a prior load, has same mem,ptr,off but higher ctrl if( memuse != this && memuse instanceof LoadNode ld && ptr==ld.ptr() && off()==ld.off() && - cfg0()!=null && cfg0().domLCA(ld.cfg0(),this) == ld.cfg0() && // Higher control means load is legal earlier - addDep(ld)._type.isa(_type) ) // and not rolling backwards + cfg0()!=null && cfg0().domLCA(ld.cfg0(),this) == ld.cfg0() ) // Higher control means load is legal earlier return ld; - Node win = find(mem,ptr,null,null); - if( win!=null ) return folded(win); if( ptr instanceof ReadOnlyNode ro ) ptr=ro.in(1); // Push a Load up through a Phi, as long as it collapses on at least @@ -183,17 +133,13 @@ public class LoadNode extends MemOpNode { !(ptr instanceof PhiNode p && p.region()==memphi.region()) ? memphi : null; if( find(memphi.in(2),ptr,stop,new BitSet())!=null || (!(memphi.region() instanceof LoopNode) && find(memphi.in(1),ptr,null,new BitSet())!=null) ) { - // Integer loop Phis widen to i64. Keep the load's declared - // width at the result, as for ordinary typed loop variables. - if( _type instanceof TypeInteger && !declaredType().isa(_type) ) return null; PhiNode phi = new PhiNode(_name,memphi.region()).keep(); - phi.setType(_type instanceof TypeInteger ? TypeInteger.BOT : _type); + phi.setType(_type); // Backedge searches may return this value Phi. for( int i=1; i<memphi.nIns(); i++ ) { Node p = ptr instanceof PhiNode pp && pp.region()==memphi.region() ? pp.in(i) : ptr; phi.addDef(load(memphi.in(i),p,stop,phi)); } - CodeGen.CODE.add(phi.unkeep()); - return _type instanceof TypeInteger ? new ConvertNode(declaredType(),phi) : phi; + return CodeGen.CODE.add(phi.unkeep()); } } @@ -202,7 +148,8 @@ public class LoadNode extends MemOpNode { // Load a flavored zero from a New private Node zero(NewNode nnn) { - Type zero = declaredType().makeZero(); + TypeStruct ts = nnn._ptr._obj; + Type zero = ts._fields[ts.findAlias(_alias)]._t.makeZero(); assert zero.isa(_type); // Catch an uninitialized non-null field return castRO(new ConstantNode(zero).peephole()); } @@ -211,40 +158,29 @@ public class LoadNode extends MemOpNode { // the unchanged loop memory, or a merge whose every arm folds. Null fails. // Register dependencies so a later pointer/offset rewrite retries the query. private Node find(Node mem, Node ptr, Node stop, BitSet visit) { - if( ptr instanceof ReadOnlyNode ro ) ptr=ro.in(1); - Field fld; - boolean external = ptr._type instanceof TypeMemPtr tmp && - (fld=tmp._obj.field(_name))!=null && fld._extern; + ptr = address(ptr); while( mem!=null ) { addDep(mem); if( mem==stop ) return mem; switch( mem ) { case MemMergeNode merge: mem=merge.alias(_alias); break; + case CastNode cast: mem=cast.in(1); break; case StoreNode st: - // Alias 1 is still unresolved, not a proven different field. + // A symbolic Store may write this field. Wait for its alias; + // skipping it can make a compound assignment read the old value. if( st._alias==1 ) return null; addDep(st.ptr()); - if( _alias==st._alias && (ptr==st.ptr() || ptr==st.nnptr()) && off()==st.off() ) { - // A full-width load cannot narrow the forwarded value. - if( declaredType() instanceof TypeInteger ti && ti.log_size()==3 && - !addDep(st.val())._type.isa(_type) ) return null; - return st; - } - if( (external || _alias==st._alias) && (external || !neverAlias(ptr,st.ptr())) && + if( _alias==st._alias && ptr==address(st.ptr()) && off()==st.off() ) return st; + if( _alias==st._alias && !neverAlias(ptr,address(st.ptr())) && !addDep(off())._type.join(addDep(st.off())._type).isHigh() ) return null; mem=st.mem(); break; case ProjNode proj: - // New's memory is private; public memory is joined at Escape. - if( external || !(proj.in(0) instanceof NewNode nnn) ) return null; - if( declaredType()==Type.BOTTOM ) return null; - return nnn; - case EscapeNode esc: - if( external ) return null; - addDep(esc.self()); - if( esc.self()==ptr ) { mem=esc.priv(); break; } - if( neverAlias(ptr,esc.self()) ) { mem=esc.pub(); break; } - return null; + if( !(proj.in(0) instanceof NewNode nnn) ) return null; + if( ptr instanceof ProjNode p && p.in(0)==nnn ) return nnn; + if( !(ptr instanceof ProjNode p && p.in(0) instanceof NewNode) ) return null; + mem=nnn.mem(); // Distinct allocations cannot overlap. + break; case MemPhiNode phi: addDep(phi.region()); // Only the original loop closes a successful cycle. Another @@ -262,6 +198,13 @@ public class LoadNode extends MemOpNode { return null; } + // Guards and read-only views keep the same address. Retain the Load's + // guarded input for its null check, but compare actual addresses here. + private static Node address(Node ptr) { + while( ptr instanceof ReadOnlyNode || ptr instanceof GuardNode ) ptr=ptr.in(1); + return ptr; + } + private Node folded(Node win) { return win instanceof StoreNode st ? extend(castRO(st.val())) : zero((NewNode)win); } @@ -275,12 +218,12 @@ public class LoadNode extends MemOpNode { if( win instanceof MemPhiNode mp ) { PhiNode phi=new PhiNode(_name,mp.region()).keep(); for( int i=1; i<mp.nIns(); i++ ) phi.addDef(load(mp.in(i),ptr,stop,value)); - return CodeGen.CODE.add(phi.unkeep().peephole()); + return phi.unkeep().peephole(); } - Node fold=folded(win); - if( fold!=null ) return fold.peephole(); + return folded(win).peephole(); } - LoadNode ld=new LoadNode(_loc,_name,_alias,in(0),mem,ptr,off()); + LoadNode ld=new LoadNode(_loc,_name,_alias,mem,ptr,off()); + ld.setDef(0,in(0)); return ld.peephole(); } @@ -312,7 +255,7 @@ public class LoadNode extends MemOpNode { // Read-Only is a deep property, and cannot be cast-away private Node castRO(Node rez) { - if( (ptr()._type.isFinal() || declaredType().isFinal()) && !rez._type.isFinal() ) + if( (ptr()._type.isFinal() || _declaredType.isFinal()) && !rez._type.isFinal() ) return new ReadOnlyNode(rez).peephole(); return rez; } @@ -320,18 +263,16 @@ public class LoadNode extends MemOpNode { // When a load bypasses a store, the store might truncate bits - and the // load will need to zero/sign-extend. private Node extend(Node val) { - if( !(declaredType() instanceof TypeInteger ti) ) return val; + if( !(_declaredType instanceof TypeInteger ti) ) return val; if( ti._min==0 ) // Unsigned return new AndNode(null,val,con(ti._max)); // Signed extension int shift = Long.numberOfLeadingZeros(ti._max)-1; Node shf = con(shift); - if( shift==0 ) { - if( !val._type.isa(_type) ) - { addDep(val); return null; } + if( shf._type==TypeInteger.ZERO ) return val; - } Node shl = new ShlNode(null,val,shf.keep()).peephole(); return new SarNode(null,shl,shf.unkeep()); } + } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/LoopNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/LoopNode.java index df686bcf..1d493db7 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/LoopNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/LoopNode.java @@ -3,22 +3,23 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; import com.seaofnodes.simple.type.TypeMem; -import com.seaofnodes.simple.util.BAOS; - -import java.util.HashMap; -import java.util.IdentityHashMap; +import com.seaofnodes.simple.util.Utils; +import java.util.BitSet; +import java.util.HashSet; public class LoopNode extends RegionNode { - public LoopNode( Parser.Lexer loc, Node xctrl, Node entry ) { super(loc,xctrl,entry,null); } + public LoopNode( Parser.Lexer loc, Node entry ) { super(loc,null,entry,null); } public LoopNode( LoopNode loop ) { super(loop); } - @Override public Tag serialTag() { return Tag.Loop; } - public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { } public CFGNode entry() { return cfg(1); } public CFGNode back () { return cfg(2); } @Override + public String label() { return "Loop"; } + + @Override public Type compute() { if( inProgress() ) return Type.CONTROL; return entry()._type; @@ -45,7 +46,7 @@ public class LoopNode extends RegionNode { x = x.idom(); } // Found a no-exit loop. Insert an exit - NeverNode iff = (NeverNode)new NeverNode(1.2f,back()).peephole(); // Ideal never-branch + NeverNode iff = (NeverNode)new NeverNode(back()).peephole(); // Ideal never-branch CProjNode t = new CProjNode(iff,0,"True" ).init(); CProjNode f = new CProjNode(iff,1,"False").init(); setDef(2,t); // True continues loop, False (never) exits loop @@ -55,13 +56,12 @@ public class LoopNode extends RegionNode { // Now fold control into the exit. Might have 1 valid exit, or an // XCtrl or a bunch of prior NeverNode exits. - Node top = ConstantNode.make(Type.TOP).peephole(); - Node memout = new MemMergeNode(); - memout.addDef(f); // placeholder for control - memout.addDef(top); + Node top = new ConstantNode(Type.TOP).peephole(); + MemMergeNode memout = new MemMergeNode(new ConstantNode(TypeMem.BOT).peephole()); + memout.setDef(0,f); for( Node u : _outputs ) - if( u instanceof PhiNode phi && phi._type.isa(TypeMem.BOT) ) - memout.addDef(phi); + if( u instanceof BulkMemPhiNode ) memout.setDef(1,u); + else if( u instanceof MemPhiNode phi ) memout.alias(phi._alias,phi); memout.init(); Node ctrl = ret.ctrl(), mem = ret.mem(), expr = ret.expr(); @@ -73,7 +73,7 @@ public class LoopNode extends RegionNode { // Nope, insert an aligned exit layer RegionNode r = new RegionNode(_loc,null,ctrl).init(); ctrl = r; r._ltree = fun._ltree; - mem = new BulkMemPhiNode(r,mem).init(); + mem = new BulkMemPhiNode(ScopeNode.MEM0,r,mem).init(); expr = new PhiNode(r,expr).init(); } // Append new Never exit @@ -88,12 +88,8 @@ public class LoopNode extends RegionNode { ret.setDef(0,ctrl.unkeep()); ret.setDef(1,mem .unkeep()); ret.setDef(2,expr.unkeep()); - // Force sane types to exit; might locally break monotonicity - ret.init(); - ret.out(0).init(); - stop.init(); - CodeGen.CODE.add(CodeGen.CODE._start); - CodeGen.CODE.add(mem); + ret._type = null; // This may LIFT the Return type, from [XCtrl,mem,expr] to [Ctrl,mem,expr] + ret.setType(ret.compute()); return fun; } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java index b25fcaaf..9c0755b9 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java @@ -3,6 +3,7 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.util.Utils; import java.io.ByteArrayOutputStream; // Generic machine-specific class, has a few Node implementations that have to @@ -13,8 +14,8 @@ public abstract class MachConcreteNode extends Node implements MachNode { public MachConcreteNode(Node[]nodes) { super(nodes); } @Override public String label() { return op(); } - @Override public Type compute () { throw new AssertionError("Do not call compute on "+getClass().getSimpleName()); } - @Override public Node idealize() { throw new AssertionError("Do not call idealize on "+getClass().getSimpleName()); } + @Override public Type compute () { throw Utils.TODO(); } + @Override public Node idealize() { throw Utils.TODO(); } @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { p.open().p(op()).p(","); diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/MachNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MachNode.java index 025b5dc4..c0b3af66 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/MachNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/MachNode.java @@ -55,6 +55,6 @@ public interface MachNode { // General form: "dst=src+src". default void asm(CodeGen code, SB sb) { throw Utils.TODO(); } - // Human-readable opcode; e.g. "ld4" or "call". + // Human readable opcode; e.g. "ld4" or "call". default String op() { throw Utils.TODO(); } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java index dfe8794d..7a329117 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java @@ -4,154 +4,99 @@ import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.*; import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.util.BAOS; import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.Utils; - import java.util.*; -/** - * Memory Merge - a merge of many aliases into a "fat memory". All aliases - * are here, but most will be lazy - take the default fat memory. +/** A default slice and explicit alias overrides. A null default means partial + * memory: absent aliases are not covered, rather than having unknown contents. */ public class MemMergeNode extends Node { - - public MemMergeNode( Node ...nodes) { super(nodes); _type = TypeMem.BOT; } public MemMergeNode(MemMergeNode mem) { super(mem); } - @Override public Tag serialTag() { return Tag.MemMerge; } - public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { baos.packed1(nIns()); } - static Node make( BAOS bais ) { Node mem = new MemMergeNode(); mem.setDefX(bais.packed1()-1,null); return mem; } - @Override public String label() { return "ALLMEM"; } - @Override public boolean isMem() { return true; } + public MemMergeNode(Node bulk) { super(null,bulk); _type = TypeMem.BOT; } + public MemMergeNode(Node bulk, int alias, Node precise) { + this(bulk); + alias(alias,precise); + } + public Node alias(int alias) { + Node n = alias < nIns() ? in(alias) : null; + assert n!=null || !(in(1) instanceof BulkMemPhiNode bulk) || !bulk.isSplit(alias); + n = n==null ? in(1) : n; + assert n!=null : "Alias not covered: "+alias; + return n; + } - @Override - protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - p.p("MEM[ "); - for( int j=2; j<nIns(); j++ ) { - p.p(j); - p.p(":"); - Node n = in(j); - while( n instanceof ScopeNode loop ) { - p.p("Lazy_"); - n = loop.mem(); - } - if( n==null ) p.p("___ "); - else p.n(n).p(" "); - } - return p.unchar(' ').p("]"); + public void alias(int alias, Node mem) { + assert alias > 1; + while( nIns()<=alias ) addDef(null); + setDef(alias,mem); } + // Scalar contents of one alias. New zeroes the allocated fields; + // join it with the incoming contents for all previously allocated objects. + // Phis use their cached types, so this query does not recurse around loops. + static Type contents(Node mem, int alias, Node dep) { + // A loop memory Phi can also be a user of the querying Store. + // Its type changing still needs to revisit this query. + dep.addDepForwards(mem); + if( mem instanceof CastNode cast ) return contents(cast.in(1),alias,dep); + if( mem instanceof MemMergeNode merge ) + return contents(merge.alias(alias),alias,dep); + if( mem instanceof ProjNode proj && proj.in(0) instanceof NewNode nnn ) { + assert proj._idx==1 && nnn.field(alias)!=null; + dep.addDep(nnn); + if( dep.addDep(nnn.in(0))._type.isHigh() ) return Type.TOP; + return contents(nnn.mem(),alias,dep).meet(nnn.field(alias)._t.makeZero()); + } + // Function parameters and call results may contain arbitrary heap values. + // Their bulk memory types do not imply empty or zero-filled storage. + if( mem._type==Type.TOP || mem._type==TypeMem.TOP ) return Type.TOP; + if( mem._type instanceof TypeMem mt && mt._alias==alias ) return mt._t; + return Type.BOTTOM; + } - @Override public Type compute() { - Type tmem = in(1)._type; - if( !(tmem instanceof TypeMem defmem) ) - // A MemMerge is structurally memory even while its default input - // is a weak, not-yet-specialized Phi. - return tmem.isHigh() ? TypeMem.TOP : TypeMem.BOT; - // Is this a single private instance memory? - if( defmem._one ) { - if( !(defmem._t instanceof TypeStruct ts) ) - return defmem; - // Perfect singleton memory, so all updates are parallel and - // independent and stack. - for( int i=2; i<nIns(); i++ ) { - Node in = in(i); - if( in !=null && in._type.isHigh() ) - return TypeMem.TOP; - if( in instanceof StoreNode st ) { - Type val = ((TypeMem)st._type)._t; - Field old = ts.field(st._name); - // Lazy add an expected field for open structs. - if( old==null ) { assert ts._open; - old = Field.make(st._name,Type.BOTTOM,st._alias,st._init); - ts = ts.add(old); - } - Field fld = old.makeFrom(val); - // TODO: val leaks into perfect singleton memory, along with its fidxs and aliases - ts = ts.replace(fld); - } + @Override public String label() { return "MemMerge"; } + @Override public boolean isMem() { return true; } + @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + p.p("MEM["); + for( int i=1; i<nIns(); i++ ) + if( in(i)!=null ) { + p.p(i==1 ? "default:" : i+":"); + p.n(in(i)).p(" "); } - return TypeMem.makePrivate(ts); + return p.p("]"); + } - } - // Normal public memory; meet across escaped inputs - TypeMem mem = defmem; - for( int i=2; i<nIns(); i++ ) { - // Has this alias escaped? - if( XInt.bit(mem._escAs,i) && - in(i) != null && !in(i)._type.isHigh() ) { - mem = (TypeMem)mem.meet(in(i)._type); - } - } - return mem; + @Override public Type compute() { + for( Node n : _inputs ) + if( n != null && !n._type.isHigh() ) + return TypeMem.BOT; + return TypeMem.TOP; } @Override public Node idealize() { - assert nIns() != 0 && in(1)!=null; // Always have a default. if( nIns()==0 ) return null; - - // Fold defaults into the default boolean progress=false, allDefault=true; for( int i=2; i<nIns(); i++ ) { - if( in(1) == in(i) ) { setDef(i,null); progress=true; } - else { allDefault=false; } + if( in(i) instanceof CastNode cast ) { setDef(i,cast.in(1)); progress=true; } + if( in(i)!=null && in(i)==in(1) ) { setDef(i,null); progress=true; } + if( in(i) instanceof MemMergeNode mem ) { + setDef(i,CodeGen.CODE.add(mem.alias(i))); + progress=true; + } + if( in(i)!=null ) allDefault=false; } + if( allDefault && in(1)!=null ) return in(1); - // If not merging any memory (all memory is just the default) - if( allDefault ) - return in(1); // Become default memory - - // Collapse stacked merged-mem if( in(1) instanceof MemMergeNode mem ) { - // A dead memory cycle can lose its last use when we bypass mem. - // Keep the peephole's result alive until the worklist sees it. - keep(); - // Goal is to swap my default mem with mem's default mem - for( int i=2; i<mem.nIns(); i++ ) { - if( mem.in(i) != null ) { - // deeper default mem has a non-default - if( i>=nIns() || in(i)==null ) - setDefX(i,mem.in(i)); - } - } + for( int i=2; i<mem.nIns(); i++ ) + if( mem.in(i)!=null && (i>=nIns() || in(i)==null) ) + alias(i,mem.in(i)); CodeGen.CODE.add(mem.in(1)); setDef(1,mem.in(1)); - return unkeep(); + return this; } - + assert BulkMemPhiNode.checkMerge(this); return progress ? this : null; } - - - public Node in( Var v ) { return in(v._idx); } - - public Node alias( int alias ) { - assert !(in(1) instanceof BulkMemPhiNode bulk && bulk.isSplit(alias) && - (alias >= nIns() || in(alias)==null)); - return alias < nIns() && in(alias)!=null ? in(alias) : in(1); - } - - public Node alias( int alias, Node st ) { return setDefX(alias,st); } - - // Now one-time do a useless-phi removal - void _useless( ) { - for( int i=1; i<nIns(); i++ ) { - if( in(i) instanceof PhiNode phi ) { - // Do an eager useless-phi removal - Node in = phi.peephole(); - CodeGen.CODE.addAll(phi._outputs); - phi.moveDepsToWorklist(); - if( in != phi ) { - if( !phi.iskeep() ) // Keeping phi around for parser elsewhere - phi.subsume(in); - setDef(i,in); // Set the update back into Scope - } - } - } - } - - @Override public boolean eq( Node n ) { - return this==n; - } } 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 bfbd46c3..1f0fc4d3 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/MemOpNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/MemOpNode.java @@ -3,12 +3,12 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.GlobalBits; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.*; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeMemPtr; +import com.seaofnodes.simple.util.Utils; import java.lang.StringBuilder; -import java.util.*; +import java.util.BitSet; +import java.util.ArrayList; /** * Convenience common base for Load and Store. @@ -19,102 +19,93 @@ import java.util.*; * 3 - Offset, integer types * 4 - Value, for Stores only */ -public abstract class MemOpNode extends TypeNode { +public abstract class MemOpNode extends Node { // The equivalence alias class public int _alias; // True if load-like, false if store-like. - // Used on CPU-specific combined memory+arithmetic ops. // // Stores produce memory (maybe as part of a tuple with other things), // loads do not. // - // Loads might pick up anti-dependencies on prior Stores, and never cause an + // Loads might pick up anti-dependences on prior Stores, and never cause an // anti-dependence themselves. // // Stores must maximally sink to the least dominator use. Loads can be // opportunistically hoisted. public final boolean _isLoad; - // Field name. + // Declared type; not final because it might be a forward-reference + // which will be lazily improved when the reference is declared. + public Type _declaredType; + + // A debug name, no semantic meaning public final String _name; // Source location for late reported errors public final Parser.Lexer _loc; - public MemOpNode(Parser.Lexer loc, String name, int alias, boolean isLoad, Type decl, Node ctrl, Node mem, Node ptr, Node off) { - super(decl, ctrl, mem, ptr, off); + public MemOpNode(Parser.Lexer loc, String name, int alias, boolean isLoad, Type glb, Node mem, Node ptr, Node off) { + super(null, mem, ptr, off); _name = name; _alias = alias; + _declaredType = glb; _loc = loc; _isLoad = isLoad; } - public MemOpNode(Parser.Lexer loc, String name, int alias, boolean isLoad, Type decl, Node ctrl, Node mem, Node ptr, Node off, Node value) { - this(loc, name, alias, isLoad, decl, ctrl, mem, ptr, off); + public MemOpNode(Parser.Lexer loc, String name, int alias, boolean isLoad, Type glb, Node mem, Node ptr, Node off, Node value) { + this(loc,name, alias, isLoad, glb, mem, ptr, off); addDef(value); } - public MemOpNode( Node ideal, MemOpNode mop ) { - super(ideal,mop instanceof LoadNode ld ? ld.declaredType() : mop._con); - _name = mop._name; - _alias = mop._alias; - _loc = mop._loc; - _isLoad= mop._isLoad; - } - - MemOpNode( BAOS bais, String[] strs, Type[] types, GlobalBits fileAliases, GlobalBits aliases, boolean isLoad ) { - this(null, - strs[bais.packed2()], - mapAlias(bais,fileAliases,aliases),isLoad, - types[bais.packed2()],null,null,null,null); - } - - private static int mapAlias( BAOS bais, GlobalBits fileAliases, GlobalBits aliases ) { - int alias = bais.packed2(); - return alias < GlobalBits.RESERVED ? alias : aliases.map(fileAliases,alias); + public MemOpNode( Node mach, MemOpNode mop ) { + super(mach); + _name = mop==null ? null : mop._name; + _alias = mop==null ? 0 : mop._alias; + _loc = mop==null ? null : mop._loc; + _isLoad= mop==null ? true : mop._isLoad; + _declaredType = mop==null ? Type.BOTTOM : mop._declaredType; + if( mop==null ) + throw Utils.TODO("Load or not"); } - @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { - baos.packed2(_name==null ? 0 : strs.get(_name)); - baos.packed2(_alias); - baos.packed2(types.get(_con)); // NPE if fails lookup - assert _isLoad == this instanceof LoadNode; // No machine ops + // Used by M2 when translating graph to Simple + public MemOpNode( boolean isLoad ) { + super((Node)null); + _name = null; + _alias = 0; + _loc = null; + _isLoad = isLoad; + _declaredType = Type.BOTTOM; } - static String mlabel(String name) { return "[]"==name ? "ary" : ("#"==name ? "len" : name); } + static String mlabel(String name) { return "[]".equals(name) ? "ary" : ("#".equals(name) ? "len" : name); } String mlabel() { return mlabel(_name); } + @Override public boolean isMem() { return !_isLoad; } + public Node mem() { return in(1); } public Node ptr() { return in(2); } public Node off() { return in(3); } - // Current declared field type. This is deliberately a live lookup: - // cyclic/forward types can sharpen by replacing the pointer's struct. - public Type declaredType() { - if( !(ptr()._type instanceof TypeMemPtr tmp) ) - return Type.BOTTOM; - Field fld = tmp._obj.field(_name); - return fld == null || fld._t == null ? Type.BOTTOM : fld._t; - } - @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return _printMach(p); } protected ExprPrinter<Node> _printMach(ExprPrinter<Node> p) { throw Utils.TODO(); } - + public int log_size() { return _declaredType.log_size(); } // Extra conditions for factoring matching memory operations through a Phi. // The caller has already checked the opcode, input shape and control. boolean canDrop(MemOpNode other, Node dep) { dep.addDep(ptr()); - return _alias>1 && _alias==other._alias && declaredType()==other.declaredType() && err()==null; + return _alias==other._alias && _declaredType==other._declaredType && err()==null; } @Override public boolean eq(Node n) { MemOpNode mem = (MemOpNode)n; // Invariant - return _alias==mem._alias && super.eq(mem); + return _alias==mem._alias; // When comparing types error to use "equals"; always use "==" } @Override - int hash() { return _alias ^ super.hash(); } + int hash() { return _alias; } @Override public Parser.ParseException err() { @@ -123,37 +114,41 @@ public abstract class MemOpNode extends TypeNode { if( ptr == Type.BOTTOM ) return null; if( ptr.isHigh() ) return null; // Assume it will fall to not-null // Better be a not-nil TMP - if( !(ptr instanceof TypeMemPtr tmp && tmp.notNull()) ) - return Parser.error( "Might be null accessing '" + _name + "'",_loc); - if( tmp._obj.field(_name)==null && - CodeGen.CODE._phase.ordinal() > CodeGen.Phase.FinalizeIR.ordinal() ) - return Parser.error("Accessing unknown field '"+_name+"' from '*"+tmp._obj._name+"'",_loc); - // Sane field - if( off() instanceof ConFldOffNode && CodeGen.CODE._phase.ordinal() > CodeGen.Phase.FinalizeIR.ordinal() ) - return Parser.error("Accessing unknown field '"+_name+"' from '*"+tmp._obj._name+"'",_loc); - return null; + if( ptr instanceof TypeMemPtr tmp && tmp.notNull() ) + return null; + return Parser.error( "Might be null accessing '" + _name + "'",_loc); } - // Memory writers which must follow this read. A packaging node is not a - // write; follow this alias through aggregates to its writers. - public Ary<Node> antiDeps( Ary<Node> antideps ) { - antideps.clear(); - return antiDeps(mem(),antideps); + // write, but can hide New's input, so follow this alias through aggregates. + public ArrayList<Node> antiDeps() { + ArrayList<Node> deps = new ArrayList<>(); + antiDeps(mem(),deps,new BitSet()); + return deps; } - private Ary<Node> antiDeps( Node mem, Ary<Node> antideps ) { + private void antiDeps(Node mem, ArrayList<Node> deps, BitSet visit) { + if( visit.get(mem._nid) ) return; + visit.set(mem._nid); for( Node use : mem._outputs ) { if( use instanceof MemMergeNode merge ) { - if( merge.alias(_alias)==mem ) - antiDeps(merge,antideps); - } else if( use instanceof CallNode || use instanceof CLInitNode || + Node slice = _alias<merge.nIns() ? merge.in(_alias) : null; + if( (slice==mem) || (slice==null && merge.in(1)==mem) ) + antiDeps(merge,deps,visit); + } else if( use instanceof CallNode || (use instanceof MemOpNode st && !st._isLoad && st._alias==_alias) || - (use instanceof EscapeNode esc && esc.fld()._alias==_alias) || + (use instanceof NewNode nnn && nnn.field(_alias)!=null) || (use instanceof MemPhiNode phi && phi._alias==_alias) || (use instanceof BulkMemPhiNode phi && !phi.isSplit(_alias)) ) { - if( !antideps.contains(use) ) antideps.add(use); + if( !deps.contains(use) ) deps.add(use); } } - return antideps; + } + + @Override boolean _upgradeType(java.util.HashMap<String,Type> types) { + Type next = _declaredType.upgradeType(types); + if( next==_declaredType ) return false; + unlock(); + _declaredType=next; + return true; } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java index aaaedb89..ba53cfd8 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java @@ -1,69 +1,42 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeMem; -import com.seaofnodes.simple.codegen.GlobalBits; -import com.seaofnodes.simple.util.BAOS; -import java.util.HashMap; -import java.util.IdentityHashMap; - -/** A Phi for one precise memory alias. */ +/** A Phi for one field alias, parallel to a bulk Phi at the same Region. */ public class MemPhiNode extends PhiNode { - public final int _alias; public MemPhiNode(String label, int alias, Node... inputs) { - super(label, inputs); + super(label,inputs); assert alias > 1; _alias = alias; } - public MemPhiNode(MemPhiNode phi) { - super(phi,phi._label); - _alias = phi._alias; - } - - @Override public Tag serialTag() { return Tag.MemPhi; } - - @Override - public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, - IdentityHashMap<Node,Integer> anodes) { - super.packed(baos,strs,types,anodes); - baos.packed2(_alias); - } + public MemPhiNode(MemPhiNode phi) { super(phi); _alias = phi._alias; } - static Node make(BAOS bais, String[] strs, Type[] types, GlobalBits fileAliases, GlobalBits aliases) { - Node[] ins = new Node[bais.packed1()]; - String label = strs[bais.packed2()]; - int alias = bais.packed2(); - if( alias >= GlobalBits.RESERVED ) alias = aliases.map(fileAliases,alias); - return new MemPhiNode(label,alias,ins); - } + @Override public boolean isMem() { return true; } + @Override public String label() { return "MemPhi_"+_alias; } - @Override - public Type compute() { - Type t = super.compute(); + @Override public Type compute() { assert BulkMemPhiNode.checkMem(this,_alias); - if( !(t instanceof TypeMem tmem) ) return t; - assert tmem._alias==1 || tmem._alias==_alias; - // This Phi structurally knows its alias, but not whether the field is - // final. Do not infer slice finality from evolving input types. - return TypeMem.make(_alias,tmem._t,tmem._one,tmem._clz,false,tmem._escFs,tmem._escAs); + if( !(region() instanceof RegionNode r) ) + return (region()._type==Type.XCONTROL || region()._type==Type.TOP) ? TypeMem.TOP : _type; + if( r.inProgress() ) return r instanceof LoopNode ? TypeMem.make(_alias,Type.BOTTOM) : TypeMem.BOT; + Type t = Type.TOP; + for( int i=1; i<nIns(); i++ ) + if( addDep(r.in(i))._type!=Type.XCONTROL && r.in(i)._type!=Type.TOP ) + t = t.meet(MemMergeNode.contents(in(i),_alias,this)); + return TypeMem.make(_alias,t); } - @Override - public Node idealize() { - // A precise memory Phi can always bypass a MemMerge and select its - // matching slice. - for( int i=1; i<nIns(); i++ ) { + @Override public Node idealize() { + for( int i=1; i<nIns(); i++ ) if( in(i) instanceof MemMergeNode mem ) { - setDef(i,mem.alias(_alias)); + setDef(i,CodeGen.CODE.add(mem.alias(_alias))); return this; } - Node slice = CLInitNode.slice(in(i),_alias,this); - if( slice!=in(i) ) { setDef(i,slice); return this; } - } if( nIns()==3 && region() instanceof LoopNode loop && !loop.inProgress() && in(2) instanceof StoreNode st && st.mem()==this ) { Node sink = st.sink(this); @@ -72,9 +45,6 @@ public class MemPhiNode extends PhiNode { return super.idealize(); } - @Override public boolean eq(Node n) { - return _alias==((MemPhiNode)n)._alias && super.eq(n); - } - + @Override public boolean eq(Node n) { return _alias==((MemPhiNode)n)._alias && super.eq(n); } @Override int hash() { return _alias; } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/MinusFNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MinusFNode.java new file mode 100644 index 00000000..c3c27883 --- /dev/null +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/MinusFNode.java @@ -0,0 +1,32 @@ +package com.seaofnodes.simple.node; + + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFloat; + + +public class MinusFNode extends Node { + public MinusFNode(Node in) { super(null, in); } + public MinusFNode(MinusNode n) { super(n); } + + @Override public String label() { return "MinusF"; } + + @Override protected String format() { return "(-%1)"; } + + @Override + public Type compute() { + if( in(1)._type.isHigh() ) return TypeFloat.TOP; + if( in(1)._type instanceof TypeFloat i0) + return i0.isConstant() ? TypeFloat.constant(-i0.value()) : i0; + return TypeFloat.F64; + } + + @Override + public Node idealize() { + // -(-x) is x + if( in(1) instanceof MinusFNode minus ) + return minus.in(1); + + return null; + } +} diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MinusNode.java index e84eb611..4d7e3ddb 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -4,7 +4,6 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.BAOS; import com.seaofnodes.simple.util.Utils; import java.util.HashMap; import java.util.IdentityHashMap; @@ -15,10 +14,7 @@ public class MinusNode extends Node implements ModeNode { public MinusNode(Node in) { super(null, in); } public MinusNode(Node in, byte mode) { super(null, in); _mode = mode; } - @Override public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node,Integer> anodes) { - baos.packed1(_mode); - } - @Override public Tag serialTag() { return Tag.Minus; } + @Override public byte mode() { return _mode; } @Override public Node setMode(byte mode) { diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/MulFNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MulFNode.java new file mode 100644 index 00000000..1126319b --- /dev/null +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/MulFNode.java @@ -0,0 +1,37 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFloat; + +import java.util.BitSet; + +public class MulFNode extends ArithFNode { + public MulFNode(Node lhs, Node rhs) { super(lhs, rhs); } + public MulFNode(ArithNode n) { super(n); } + + @Override public String label() { return "MulF"; } + + @Override public String glabel() { return "*"; } + + @Override double doOp( double x, double y ) { return x*y; } + + @Override + public Node idealize() { + Node lhs = in(1); + Node rhs = in(2); + Type t1 = lhs._type; + Type t2 = rhs._type; + + // Mul of 1. We do not check for (1*x) because this will already + // canonicalize to (x*1) + if ( t2.isConstant() && t2 instanceof TypeFloat i && i.value()==1 ) + return lhs; + + // Move constants to RHS: con*arg becomes arg*con + if ( t1.isConstant() && !t2.isConstant() ) + return swap12(); + + return null; + } + @Override Node copy(Node lhs, Node rhs) { return new MulFNode(lhs,rhs); } +} diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MulNode.java index bb99cad5..37d6a2bc 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -9,7 +9,6 @@ public class MulNode extends ArithNode { @Override boolean allowFloat() { return true; } public MulNode(Node lhs, Node rhs) { super(null, lhs, rhs); } public MulNode(Node lhs, Node rhs, byte mode) { super(null, lhs, rhs, mode); } - @Override public Tag serialTag() { return Tag.Mul; } @Override public String op() { return "*"; } @@ -36,7 +35,7 @@ public class MulNode extends ArithNode { // canonicalize to (x*1) long c = i.value(); if( c==1 ) return lhs._type.isa(compute()) ? lhs : super.idealize(); - if( c==0 ) return CodeGen.CODE.ZERO; + if( c==0 ) return com.seaofnodes.simple.Parser.ZERO; // Mul by a power of 2, +/-1. Bit patterns more complex than this // are unlikely to win on an X86 vs the normal "imul", and so // become machine-specific. diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/MultiNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MultiNode.java index 152d525a..b4d6d78e 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/MultiNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/MultiNode.java @@ -25,5 +25,5 @@ public interface MultiNode { // Return not-null if this projection index is a ideal copy. // Called by ProjNode ideal and used to collapse Multis. - default Node pcopy(int idx, Node dep) { return null; } + default Node pcopy(int idx) { return null; } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/NeverNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/NeverNode.java index ba1dab8c..66879b66 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/NeverNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/NeverNode.java @@ -11,8 +11,7 @@ import com.seaofnodes.simple.type.TypeTuple; // "Never true" for infinite loop exits public class NeverNode extends IfNode { public NeverNode(NeverNode ctrl) { super(ctrl); } - public NeverNode(float ignore, Node ctrl) { super(ctrl,null); } - @Override public Tag serialTag() { return Tag.Never; } + public NeverNode(Node ctrl) { super(ctrl,null); } @Override public String label() { return "Never"; } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/NewNode.java index fe80f690..bdcdfdfc 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/NewNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/NewNode.java @@ -1,73 +1,79 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; -import com.seaofnodes.simple.Parser; + import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.BAOS; import com.seaofnodes.simple.util.SB; -import java.util.HashMap; -import java.util.IdentityHashMap; - -/** - * Allocation! Allocate a chunk of memory, no init. - * Takes in ctrl and size, and TypeStruct. - * Produces a ptr and private memory. -*/ +import com.seaofnodes.simple.util.Utils; + + +/** Allocate a zeroed object. Inputs {ctrl, $mem, size}; + * results {ptr, $mem}. + * The memory input and result cover only the aliases in the allocated struct. + */ public class NewNode extends Node implements MultiNode { - public TypeStruct _ts; - public NewNode( TypeStruct ts, Node ctrl, Node size ) { super(ctrl,size); _ts = ts; } - public Node ctrl() { return in(0); } - public Node size() { return in(1); } + public TypeMemPtr _ptr; - // --- Serialization - public NewNode(NewNode nnn) { super(nnn); _ts = nnn._ts; } - @Override public Tag serialTag() { return Tag.New; } - @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { - baos.packed2(types.get(_ts)); - } - static Node make( BAOS bais, Type[] types) { - TypeStruct ts = (TypeStruct)types[bais.packed2()]; - return new NewNode(ts,null,null); + public NewNode(TypeMemPtr ptr, Node... nodes) { + super(nodes); + assert !ptr.nullable(); + _ptr = ptr; + assert nodes.length==3; + assert nodes[0]._type==Type.CONTROL || nodes[0]._type==Type.XCONTROL; + assert nodes[1]._type instanceof TypeMem; + assert nodes[2]._type instanceof TypeInteger || nodes[2]._type==Type.NIL; } - @Override public String label() { return "new_"+(_ts.isAry() ? "ary_"+_ts._fields[1]._t.str() : _ts.str()); } + public Node mem() { return in(1); } + public Node size() { return in(2); } - @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - return p.p("new ").p(_ts.str()); + @Override boolean _upgradeType(java.util.HashMap<String,Type> types) { + TypeMemPtr ptr = (TypeMemPtr)_ptr.upgradeType(types); + if( ptr==_ptr ) return false; + unlock(); + _ptr = ptr; + return true; } - @Override public TypeTuple compute() { - if( ctrl()._type.isHigh() ) - return TypeTuple.DEAD_NEW; - TypeStruct ts = _ts.isAry() ? _ts : _ts.makeInit(); - return TypeTuple.make(TypeMemPtr.make(_ts), - // Arrays get pre-zeroed by constructor. - // Objects start with default-zero memory for - // fields that can be zero-initialized. This is - // still private memory, so no aliases escape yet. - TypeMem.make(1,ts,true,false,false,null,null)); + public Field field(int alias) { + for( Field f : _ptr._obj._fields ) + if( !f._one && f._alias==alias ) return f; + return null; } - @Override public Node idealize() { return null; } + public NewNode(NewNode nnn) { super(nnn); _ptr = nnn._ptr; } - @Override boolean _upgradeType( HashMap<String,Type> TYPES) { - Type old = _ts; _ts = (TypeStruct)_ts.upgradeType(TYPES); return old != _ts; - } - // Two NewNodes are never equal; NewNodes always make independent pointers - // so they are not functional - they internally bump-ptr allocate always a - // new pointer. - @Override public boolean eq(Node n) { return this==n; } + @Override public String label() { return "new_"+(_ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._t.str() : _ptr._obj.str()); } + @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + return p.p("new ").p(_ptr._obj.str()); + } - @Override int hash() { return _ts.hashCode(); } + @Override + public TypeTuple compute() { + return in(0)._type.isHigh() + ? TypeTuple.make(_ptr.makeHigh(),TypeMem.TOP) + : TypeTuple.make(_ptr,TypeMem.BOT); + } - @Override public Parser.ParseException err() { - return _ts._fref ? Parser.error("Unknown struct type '"+_ts._name+"'",null) : null; + @Override + public Node idealize() { + Node progress=null; + for( int i=1; i<nIns(); i++ ) { + if( in(i) instanceof CastNode cast ) + progress = setDef(i,cast.in(1)); + } + return progress == null ? null : this; } + @Override + public boolean eq(Node n) { return this == n; } + + @Override + int hash() { return _ptr.hashCode(); } // ------------ // MachNode specifics, shared across all CPUs @@ -76,31 +82,30 @@ public class NewNode extends Node implements MultiNode { private RegMask _retMask; private RegMask _kills; public void cacheRegs(CodeGen code) { - _arg2Reg = code._mach.callArgMask(TypeFunPtr.CALLOC,2,0,code._cCallingConv).firstReg(); - _arg3Mask = code._mach.callArgMask(TypeFunPtr.CALLOC,3,0,code._cCallingConv); + _arg2Reg = code._mach.callArgMask(TypeFunPtr.CALLOC,2,0).firstReg(); + _arg3Mask = code._mach.callArgMask(TypeFunPtr.CALLOC,3,0); // Return mask depends on TFP (either GPR or FPR) _retMask = code._mach.retMask(TypeFunPtr.CALLOC); // Kill mask is all caller-saves, and any mirror stack slots for args // in registers. - RegMaskRW kills = code._cCallerSave.copy(); + RegMaskRW kills = code._callerSave.copy(); // Start of stack slots int maxReg = code._mach.regs().length; // Incoming function arg slots, all low numbered in the RA int fslot = cfg0().fun()._maxArgSlot; // Killed slots for this calls outgoing args - int xslot = code._mach.maxArgSlot(TypeFunPtr.CALLOC,code._cCallingConv); + int xslot = code._mach.maxArgSlot(TypeFunPtr.CALLOC); _xslot = (maxReg+fslot)+xslot; for( int i=0; i<xslot; i++ ) kills.set((maxReg+fslot)+i); _kills = kills; } public String op() { return "alloc"; } - public RegMask regmap(int i) { return i==1 ? _arg3Mask : null; } + public RegMask regmap(int i) { return i==2 ? _arg3Mask : null; } public RegMask outregmap() { return null; } public RegMask outregmap(int idx) { return idx==0 ? _retMask : null; } public RegMask killmap() { return _kills; } public void asm(CodeGen code, SB sb) { - sb.p("#malloc, ").p(code.reg(size())); + sb.p("#calloc, ").p(code.reg(size())); } - } 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 77b43ba8..230593a8 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/Node.java @@ -4,11 +4,13 @@ import com.seaofnodes.print.BaseNode; import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.util.Ary; +import com.seaofnodes.simple.util.Utils; import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.GlobalBits; import com.seaofnodes.simple.print.IRPrinter; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.*; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFloat; +import com.seaofnodes.simple.type.TypeInteger; import java.util.*; import java.util.function.Function; import static com.seaofnodes.simple.codegen.CodeGen.CODE; @@ -68,84 +70,8 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { _hash = 0; } - // Disk/serialized opcode tags - public enum Tag { - Add,And,BulkMemPhi,EQ,NE,LT,LE,ULT, - CallEnd,Call,ConFldOff,Con,CLInit,CProj, - Div,Escape,Extern,ExternOffset,Fun,FunPtr,Guard, - If,Load,Loop,MemMerge,MemPhi,Minus,Mul, - New,Never,Not,Or,Parm,Phi,Proj,PtrToInt, - ReadOnly,Return,Region,RoundF32, - Sar,Shl,Shr,Start,StartCU,StopCU,Stop,Store,Sub, - ToInteger,ToFloat,XCtrl,Xor; - public static final Tag[] VALS = values(); - public Node make( BAOS bais, String[] strs, Type[] types, GlobalBits fileAliases, GlobalBits aliases ) { - return switch(this) { - case Add -> new AddNode(null,null,(byte)bais.packed1()); - case And -> { int mode=bais.packed1(); assert mode==1; yield new AndNode(null,null,null); } - case Div -> new DivNode(null,null,(byte)bais.packed1()); - case Guard -> GuardNode.make(bais); - case If -> new IfNode(null,null); - case Loop -> new LoopNode(null,null,null); - case Minus -> new MinusNode(null,(byte)bais.packed1()); - case Mul -> new MulNode(null,null,(byte)bais.packed1()); - case Never -> new NeverNode(1.2f, null); - case Not -> new NotNode(null); - case Or -> { int mode=bais.packed1(); assert mode==1; yield new OrNode(null,null,null); } - case ReadOnly-> new ReadOnlyNode((Node)null); - case Return ->new ReturnNode(null,null,null,null,null); - case RoundF32-> new RoundF32Node(null); - case PtrToInt-> new PtrToIntNode((Node)null); - case Sar -> { int mode=bais.packed1(); assert mode==1; yield new SarNode(null,null,null); } - case Shl -> { int mode=bais.packed1(); assert mode==1; yield new ShlNode(null,null,null); } - case Shr -> { int mode=bais.packed1(); assert mode==1; yield new ShrNode(null,null,null); } - case Start -> new StartNode(null,null,TypeInteger.BOT); - case Sub -> new SubNode(null,null,(byte)bais.packed1()); - case ToFloat-> new ToFloatNode(null); - case ToInteger-> new ToIntegerNode(null); - case XCtrl -> new XCtrlNode(); - case Xor -> { int mode=bais.packed1(); assert mode==1; yield new XorNode(null,null,null); } - - case EQ -> new BoolNode.EQ (null,null,(byte)bais.packed1()); - case NE -> new BoolNode.NE (null,null,(byte)bais.packed1()); - case LT -> new BoolNode.LT (null,null,(byte)bais.packed1()); - case LE -> new BoolNode.LE (null,null,(byte)bais.packed1()); - case ULT -> new BoolNode.ULT(null,null,(byte)bais.packed1()); - - case Call -> CallNode.make(bais,strs); - case CallEnd-> CallEndNode.make(bais ,types); - case CLInit -> new CLInitNode(bais,strs); - case CProj -> CProjNode.make(bais,strs); - 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); - case MemMerge->MemMergeNode.make(bais); - case MemPhi-> MemPhiNode.make(bais,strs,types,fileAliases,aliases); - case BulkMemPhi-> BulkMemPhiNode.make(bais,strs,types,fileAliases,aliases); - case New -> NewNode.make(bais ,types); - case Parm -> ParmNode.make(bais,strs,types); - case Phi -> PhiNode.make(bais,strs,types); - case Proj -> ProjNode.make(bais,strs); - case Store -> new StoreNode(bais,strs,types,fileAliases,aliases); - case Stop -> StopNode.make(bais); - case StartCU-> StartCUNode.make(bais,strs,types); - case StopCU -> StopCUNode.make(bais); - case Region-> RegionNode.make(bais); - - default -> throw Utils.TODO("Should not reach here"); - }; - } - }; - public Tag serialTag() { throw Utils.TODO(); } - // Serialize extra data, including input counts - public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {} - // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ" - public String label() { return serialTag().toString(); } + public String label() { return getClass().getSimpleName().replace("Node",""); } public String p(int depth) { return IRPrinter.prettyPrint(this,depth); } @@ -252,8 +178,7 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { } // Breaks the edge invariants, used temporarily - @SuppressWarnings("unchecked") - protected <N extends Node> N addUse(Node n) { _outputs.add(n); return (N)this; } + protected <N extends Node> void addUse(N n) { _outputs.add(n); } // Remove node 'use' from 'def's (i.e. our) output list, by compressing the list in-place. // Return true if the output list is empty afterward. @@ -275,12 +200,6 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { old_def.kill(); // Kill old def } } - public Node removeLast() { - Node old_def = _inputs.pop(); - if( old_def != null ) - old_def.delUse(this); - return old_def; - } /** * Kill a Node with no <em>uses</em>, by setting all of its <em>defs</em> @@ -317,13 +236,11 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { // Shortcuts to stop DCE mid-parse // Add bogus null use to keep node alive - public <N extends Node> N keep() { return addUse(null); } + @SuppressWarnings("unchecked") + public <N extends Node> N keep() { addUse(null); return (N)this; } // Remove bogus null. @SuppressWarnings("unchecked") - public <N extends Node> N unkeep() { - delUse(null); - return (N)this; - } + public <N extends Node> N unkeep() { delUse(null); return (N)this; } // Test "keep" status public boolean iskeep() { return _outputs.find(null) != -1; } public void unkill() { @@ -343,6 +260,7 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { n.unlock(); int idx = n._inputs.find(this); n._inputs.set(idx,nnn); + n.moveDepsToWorklist(); // Rewiring can change a dependent query without changing type. nnn.addUse(n); CODE.add(n); CODE.addAll(n._outputs); @@ -413,13 +331,13 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { * Try to peephole at this node and return a better replacement Node. * Always returns some not-null Node (often this). */ - public final Node peephole( ) { - var obs = CODE._midAssert ? null : CODE._obs; + public final Node peephole() { + var obs = CodeGen.CODE._midAssert ? null : CodeGen.CODE._obs; if( obs != null ) obs.before(this); Node n = peepholeOpt(); - Node rez = n==null ? this : deadCodeElim(n._nid >= _nid ? n.peephole() : n); - if( obs != null ) obs.after(this, n==null ? null : rez, false); - return rez; // Cannot return null for no-progress + Node rez = n == null ? this : deadCodeElim(n._nid >= _nid ? n.peephole() : n); + if( obs != null ) obs.after(this, n == null ? null : rez, false); + return rez; } /** @@ -445,9 +363,11 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { // Compute initial or improved Type Type old = setType(compute()); - // Replace constant computations from non-constants with a constant node. - if( !isConst() && !(this instanceof ParmNode parm && parm.inProgress()) && - _type.isHighOrConst() ) + // Replace constant computations from non-constants with a constant + // node. If peeps are disabled, still allow high Phis to collapse; + // they typically come from dead Regions, and we want the Region to + // collapse, which requires the Phis to die first. + if( !isConst() && _type.isHighOrConst() ) return ConstantNode.make(_type).peephole(); // Global Value Numbering @@ -515,8 +435,7 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { // If changing, add users to worklist. public Type setType(Type type) { Type old = _type; - assert old == null || type.isa(old) : "Monotonicity test failed: "+this+"#"+_nid+" old="+old+" new="+type; - //assert !type.hasClosedBuilder0(); // New types should not have closed builders + assert old == null || type.isa(old) : "Monotonicity test failed"; if( old == type ) return old; _type = type; // Set _type late for easier assert debugging CODE.addAll(_outputs); @@ -581,33 +500,28 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { // revisit them if `this` changes. Ary<Node> _deps; public int nDeps() { return _deps == null ? 0 : _deps.size(); } - public Node dep(int idx) { return _deps.at(idx); } - - private boolean addDepImpl( Node dep ) { - // Running peepholes during the big assert cannot have side effects - // like adding dependencies. - if( CODE._midAssert ) return false; - if( CODE._obs != null ) CODE._obs.dep(this, dep); - if( dep._deps==null ) dep._deps = new Ary<>(Node.class); - if( dep._deps .find(this) != -1 ) return false; // Already on list - if( dep._outputs.find(this) != -1 ) return false; - return true; - } + public Node dep(int idx) { return _deps.get(idx); } /** * Add a node to the list of dependencies. Only add it if it's not an input * or output of this node, that is, it is at least one step away. The node * being added must benefit from this node being peepholed. */ - public <N extends Node> N addDep( N dep ) { - if( !addDepImpl(dep) ) return dep; - if( dep._inputs .find(this) != -1 ) return dep; // No need for deps on immediate neighbors - dep._deps.add(this); - return dep; - } + <N extends Node> N addDep( N dep ) { return addDep(dep,false); } + + // Keep an explicit dependency when inspecting an immediate user's shape. + <N extends Node> N addDepForwards(N dep) { return addDep(dep,true); } - public <N extends Node> N addDepForwards( N dep ) { - if( !addDepImpl(dep) ) return dep; + private <N extends Node> N addDep(N dep, boolean forwards) { + // Running peepholes during the big assert cannot have side effects + // like adding dependencies. + if( CODE._midAssert ) return dep; + var obs = CodeGen.CODE._midAssert ? null : CodeGen.CODE._obs; + if( obs != null ) obs.dep(this, dep); + if( dep._deps==null ) dep._deps = new Ary<>(Node.class); + if( dep._deps .find(this) != -1 ) return dep; // Already on list + if( !forwards && dep._inputs .find(this) != -1 ) return dep; // No need for deps on immediate neighbors + if( !forwards && dep._outputs.find(this) != -1 ) return dep; dep._deps.add(this); return dep; } @@ -671,6 +585,26 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { /** Is this Node Memory related */ public boolean isMem() { return false; } + // Semantic change to the graph (so NOT a peephole), used by the Parser. + // If any input is a float, flip to a float-flavored opcode and widen any + // non-float input. + public final Node widen() { + if( !hasFloatInput() ) return this; + Node flt = copyF(); + if( flt==null ) return this; + for( int i=1; i<nIns(); i++ ) + flt.setDef(i, in(i)._type instanceof TypeFloat ? in(i) : new ToFloatNode(in(i)).peephole()); + kill(); + return flt; + } + private boolean hasFloatInput() { + for( int i=1; i<nIns(); i++ ) + if( in(i)._type instanceof TypeFloat ) + return true; + return false; + } + Node copyF() { return null; } + // ------------------------------------------------------------------------ // Peephole utilities @@ -722,7 +656,7 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { public Parser.ParseException err() { return null; } // Common integer constants - public static ConstantNode con(long x) { return (ConstantNode)ConstantNode.make(TypeInteger.constant(x)).peephole(); } + public static ConstantNode con(long x) { return (ConstantNode)(new ConstantNode(TypeInteger.constant(x)).peephole()); } // Utility to walk the entire graph applying a function; return the first // not-null result. @@ -743,20 +677,6 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { return null; } - public void gather(HashMap<String,Integer> strs ) { } - - // If a Type upgrade happens, put on worklist - boolean _upgradeType( HashMap<String,Type> TYPES ) { return false; } - public final void upgradeType( HashMap<String,Type> TYPES ) { - Type old = _type; _type = _type.upgradeType(TYPES); - boolean progress = _upgradeType(TYPES); - if( progress || old != _type ) { - CODE.add(this); - CODE.addAll(_outputs); - if( _deps!=null ) CODE.addAll(_deps); - } - } - /** * Debugging utility to find a Node by index */ @@ -770,4 +690,16 @@ public abstract class Node extends BaseNode<Node> implements Cloneable { for( Node use : _outputs ) if( use != null && (x = use._find(nid,bs)) != null ) return x; return null; } + + boolean _upgradeType(java.util.HashMap<String,Type> types) { return false; } + public final void upgradeType(java.util.HashMap<String,Type> types) { + Type old = _type; + _type = _type.upgradeType(types); + boolean progress = _upgradeType(types); + if( progress || old!=_type ) { + CODE.add(this); + CODE.addAll(_outputs); + moveDepsToWorklist(); + } + } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/NotNode.java index 2328c287..775b069b 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -6,7 +6,8 @@ import com.seaofnodes.simple.util.Utils; public class NotNode extends Node { public NotNode(Node in) { super(null, in); } - @Override public Tag serialTag() { return Tag.Not; } + + @Override public String label() { return "Not"; } @Override protected String format() { return "(!%1)"; } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/OrNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/OrNode.java index 9d50d1a9..618c5340 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/OrNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/OrNode.java @@ -6,7 +6,6 @@ import com.seaofnodes.simple.type.TypeInteger; public class OrNode extends ArithNode { public OrNode( Parser.Lexer loc, Node lhs, Node rhs) { super(loc,lhs, rhs, (byte)1); } - @Override public Tag serialTag() { return Tag.Or; } @Override public String label() { return "Or"; } @Override public String op() { return "|"; } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ParmNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ParmNode.java index 91c471c0..52fe5704 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/ParmNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ParmNode.java @@ -2,55 +2,31 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.codegen.Serialize; -import com.seaofnodes.simple.util.BAOS; - -import java.util.HashMap; -import java.util.IdentityHashMap; +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.type.TypeMemPtr; public class ParmNode extends PhiNode { // Argument indices are mapped one-to-one on CallNode inputs + private Type _declaredType; public final int _idx; // Argument index - public Type _declaredType; public ParmNode(String label, int idx, Type declaredType, Node... inputs) { - super(label, inputs); + super(label,inputs); _idx = idx; _declaredType = declaredType; - _type = declaredType; - } - public ParmNode(ParmNode parm) { super(parm, parm._label ); _idx = parm._idx; _declaredType = parm._declaredType; } - @Override public Tag serialTag() { return Tag.Parm; } - @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { - baos.packed1(Serialize.nIns(this,anodes)); // Object-local linked calls - baos.packed2(_label==null ? 0 : strs.get(_label)); - baos.packed2(types.get(_declaredType)); // NPE if fails lookup - baos.packed1(_idx); - } - static Node make( BAOS bais, String[] strs, Type[] types) { - Node[] ins = new Node[bais.packed1()]; - String label = strs[bais.packed2()]; - Type minType = types[bais.packed2()]; - return new ParmNode(label, bais.packed1(), minType, ins); } + public ParmNode(ParmNode parm) { super(parm, parm._label, parm._declaredType ); _idx = parm._idx; _declaredType = parm._declaredType; } @Override public String label() { return MemOpNode.mlabel(_label); } - @Override boolean _upgradeType( HashMap<String,Type> TYPES) { - Type t = _declaredType.upgradeType(TYPES); - if( t == _declaredType ) return false; - unlock(); - _declaredType = t; - return true; - } - public FunNode fun() { return (FunNode)in(0); } @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { - if( fun()._name!=null && fun()._name.endsWith("<clinit>") && _label.equals("arg") ) + if( "main".equals(fun()._name) && _label.equals("arg") ) return p.p("arg"); p.p("Parm_").p(_label).open(); for( Node in : _inputs ) p.n(in).p(","); @@ -63,42 +39,34 @@ public class ParmNode extends PhiNode { @Override public Type compute() { - if( !(region() instanceof RegionNode r) ) - return region()._type==Type.XCONTROL || region()._type==Type.TOP ? (_type instanceof TypeMem ? TypeMem.TOP : Type.TOP) : _type; - // During parsing Phis have to be computed type pessimistically. - if( r.inProgress() || in(nIns()-1)==null ) - return _declaredType; - // Set type to local top of the starting type - Type t = Type.TOP; - for( int i = 1; i < nIns(); i++ ) { - // If the region's control input is live, add this as a dependency - // to the control because we can be peeped should it become dead. - Type ctrl = addDep(r.in(i))._type; - if( ctrl != Type.XCONTROL && ctrl != Type.TOP ) { - if( in(i)._type==Type.BOTTOM ) - return Type.BOTTOM; - t = t.meet(in(i)._type); - } - } + Type t = inProgress() ? _declaredType : super.compute().join(_declaredType); // Recursion is a loop in disguise. A Parm merges the first argument // with those from recursive calls, just as a loop Phi merges its entry // and backedge. Use the widening rule in PhiNode.compute here too. // CallEndNode.compute handles the separate cycle through return values; // widening this Parm alone cannot stop f(x-1)+1 from growing its result. - if( t instanceof TypeInteger ti && !ti.isHigh() && !ti.isConstant() ) - t = ti.same_but_slightly_wider_than(); - return t.join(_declaredType); + if( t instanceof TypeInteger ti && t != _type && + !ti.isHigh() && !ti.isConstant() && + (!(_type instanceof TypeInteger oldi) || ti._widen <= oldi._widen) ) + return ti.same_but_slightly_wider_than(_declaredType); + return t; } @Override public Node idealize() { if( !(region() instanceof FunNode) ) - // A dead function can lose every caller before the Parm itself is - // removed; there is no replacement input in that transient form. - return nIns()==1 ? null : in(1); // Input has collapse to e.g. starting control. + return in(1); // Input has collapse to e.g. starting control. // If function is folding, do all possible peeps if( fun()._folding ) return super.idealize(); + // Can upgrade minType even while in-progress + if( _declaredType instanceof TypeMemPtr tmp && _declaredType.isFRef() ) { + TypeMemPtr tmp2 = (TypeMemPtr) Parser.TYPES.get(tmp._obj._name); + if( tmp2!=null && tmp2 != _declaredType ) { + _declaredType = tmp2; + return this; + } + } // Skip most phi optimizations on parms return null; } @@ -106,4 +74,12 @@ public class ParmNode extends PhiNode { @Override public boolean eq( Node n ) { return ((ParmNode)n)._idx==_idx && super.eq(n); } + + @Override boolean _upgradeType(java.util.HashMap<String,Type> types) { + Type next = _declaredType.upgradeType(types); + if( next==_declaredType ) return false; + unlock(); + _declaredType=next; + return true; + } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/PhiNode.java index 3cf6f854..064852e4 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -3,31 +3,24 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.Serialize; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.*; -import java.util.HashMap; -import java.util.IdentityHashMap; +import com.seaofnodes.simple.util.Utils; public class PhiNode extends Node { public final String _label; - public PhiNode( String label, Node... inputs) { + int lattice_drop; + + public PhiNode(String label, Node... inputs) { super(inputs); _label = label; } - public static PhiNode make(String label, Type minType, Node... inputs) { - return minType instanceof TypeMem mem - ? mem._alias==1 - ? new BulkMemPhiNode(label,inputs) - : new MemPhiNode(label,mem._alias,inputs) - : new PhiNode(label, inputs); - } // Used by ParmNode - public PhiNode(PhiNode phi, String label) { super(phi); _label = label; } + public PhiNode(PhiNode phi, String label, Type type) { super(phi); _label = label; _type = type; } // Used by instruction Selection - public PhiNode(PhiNode phi) { this(phi,phi._label); } + public PhiNode(PhiNode phi) { this(phi,phi._label,phi._type ); } // Used by the infinite-loop exit breaker public PhiNode(RegionNode r, Node sample) { super(new Node[]{r}); @@ -36,14 +29,9 @@ public class PhiNode extends Node { while( nIns() < r.nIns() ) addDef(sample); } - @Override public Tag serialTag() { return Tag.Phi; } - @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { - baos.packed1(nIns()); - baos.packed2(_label==null ? 0 : strs.get(_label)); - } - static Node make( BAOS bais, String[] strs, Type[] types) { - Node[] ins = new Node[bais.packed1()]; - return new PhiNode(strs[bais.packed2()], ins); + + public static PhiNode make(String label, Type type, Node... inputs) { + return type instanceof TypeMem ? new BulkMemPhiNode(label,inputs) : new PhiNode(label,inputs); } @Override public String label() { return "Phi_"+MemOpNode.mlabel(_label); } @@ -59,17 +47,20 @@ public class PhiNode extends Node { public CFGNode region() { return (CFGNode)in(0); } @Override public boolean isMem() { return _type instanceof TypeMem; } + boolean isRPC() { return false; } @Override public Type compute() { if( !(region() instanceof RegionNode r) ) - return Type.TOP; + return region()._type==Type.XCONTROL || region()._type==Type.TOP ? (_type instanceof TypeMem ? TypeMem.TOP : Type.TOP) : _type; // During parsing Phis have to be computed type pessimistically. if( r.inProgress() || in(nIns()-1)==null ) + // The backedge has not been parsed. Declarations belong on + // Convert/Parm nodes; an unfinished value Phi knows no bound. return Type.BOTTOM; // Set type to local top of the starting type Type t = Type.TOP; - for( int i = 1; i < nIns(); i++ ) { + for (int i = 1; i < nIns(); i++) { // If the region's control input is live, add this as a dependency // to the control because we can be peeped should it become dead. Type ctrl = addDep(r.in(i))._type; @@ -79,6 +70,7 @@ public class PhiNode extends Node { t = t.meet(in(i)._type); } } + Type newt = t; // Widen loop Phis so integer ranges cannot grow one step per trip forever. // Recursion is a loop in disguise: ParmNode.compute widens arguments, @@ -86,75 +78,98 @@ public class PhiNode extends Node { // recursive result can feed itself without passing through a Parm. // Above-center ranges may still fall to unwidened constants. // Widen only below-center ranges to preserve monotonicity. - if( r instanceof LoopNode && // Only around loops - t instanceof TypeInteger ti && // Only widen integers - !ti.isHigh() && !ti.isConstant() ) { // Only widen live ranges - // Widen, to prevent infinite falling of TypeIntegers - return ti.same_but_slightly_wider_than(); + if( region() instanceof LoopNode && // Only around loops + newt instanceof TypeInteger newi && + // Types changed and are falling (the optimistic case, expected to fall forever) + newi != _type ) { + if( !newi.isHigh() && !newi.isConstant() && (!(_type instanceof TypeInteger oldi) || newi._widen <= oldi._widen) ) + return newi.same_but_slightly_wider_than(TypeInteger.BOT); } - return t; + return newt; } @Override public Node idealize() { - RegionNode r = (RegionNode)region(); - if( r.inProgress() || nOuts()==0 ) + if( !(region() instanceof RegionNode r ) ) + return in(1); // Input has collapse to e.g. starting control. + if( r.inProgress() || inProgress() || r.nIns()<=1 ) return null; // Input is in-progress // If we have only a single unique input, become it. Node live = singleUniqueInput(); - if( live != null ) - return live; + if( live != null ) return live; // No bother if region is going to fold dead paths soon for( int i=1; i<nIns(); i++ ) if( r.in(i)._type == Type.XCONTROL ) return null; - // Generic "pull down op" - Node progress; - if( same_op() && (progress = drop_same_op()) != null ) - return progress; + // Phi(op(a,b,...),op(c,d,...)) -> op(Phi(a,c),Phi(b,d),...). + Node down; + if( same_op() && (down=drop_same_op())!=null ) return down; - // If merging Phi(ZERO, guardNZ(N)) at the matching `if(N)` join, the - // Phi is exactly N. The guard arm proves N is non-zero, and the other - // arm contributes N's zero value. This is only legal if N is already - // available at the Phi region; otherwise a return-scope/live-on-exit - // Phi can lose the zero arm and export a branch-local value upward. - Node unguard; - if( (unguard=matchingGuardMerge(r,1)) != null ) return unguard; - if( (unguard=matchingGuardMerge(r,2)) != null ) return unguard; + if( nIns()==3 ) { + Node merge = matchingGuardMerge(r,1); + if( merge!=null ) return merge; + merge = matchingGuardMerge(r,2); + if( merge!=null ) return merge; + } + + // If merging Phi(N, cast(N)) - we are losing the cast JOIN effects, so just remove. + if( nIns()==3 ) { + if( in(1) instanceof CastNode cast && addDep(cast.in(1))==in(2) ) return in(2); + if( in(2) instanceof CastNode cast && addDep(cast.in(1))==in(1) ) return in(1); + } + // If merging a null-checked null and the checked value, just use the value. + // if( val ) ..; phi(Region,False=0/null,True=val); + // then replace with plain val. + if( nIns()==3 ) { + int nullx = -1; + if( in(1)._type == in(1)._type.makeZero() ) nullx = 1; + if( in(2)._type == in(2)._type.makeZero() ) nullx = 2; + if( nullx != -1 ) { + Node val = in(3-nullx); + if( val instanceof CastNode cast ) + val = cast.in(1); + Node ridom = r.idom(this); + if( ridom instanceof IfNode iff && addDep(iff.pred())==val ) { + // Must walk the idom on the null side to make sure we hit False. + CFGNode idom = (CFGNode)r.in(nullx); + while( idom != null && idom.nIns() > 0 && idom.in(0) != iff ) idom = idom.idom(); + if( idom instanceof CProjNode proj && proj._idx==1 ) + return val; + } else if( ridom != null ) addDep(ridom); + } + } return null; } private Node matchingGuardMerge(RegionNode r, int nzIdx) { Node zero = in(3-nzIdx); - if( !(in(nzIdx) instanceof GuardNode cast && cast._nonZero) || - zero._type.makeZero()!=zero._type || - cast.in(1)==this ) + if( !(in(nzIdx) instanceof GuardNode guard && guard._nonZero) || + zero==null || zero._type.makeZero()!=zero._type || guard.in(1)==this ) return null; if( !(r.in(nzIdx) instanceof CProjNode nz && nz._idx==0 && nz.ctrl() instanceof IfNode iff) ) return null; if( !(r.in(3-nzIdx) instanceof CProjNode z && z._idx==1 && z.ctrl()==iff) ) return null; - // Predicate was guarded? - Node n = cast.in(1), tested = null; + Node n=guard.in(1), tested=null; while( true ) { - if( iff.pred()==n ) tested = n; - if( !(n instanceof GuardNode guard && guard._nonZero) ) break; - n = guard.in(1); + if( iff.pred()==n ) tested=n; + if( !(n instanceof GuardNode g && g._nonZero) ) break; + n=g.in(1); } - CFGNode early = CFGNode.earlyCFG(n); - return tested!=null && (early==null || early.dominates(r,this)) ? n : null; + // The source must exist before the join, not just on its guarded arm. + return tested!=null && ScopeNode.availableAt(n,r,new java.util.BitSet()) ? n : null; } // Same op on all Phi paths; all ops have only the Phi as a use. // None have a control input. private boolean same_op() { Node op = in(1); - if( op instanceof EscapeNode || op instanceof CFGNode || op instanceof ConstantNode || op instanceof PhiNode || + if( op instanceof CFGNode || op instanceof ConstantNode || op instanceof PhiNode || op instanceof ProjNode || op instanceof NewNode || op instanceof ScopeNode || op instanceof MemMergeNode ) return false; // A bulk Phi must split its aliases before factoring precise Stores. if( this instanceof BulkMemPhiNode ) return false; @@ -206,7 +221,7 @@ public class PhiNode extends Node { ins[i] = in(i).in(j); t = t.meet(ins[i]._type); } - // Do not introduce an operand Phi mixing incompatible type families. + // Not accepts both pointers and integers, but a Phi cannot mix them. if( t==Type.BOTTOM ) { for( int i=1; i<ins.length; i++ ) addDep(ins[i]); cp.kill(); @@ -214,7 +229,7 @@ public class PhiNode extends Node { } PhiNode phi = j==1 && op instanceof MemOpNode mem ? new MemPhiNode(_label,mem._alias,ins) - : PhiNode.make(_label,t,ins); + : PhiNode.make(_label,t.glb(false),ins); x = phi.peephole(); } cp.addDef(x); @@ -261,27 +276,31 @@ public class PhiNode extends Node { } // Never equal if inProgress. - @Override public boolean eq( Node n ) { - return !inProgress() && super.eq(n); - } + // Also, joins + @Override public boolean eq(Node n) { return !inProgress(); } @Override public Parser.ParseException err() { + if( inProgress() ) return null; + // A scalar union is useful for truth tests, e.g. !(ptr || integer). + // A typed consumer reports an invalid use of that union itself. if( _type != Type.BOTTOM ) return null; - // Global BOTTOM retains the same role for non-scalar families. + // BOTTOM means we mixed e.g. int and ptr for( int i=1; i<nIns(); i++ ) // Already an error, but better error messages come from elsewhere if( in(i)._type == Type.BOTTOM ) return null; - SB sb = new SB().p("No common type amongst "); - for( int i=1; i<nIns(); i++ ) - sb.p(in(i)._type.toString()).p(" and "); - return Parser.error(sb.unchar(5).toString(),null); - } - - @Override public void gather( HashMap<String,Integer> strs ) { - Serialize.gather(strs,_label); + // Gather a minimal set of types that "cover" all the rest + boolean ti=false, tf=false, tp=false, tn=false; + for( int i=1; i<nIns(); i++ ) { + Type t = in(i)._type; + ti |= t instanceof TypeInteger x; + tf |= t instanceof TypeFloat x; + tp |= t instanceof TypeMemPtr x; + tn |= t==Type.NIL; + } + return ReturnNode.mixerr(ti,tf,tp,tn, ((RegionNode)region())._loc); } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/Proj.java b/chapter25/src/main/java/com/seaofnodes/simple/node/Proj.java deleted file mode 100644 index c8b9378f..00000000 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/Proj.java +++ /dev/null @@ -1,2 +0,0 @@ -package com.seaofnodes.simple.node; -public interface Proj { int idx(); } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ProjNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ProjNode.java index fcc113a8..92c6c2be 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/ProjNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ProjNode.java @@ -2,15 +2,12 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; -import com.seaofnodes.simple.codegen.Serialize; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeMem; import com.seaofnodes.simple.type.TypeTuple; -import com.seaofnodes.simple.util.BAOS; -import java.util.HashMap; -import java.util.IdentityHashMap; -public class ProjNode extends Node implements Proj { + +public class ProjNode extends Node { // Which slice of the incoming multipart value public final int _idx; @@ -24,14 +21,6 @@ public class ProjNode extends Node implements Proj { _label = label; } public ProjNode(ProjNode p) { super(p); _idx = p._idx; _label = p._label; } - @Override public Tag serialTag() { return Tag.Proj; } - @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { - baos.packed1(_idx); - baos.packed2(_label==null ? 0 : strs.get(_label)); - } - static Node make( BAOS bais, String[] strs ) { - return new ProjNode(null, bais.packed1(), strs[bais.packed2()] ); - } @Override public String label() { return _label; } @@ -55,17 +44,11 @@ public class ProjNode extends Node implements Proj { return t instanceof TypeTuple tt ? tt._types[_idx] : t.oob(); } - @Override public Node idealize() { return ((MultiNode)in(0)).pcopy(_idx,this); } + @Override public Node idealize() { return ((MultiNode)in(0)).pcopy(_idx); } @Override public boolean eq( Node n ) { return _idx == ((ProjNode)n)._idx; } @Override int hash() { return _idx; } - - @Override public int idx() { return _idx; } - - @Override public void gather( HashMap<String,Integer> strs ) { - Serialize.gather(strs,_label); - } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java index d3d38060..55c0fc02 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java @@ -18,7 +18,6 @@ public class PtrToIntNode extends Node { public PtrToIntNode(Node ptr) { super(null,ptr); } public PtrToIntNode(PtrToIntNode ptr) { super(ptr); } - @Override public Tag serialTag() { return Tag.PtrToInt; } @Override protected String format() { return "(i64*)%1"; } @Override public Type compute() { diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java index 0a720d5b..94c1b6f6 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java @@ -10,7 +10,7 @@ import com.seaofnodes.simple.type.TypeMemPtr; public class ReadOnlyNode extends Node { public ReadOnlyNode( Node n ) { super(null,n); } public ReadOnlyNode( ReadOnlyNode n ) { super(n); } - @Override public Tag serialTag() { return Tag.ReadOnly; } + @Override public String label() { return "ReadOnly"; } @Override protected String format() { return "(const)%1"; } @Override public Type compute() { diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/RegionNode.java index 8883ba8e..59030dee 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/RegionNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/RegionNode.java @@ -2,10 +2,10 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; +import com.seaofnodes.simple.*; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.util.BAOS; import java.util.*; public class RegionNode extends CFGNode { @@ -15,9 +15,9 @@ public class RegionNode extends CFGNode { public RegionNode(Parser.Lexer loc, Node... nodes) { super(nodes); _loc = loc; } public RegionNode(RegionNode r, Parser.Lexer loc) { super(r); _loc = loc; } public RegionNode(RegionNode r) { super(r); if( r!=null ) _loc = r._loc; } - @Override public Tag serialTag() { return Tag.Region; } - public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { baos.packed1(nIns()); } - static Node make( BAOS bais ) { return new RegionNode(null,new Node[bais.packed1()]); } + + @Override + public String label() { return "Region"; } @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p(label()); @@ -47,10 +47,10 @@ public class RegionNode extends CFGNode { // If a CFG diamond with no merging, delete: "if( pred ) {} else {};" if( !hasPhi() && nIns()>3 && // No Phi users, just a control user - in(1) instanceof CProjNode p1 && - in(2) instanceof CProjNode p2 && - addDepForwards(p1).in(0)==addDepForwards(p2).in(0) && - p1.in(0) instanceof IfNode iff ) { + in(1) instanceof CProjNode p1 && + in(2) instanceof CProjNode p2 && + addDepForwards(p1).in(0)==addDepForwards(p2).in(0) && + p1.in(0) instanceof IfNode iff ) { // Replace with the iff.ctrl directly if( nIns()==3 ) return iff.ctrl(); // Just delete the path for fat Regions @@ -76,7 +76,7 @@ public class RegionNode extends CFGNode { // Fold Region for( int j=1; j<region.nIns(); j++ ) addDef(region.in(j)); - setDef(i,CodeGen.CODE.XCTRL); + setDef(i,Parser.XCTRL); return this; } } @@ -112,8 +112,7 @@ public class RegionNode extends CFGNode { CodeGen.CODE.addAll(phi.delDef(path)._outputs); } } - if( !isDead() ) delDef(path); - return isDead() ? CodeGen.CODE.XCTRL : this; + return isDead() ? Parser.XCTRL : delDef(path); } private int findDeadInput(int startIdx) { diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ReturnNode.java index 0431f7d5..8fea803d 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -10,15 +10,7 @@ import com.seaofnodes.simple.type.*; import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; -/** - * The Return node has two inputs. The first input is a control node and the - * second is the data node that supplies the return value. - * <p> - * In this presentation, Return functions as a Stop node, since multiple <code>return</code> statements are not possible. - * The Stop node will be introduced in Chapter 6 when we implement <code>if</code> statements. - * <p> - * The Return's output is the value from the data node. - */ +/** Keeps all preceding memory effects and the return value alive: {ctrl, $mem, value, rpc}. */ public class ReturnNode extends CFGNode { public FunNode _fun; @@ -28,7 +20,6 @@ public class ReturnNode extends CFGNode { _fun = fun; } public ReturnNode( ReturnNode ret, FunNode fun ) { super(ret); _fun = fun; } - @Override public Tag serialTag() { return Tag.Return; } public Node ctrl() { return in(0); } public Node mem () { return in(1); } @@ -37,6 +28,9 @@ public class ReturnNode extends CFGNode { @Override public FunNode fun() { return _fun; } @Override + public String label() { return "Return"; } + + @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { p.p("return "); if( expr()==null ) p.p("----"); @@ -49,10 +43,13 @@ public class ReturnNode extends CFGNode { @Override public Type compute() { - return TypeTuple.make(ctrl()._type,mem()._type,expr()._type); + if( inProgress () ) return TypeTuple.RET; // In progress + // Inlining can delete the entry while callers still use this return. + return TypeTuple.make(ctrl()._type,TypeMem.BOT,expr()._type); } @Override public Node idealize() { + if( inProgress () ) return null; if( _fun.isDead() ) return null; // Upgrade signature based on return type @@ -61,47 +58,104 @@ public class ReturnNode extends CFGNode { if( ret != fcn.ret() && ret.isa(fcn.ret()) ) _fun.setSig(fcn.makeFrom(ret)); + // If dead (cannot be reached; infinite loop), kill the exit values + if( ctrl()._type==Type.XCONTROL && + !(mem() instanceof ConstantNode && expr() instanceof ConstantNode) ) { + Node top = new ConstantNode(Type.TOP).peephole(); + setDef(1,top); + setDef(2,top); + return this; + } + return null; } + public boolean inProgress() { + return ctrl().getClass() == RegionNode.class && ((RegionNode)ctrl()).inProgress(); + } + + // Remember parsed type kinds for diagnostics; the optimized expression decides validity. + private boolean ti=false, tf=false, tp=false, tn=false; + + // Add a return exit to the current parsing function + void addReturn( Node ctrl, Node rmem, Node expr ) { + assert inProgress(); + + // Gather parse-time return types for error reporting + Type t = expr._type; + ti |= t instanceof TypeInteger x; + tf |= t instanceof TypeFloat x; + tp |= t instanceof TypeMemPtr x; + tn |= t==Type.NIL; + + // Merge path into the One True Return + RegionNode r = (RegionNode)ctrl(); + // Assert that the Phis are in particular outputs; not reordered or shuffled + PhiNode mem = (PhiNode)r.out(0); assert mem.isMem(); + PhiNode rez = (PhiNode)r.out(1); assert !rez.isMem(); + // Pop "inProgress" null off + r ._inputs.pop(); + mem._inputs.pop(); + rez._inputs.pop(); + // Add new return point + r .addDef(ctrl); + mem.addDef(rmem); + rez.addDef(expr); + // Back to being inProgress + r .addDef(null); + mem.addDef(null); + rez.addDef(null); + } + @Override public Parser.ParseException err() { - if( ctrl()._type == Type.CONTROL && - expr()._type == Type.TOP ) - return Parser.error("No defined return type",null); - if( ctrl()._type == Type.CONTROL && - expr()._type == TypeScalar.BOT ) - return Parser.error("Cannot return generic scalar",null); - // With no user constructor, <init> doubles as the public no-arg - // constructor. Required fields remain TOP in its private memory: a - // typed parser poison which deliberately emitted no Store or code. - if( ctrl()._type == Type.CONTROL && _fun.isInstance() ) { - TypeStruct self = ((TypeMemPtr)_fun.sig().arg(0))._obj; - if( !CodeGen.hasUserConstructor(self) && expr()._type instanceof TypeMem mem ) - for( Field fld : self._fields ) { - if( !(fld._t instanceof TypeNil tn && tn.notNull()) ) continue; - Type actual = mem._alias==1 && mem._t instanceof TypeStruct ts - ? ts.field(fld._fname)._t - : mem._alias==fld._alias ? mem._t : Type.TOP; - if( actual==Type.TOP || actual instanceof TypeNil atn && atn.nullable() ) - return Parser.error("'"+self._name+"' is not fully initialized, field '"+ - fld._fname+"' is only partially set in the constructor",null); - } + if( ctrl()._type != Type.CONTROL ) return null; // Exit path is dead + if( expr()._type==TypeScalar.BOT ) { + int kinds = returnKinds(expr(),new java.util.BitSet()); + return mixerr((kinds&1)!=0,(kinds&2)!=0,(kinds&4)!=0,(kinds&8)!=0,_fun._loc); } - return null; + return expr()._type==Type.BOTTOM || expr()._type==Type.TOP || expr()._type==TypeScalar.BOT ? mixerr(ti,tf,tp,tn,_fun._loc) : null; + } + + private static int returnKinds(Node n, java.util.BitSet visit) { + if( n==null || visit.get(n._nid) ) return 0; + visit.set(n._nid); + Type t=n._type; + if( t instanceof TypeInteger ) return 1; + if( t instanceof TypeFloat ) return 2; + if( t instanceof TypeMemPtr || t instanceof TypeFunPtr ) return 4; + if( t==Type.NIL ) return 8; + int kinds=0; + if( n instanceof PhiNode phi ) + for( int i=1; i<n.nIns(); i++ ) + if( phi.region().in(i)._type==Type.CONTROL ) kinds |= returnKinds(n.in(i),visit); + return kinds; } - @Override public boolean eq( Node n ) { - return !_fun.isDead() && !((ReturnNode)n)._fun.isDead(); + static Parser.ParseException mixerr( boolean ti, boolean tf, boolean tp, boolean tn, Parser.Lexer loc ) { + if( !ti && !tf && !tp && !tn ) + // Hit when the function never returns. e.g. `f = { -> return f() };` + return Parser.error("No defined return type", loc); + // Rather ugly way to print conflicting return types + int cnt = (ti?1:0) + (tf?1:0) + (tp||tn?1:0); + if( cnt==1 ) return null; // Uniform return type, something else errored and will report + SB sb = new SB().p("No common type amongst "); + if( ti ) sb.p("int and "); + if( tf ) sb.p("f64 and "); + if( tp || tn ) sb.p("reference and "); + return Parser.error(sb.unchar(5).toString(),loc); } + // ------------ // MachNode specifics, shared across all CPUs public String op() { - return _fun._frameAdjust > 0 ? "epilog" : "ret "; + return _fun._frameAdjust > 0 ? "addi" : "ret"; } // Correct Nodes outside the normal edges public void postSelect(CodeGen code) { - _fun.setRet(this); + FunNode fun = (FunNode)rpc().in(0); + _fun = fun; + fun.setRet(this); } public RegMask regmap(int i) { return i==2 @@ -113,14 +167,13 @@ public class ReturnNode extends CFGNode { public void asm(CodeGen code, SB sb) { int frameAdjust = fun()._frameAdjust; if( frameAdjust>0 ) - sb.p("\n").p("rsp += #").p(frameAdjust).p("\nret "); + sb.p("rsp += #").p(frameAdjust).p("\nret"); // Post code-gen, just print the "ret" - if( code._phase.ordinal() < CodeGen.Phase.RegAlloc.ordinal() || - (code._phase.ordinal() == CodeGen.Phase.RegAlloc.ordinal() && !code._regAlloc.done()) ) + if( code._phase.ordinal() <= CodeGen.Phase.RegAlloc.ordinal() ) // Prints return reg (either RAX or XMM0), RPC and then the // callee-save registers. for( int i=2; i<nIns(); i++ ) - sb.p(code.reg(in(i))).p(" "); + sb.p(code.reg(in(i),fun())).p(" "); // If we did not get the expected rpc, print which one we got else if( code._regAlloc.regnum(rpc()) != code._mach.rpc() ) sb.p("[").p(code.reg(rpc())).p("]"); diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java b/chapter25/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java index 65f10a79..10a0e4aa 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java @@ -7,7 +7,8 @@ import com.seaofnodes.simple.type.TypeFloat; public class RoundF32Node extends Node { public RoundF32Node(Node lhs) { super(null, lhs); } - @Override public Tag serialTag() { return Tag.RoundF32; } + + @Override public String label() { return "RoundF32"; } @Override protected String format() { return "((f32)%1)"; } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/SarNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/SarNode.java index 737b531f..54611b0d 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/SarNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/SarNode.java @@ -6,7 +6,6 @@ import com.seaofnodes.simple.type.TypeInteger; public class SarNode extends ArithNode { public SarNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs, (byte)1); } - @Override public Tag serialTag() { return Tag.Sar; } @Override public String op() { return ">>"; } @Override long doOp( long x, long y ) { return x >> y; } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ScopeNode.java index 21d60d53..3c1e9776 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -1,21 +1,22 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; + import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.Var; import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.CompUnit; -import com.seaofnodes.simple.codegen.ParseAll; import com.seaofnodes.simple.type.*; import com.seaofnodes.simple.util.Ary; +import com.seaofnodes.simple.util.AryInt; import com.seaofnodes.simple.util.Utils; + import java.util.*; /** * The Scope node is purely a parser helper - it tracks names to nodes with a - * stack of variables. + * stack of variable records. */ -public class ScopeNode extends MemMergeNode { +public class ScopeNode extends Node { /** * The control is a name that binds to the currently active control @@ -25,38 +26,40 @@ public class ScopeNode extends MemMergeNode { public static final String ARG0 = "arg"; public static final String MEM0 = "$mem"; - // All active/live variables in all nested scopes, all run together. - // Maps 1-to-1 to the _inputs array. + public Node in(Var v) { return in(v._idx); } + + @Override public Type compute() { return Type.BOTTOM; } + @Override public Node idealize() { return null; } + + // All active/live variables in all nested scopes, all run together public final Ary<Var> _vars; // Lexical scope is typed: public static class Kind { // Basic block scoping - public static class Block extends Kind { } - // Constructor/initializer block for an allocated struct. - public static class Constructor extends Block { - public Constructor(TypeStruct self) { _self = self; } - public final TypeStruct _self; - } + public static class Block extends Kind { } // Function scope - public static class Func extends Kind { public Func(String name) { _name=name;} public final String _name; } + public static class Func extends Kind { } + // Struct definition, mapping types to fields + public static class Define extends Kind { public Define(TypeMemPtr tmp) { _tmp=tmp; } public final TypeMemPtr _tmp; } + // Struct allocation, mapping values to fields + public static class Alloc extends Kind { public Alloc (TypeMemPtr tmp) { _tmp=tmp; } public final TypeMemPtr _tmp; } public int _lexSize; // Number of Vars in this scope } public final Ary<Kind> _kinds; // Lexical scope nesting depth public int depth() { return _kinds._len; } - public Kind klast() { return _kinds.last(); } - // Extra guards; tested predicates and cast results + // Extra guards; tested predicates and casted results private final Ary<Node> _guards; // A new ScopeNode public ScopeNode() { - super(); - _vars = new Ary<>(Var .class); - _kinds = new Ary<>(Kind.class); - _guards = new Ary<>(Node.class); + _type = Type.BOTTOM; + _vars = new Ary<>(Var .class); + _kinds = new Ary<>(Kind .class); + _guards = new Ary<>(Node .class); } @Override public String label() { return "Scope"; } @@ -67,7 +70,7 @@ public class ScopeNode extends MemMergeNode { int j=1; for( int i=0; i<nIns(); i++ ) { if( j < depth() && i == _kinds.at(j)._lexSize ) { p.p("| "); j++; } - Var v = var(i); + Var v = _vars.at(i); p.p(v._type()); p.p(" "); if( v._final ) p.p("!"); @@ -86,7 +89,7 @@ public class ScopeNode extends MemMergeNode { public Node ctrl() { return in(0); } - public Node mem() { return in(1); } + public Node mem() { return in(lookup(MEM0)); } public Var var(int i) { return _vars.at(i); } /** @@ -100,7 +103,7 @@ public class ScopeNode extends MemMergeNode { * @return Node that was bound */ public <N extends Node> N ctrl(N n) { return setDef(0,n); } - public Node mem(Node n) { return setDef(1,n); } + public Node mem(Node n) { update(MEM0,n); return n; } public void push( Kind kind ) { kind._lexSize = _vars.size(); @@ -109,11 +112,7 @@ public class ScopeNode extends MemMergeNode { // Pop a lexical scope public void pop() { - promote(null); // Promote forward references to the next outer scope - _pop(); - } - // Pop a scope, no promote FRefs - public void _pop() { + promote(); // Promote forward references to the next outer scope int n = _kinds.pop()._lexSize; popUntil(n); // Pop off inputs going out of scope _vars.setLen(n); // Pop off variables going out of scope @@ -122,86 +121,69 @@ public class ScopeNode extends MemMergeNode { // Look for forward references in the last lexical scope and promote to the // next outer lexical scope. At the last scope declare them an error. - public void promote(CodeGen code) { - Kind kind = klast(); + public void promote() { + Kind kind = _kinds.last(); int n = kind._lexSize; for( int i=n; i<nIns(); i++ ) { Var v = var(i); - Node vn = in(i); if( !v.isFRef() ) continue; - // Shuffle vars and inputs to move the forward reference to - // *before* the start of the using scope ("as if" it was not - // actually a forward reference). - for( int j=n; j<=i; j++ ) { - Var vtmp = var(j); - Node ntmp = in (j); - v._idx = j; - _vars .set(j,v ); - _inputs.set(j,vn); - v = vtmp; - vn = ntmp; - } + if( depth()==1 ) + throw Parser.error("Undefined name '" + v._name + "'",v._loc); + _vars .swap(n,i); + _inputs.swap(n,i); + v._idx = n; kind._lexSize = ++n; } } - public boolean inFunction () { return klast() instanceof Kind.Func; } - // Is any enclosing scope a constructor? - public boolean inConstructor() { return constructorSelf() != null; } - // Nearest constructor self type, if any. - public TypeStruct constructorSelf() { - for( int i=depth()-1; i>=0; i-- ) - if( _kinds.at(i) instanceof Kind.Constructor ctor ) - return ctor._self; - else if( _kinds.at(i) instanceof Kind.Func func && FunNode.isInstance(func._name) ) - return ((TypeMemPtr)var(_kinds.at(i)._lexSize).type())._obj; - return null; + // Allocation fields live in the scope immediately outside the constructor + // block. Nested blocks retain that permission; surrounding bindings do not. + public boolean canInit(Var v) { + for( int i=_kinds._len-1; i>0; i-- ) + if( _kinds.at(i) instanceof Kind.Alloc ) + return v._idx >= _kinds.at(i-1)._lexSize && v._idx < _kinds.at(i)._lexSize; + return false; } - // Find nearest enclosing function scope - public int enclosingFunction() { - for( int i=depth()-1; i>=0; i-- ) - if( _kinds.at(i) instanceof Kind.Func func && !FunNode.isInstance(func._name) ) - return i; - throw Utils.TODO("Should not reach here"); - } + public boolean inConstructor() { return _kinds.last() instanceof Kind.Define; } + public boolean inAllocation () { return _kinds.last() instanceof Kind.Alloc ; } + public boolean inFunction () { return _kinds.last() instanceof Kind.Func ; } - // Find nearest enclosing function or declaration scope - public int enclosingFuncOrDecl() { - for( int i=depth()-1; i>=0; i-- ) - if( _kinds.at(i) instanceof Kind.Func func ) - return i; - throw Utils.TODO("Should not reach here"); - } - - // Which lexical scope contains this Var? - public int kindx( Var v ) { + public Kind kind( Var v ) { for( int i=depth()-1; i>=0; i-- ) if( v._idx >= _kinds.at(i)._lexSize ) - return i; - return -1; + return _kinds.at(i); + throw Utils.TODO(); } - // Which lexical scope contains this Var? - public Kind kind( Var v ) { - int kx = kindx(v); - return kx == -1 ? null : _kinds.at(kx); - } + // Return the kind for the defining scope, or a function scope if found + // first. Block scopes means the variable can be r/w directly in the + // scope, Function scopes require final constants (no capture), and + // Constructor scopes mean an instance variable reference. - public int kindFcnx( Var v ) { - for( int i=depth()-1; i>=0; i-- ) - if( v._idx >= _kinds.at(i)._lexSize && _kinds.at(i) instanceof Kind.Func ) - return i; - throw Utils.TODO("Should not reach here"); - } + // Walk up-lexical scope looking for defining lexical Kind. + // - Skip any amount of nested block scopes + // - Walking out of a function requires v be a final constant (for now) - + // and we can stop walking. + + // - Walking out of a method and (skipping Blocks) into the matching struct + // is OK, and this is a instance var load. + + // - Walking out of a method and into the wrong struct is an error; + // including nested structs containing (no nested classes yet) - // Which function scope contains this Var? - public Kind kindFcn( Var v ) { - int kx = kindFcnx(v); - return kx == -1 ? null : _kinds.at(kx); + // Returns error, forward-ref, ok containing struct, ok containing function/block. + + public String outOfFunction( Var v ) { + //if( v==null ) return null; // Prolly forward reference + //int i; for( i=_lexSize._len-1; i>=0 && v._idx<_lexSize.at(i); i-- ) + // if( _kinds.at(i)=="{->}" ) return "{->}"; + //return _kinds.at(i); + throw Utils.TODO(); } + // Find name in reverse, return an index into _vars or -1. Linear scan // instead of hashtable, but probably doesn't matter until the scan // typically hits many dozens of variables. @@ -216,9 +198,9 @@ public class ScopeNode extends MemMergeNode { * Create a new variable name in the current scope */ public boolean define( String name, Type declaredType, boolean xfinal, Node init, Parser.Lexer loc ) { - assert _kinds.isEmpty() || name!=MEM0 ; // Later scopes do not define memory + assert _kinds.isEmpty() || name.charAt(0)!='$' ; // Later scopes do not define memory if( depth() > 0 ) - for( int i=_vars.size()-1; i>=klast()._lexSize; i-- ) { + for( int i=_vars.size()-1; i>=_kinds.last()._lexSize; i-- ) { Var n = var(i); if( n._name.equals(name) ) { if( !n.isFRef() ) return false; // Double define @@ -226,45 +208,17 @@ public class ScopeNode extends MemMergeNode { if( !xfinal || !declaredType.isConstant() ) throw fref.err(); // Must be a final constant n.defFRef(declaredType,xfinal,loc); // Declare full correct type, final, source location setDef(n._idx,fref.addDef(init)); // Set FRef to defined; tell parser also - return true; } } - Var v = new Var(nIns(),name,declaredType,xfinal,loc,init==CodeGen.CODE.XCTRL); + Var v = new Var(nIns(),name,declaredType,xfinal,loc,init==Parser.XCTRL); _vars.add(v); // Creating a forward reference - if( init==CodeGen.CODE.XCTRL ) - //init = new FRefNode(v).init(); - throw Utils.TODO(); + if( init==Parser.XCTRL ) + init = new FRefNode(v).init(); addDef(init); return true; } - // Normal lookup was tried and failed. Append a forward ref in the current - // lexical scope. Scope pops promote it outwards "as if" the final constant - // had been declared before the using scope. - - public Var defineFRef( String id, Type t, boolean xfinal, Parser.Lexer loc ) { - Var var = new Var(nIns(),id,t,xfinal,loc,true); - FRefNode fref = new FRefNode(id,loc).init(); - fref._type = fref._con = t; - _vars.add(var); - addDef(fref); - return var; - } - - - // Parser state carries one complete, conservative memory value. Precise - // alias partitions belong to the graph and are introduced by peepholes. - public Node mem( int alias ) { - assert alias == 1; - return in(update(var(1),null)); - } - public void mem( int alias, Node st ) { - assert alias == 1; - update(var(1),st); - } - - /** * Lookup a name in all scopes starting from most deeply nested. * @@ -300,7 +254,7 @@ public class ScopeNode extends MemMergeNode { // Set real Phi in the loop head // The phi takes its one input (no backedge yet) from a recursive // lookup, which might have insert a Phi in every loop nest. - : loop.setDef(v._idx,PhiNode.make(v._name, v.type(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).init()); + : loop.setDef(v._idx,PhiNode.make(v._name, v.type(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).peephole()); setDef(v._idx,old); } //assert !v._final || st==null; @@ -338,15 +292,9 @@ public class ScopeNode extends MemMergeNode { n.keep(); dup.addDef(ctrl()); // Control input is just copied - // Memory is one ordinary parser-state value. At loop heads the - // Scope sentinel triggers the same lazy-Phi machinery as variables. - dup.addDef(loop ? this : mem()); - - // Copy of other inputs - for( int i=2; i<nIns(); i++ ) - // For lazy phis on loops we use a sentinel - // that will trigger phi creation on update - dup.addDef(loop && !var(i)._final ? this : in(i)); + // Memory is one binding, with the same lazy loop Phi as scalar names. + for( int i=1; i<nIns(); i++ ) + dup.addDef(loop && !_vars.at(i)._final ? this : in(i)); return dup; } @@ -360,17 +308,14 @@ public class ScopeNode extends MemMergeNode { */ public RegionNode mergeScopes(ScopeNode that, Parser.Lexer loc) { RegionNode r = ctrl(new RegionNode(loc,null,ctrl(), that.ctrl()).keep()); - this._merge(that,r); + _merge(that,r); that.kill(); // Kill merged scope CodeGen.CODE.add(r); return r.unkeep(); } - public void _merge(ScopeNode that, RegionNode r) { - _merge(that,r,nIns()); - } - public void _merge(ScopeNode that, RegionNode r, int max ) { - for( int i = 1; i < max; i++) + private void _merge(ScopeNode that, RegionNode r) { + for( int i = 1; i < nIns(); i++) if( in(i) != that.in(i) ) { // No need for redundant Phis // If we are in lazy phi mode we need to a lookup // by name as it will trigger a phi creation @@ -383,38 +328,18 @@ public class ScopeNode extends MemMergeNode { // Balance arms of an IF. Extra lonely defs are thrown: "if(pred) int x;". // Forward refs are copied to the other side, "as if" they were there all along. - // 'this' scope is the default, 'scope' might have frefs. public void balanceIf( ScopeNode scope ) { - if( nIns()==scope.nIns() ) - return; // Fast-path cutout - int kidx = enclosingFunction(); - assert _kinds.at(kidx) == scope._kinds.at(kidx); - if( kidx==0 ) kidx = 1; - for( int i = _kinds.at(kidx-1)._lexSize; i < scope.nIns(); i++ ) { + for( int i = nIns(); i < scope.nIns(); i++ ) { Var n = scope.var(i); - if( i<nIns() && var(i)==n ) continue; if( n.isFRef() ) { // RHS has forward refs - _vars.insert(n,i); - insertDef(i,scope.in(i)); + _vars.add(n); // Copy to LHS + addDef(scope.in(i)); } else throw Parser.error("Cannot define a '"+n._name+"' on one arm of an if",n._loc); } } - public void balanceLoopFRefs( ScopeNode scope ) { - for( int i = nIns(); i < scope.nIns(); i++ ) { - Var n = scope.var(i); - if( n.isFRef() ) { // Loop body has forward refs - Var v = new Var(nIns(),n._name,n.type(),n._final,n._loc,true); - v._uninit = n._uninit; - _vars.add(v); - addDef(scope.in(i)); - } - } - } - - // ------------------------------------------------ // peephole the backedge scope into this loop head scope // We set the second input to the phi from the back edge (i.e. loop body) public void endLoop(ScopeNode back, ScopeNode exit ) { @@ -422,108 +347,49 @@ public class ScopeNode extends MemMergeNode { assert ctrl instanceof LoopNode loop && loop.inProgress(); ctrl.setDef(2,back.ctrl()); - this._endLoop( this, back, exit); + _endLoop(this,back,exit); back.kill(); // Loop backedge is dead // Now one-time do a useless-phi removal - this._useless(); + _useless(); } // Fill in the backedge of any inserted Phis void _endLoop( ScopeNode scope, Node back, Node exit ) { for( int i=1; i<nIns(); i++ ) { - if( var(i)._final ) continue; // Final vars did not get modified in the loop - Node n = in(i); - if( var(i).type().isHighOrConst() && !(n instanceof PhiNode phi && phi.inProgress()) ) { // Cannot lift higher than a constant, so no Phi - Node con = ConstantNode.make(var(i).type()).init(); - n.subsume(con); - n = con; - } else if( back.in(i) != scope ) { - PhiNode phi = (PhiNode)n; + if( _vars.at(i)._final ) continue; // Final vars did not get modified in the loop + if( _vars.at(i).type().isHighOrConst() ) continue; // Cannot lift higher than a constant, so no Phi + if( back.in(i) != scope ) { + PhiNode phi = (PhiNode)in(i); assert phi.region()==scope.ctrl() && phi.in(2)==null; phi.setDef(2,back.in(i)); // Fill backedge } if( exit.in(i) == scope ) // Replace a lazy-phi on the exit path also - exit.setDef(i,n); + exit.setDef(i,in(i)); + } + } + + + // Now one-time do a useless-phi removal + void _useless( ) { + for( int i=1; i<nIns(); i++ ) { + if( in(i) instanceof PhiNode phi ) { + // Do an eager useless-phi removal + Node in = phi.peephole(); + CodeGen.CODE.addAll(phi._outputs); + phi.moveDepsToWorklist(); + if( in != phi ) { + if( !phi.iskeep() ) // Keeping phi around for parser elsewhere + phi.subsume(in); + setDef(i,in); // Set the update back into Scope + } + } } } - // ------------------------------------------------ // Up-casting: using the results of an If to improve a value. // E.g. "if( ptr ) ptr.field;" is legal because ptr is known not-null. - // - // This operation is lexical, not a full semantic theorem prover, and thus - // its behavior changes the language spec. Optimizations that hide the - // lexical structure (e.g. constant folding control flow) can hide what is - // guarded or how the guard is applied, making it somewhat fragile. Several - // good use cases are covered here, but not everything you might like. - // - // Simple lexical facts work well: - // - // ``` - // if( ptr ) - // return ptr.field; // `ptr` is guarded non-null here. - // - // if( a && a.ptr ) - // return a.ptr.x; // `a` is guarded while parsing the RHS load, - // // and remains guarded on the true arm. - // ``` - // - // Short-circuit expressions are lowered as nested Phis. We recursively - // decompose the recognized Phi shape, so chains such as: - // - // ``` - // if( x && y && z ) - // use(x,y,z); - // ``` - // - // discover the nested `x && y` and then `x`, `y`, and `z`; this is not - // merely a one-level lookup. Likewise, nested tests carry lexical guards - // inwards: - // - // ``` - // if( x && y ) - // if( z && a ) - // if( x.ptr && z.ptr ) - // ... - // ``` - // - // Here `x` is already guarded while parsing the outer true arm, `z` while - // parsing the inner true arm, and both are available before the matching - // loads. The later post-test decomposition is just a convenience for - // values still schedulable at the post-test control. - // - // The hard limit is that a semantic implication is not always a - // schedulable Node. For `(x && y)`, the true side implies that `y` was - // true, but `y` might be a value computed only on the RHS control arm: - // - // ``` - // val ptr = ...; - // if( ptr && (ptr=fcn() ? 0 : arg ) {} - // ``` - // - // A Guard pinned after the Phi merge and using the branch-local `ptr` - // value would give that value a use above its defining arm. GCM then has - // no legal block between the value's early control and the LCA of its - // uses. Thus `_addGuards` first checks that the predicate's effective - // early CFG dominates the control where the Guard would be pinned. If it - // does not, we skip that guard even if the fact is semantically true. - // - // A more complete answer would need a richer logical/proof node, or would - // need to rematerialize predicates on the guarded path instead of using - // branch-local nodes directly. Plain GuardNodes are intentionally kept to - // facts represented by values already available at the guard control. - // - // TODO: Make this more robust for shapes like: - // ``` - // if( !ary || !ary# ) exit(); // facts about ary are lost, despite the early exit - // for( ...; ary# ) ...; - // ``` - // Here the guard ends up guarding the whole expression (both ary and ary# - // are not-null), but loses that ary is not-null. A better answer might be - // a dedicated logical expression node, or some sort of gather-of-checked - // values to Guard post-test. - - public void addGuards( CFGNode ctrl, Node pred, boolean invert ) { + public void addGuards( Node ctrl, Node pred, boolean invert ) { + assert ctrl instanceof CFGNode; _guards.add(ctrl); // Marker between guard sets // add pred & its cast to the normal input list, with special Vars if( pred==null || pred.isDead() ) @@ -531,11 +397,10 @@ public class ScopeNode extends MemMergeNode { _addGuards(ctrl,pred,invert); } - private void _addGuards( CFGNode ctrl, Node pred, boolean invert ) { + private void _addGuards( Node ctrl, Node pred, boolean invert ) { if( pred==null || pred.isDead() ) return; - CFGNode early = CFGNode.earlyCFG(pred); - if( early != null && !early.dominates(ctrl,null) ) + if( !availableAt(pred,(CFGNode)ctrl,new BitSet()) ) return; // A single negation is handled below. For !!p (including p != null) // or a negated short-circuit Phi, also discover the underlying facts. @@ -545,10 +410,8 @@ public class ScopeNode extends MemMergeNode { _addGuards(ctrl,not.in(1),!invert); pred.unkeep(); } - // Short-circuit logic is represented by a Phi. For `a || b` being - // false, or `a && b` being true, both individual operands have the - // same proven value as the whole expression. The "skipped RHS" path - // is the Region input whose Phi value is the controlling If predicate. + // A false || or true && proves both operands. Only guard values + // available at this control, not ones confined to the RHS branch. if( pred instanceof PhiNode phi && phi.nIns()==3 && phi.region() instanceof RegionNode r && r.nIns()==3 ) for( int i=1; i<3; i++ ) if( r.in(i) instanceof CProjNode prj && prj.ctrl() instanceof IfNode iff ) { @@ -562,19 +425,31 @@ public class ScopeNode extends MemMergeNode { // Invert the If conditional if( invert ) pred = pred instanceof NotNode not ? not.in(1) : CodeGen.CODE.add(new NotNode(pred).peephole()); - Node zeroPred = pred instanceof NotNode not ? not.in(1) : null; // This is a zero/null test. // Compute the positive test type. _addGuard(true,ctrl,pred); - - // Compute the negative test type. - if( zeroPred != null ) - _addGuard(false,ctrl,zeroPred); + if( pred instanceof NotNode not ) + _addGuard(false,ctrl,not.in(1)); + } + + // A Phi's value is available at its Region. Other data nodes require all + // their inputs, including pinned control, to dominate the proposed guard. + static boolean availableAt(Node n, CFGNode ctrl, BitSet visit) { + if( n==null || n instanceof ConstantNode || visit.get(n._nid) ) return true; + visit.set(n._nid); + if( n instanceof PhiNode phi ) n = phi.region(); + if( n instanceof CFGNode cfg ) { + for( CFGNode dom=ctrl; dom!=null; dom=dom.idom() ) + if( dom==cfg ) return true; + return false; + } + for( Node def : n._inputs ) + if( !availableAt(def,ctrl,visit) ) return false; + return true; } private static Node shortCircuitSkippedPred( Node val, Node pred, CProjNode prj, boolean invert ) { - if( val == null ) - return null; + if( val == null ) return null; if( val == pred ) return (prj._idx==0)==invert ? val : null; if( val instanceof NotNode not && not.in(1)==pred ) @@ -587,19 +462,16 @@ public class ScopeNode extends MemMergeNode { } private void _addGuard(boolean nonZero, Node ctrl, Node pred) { - Node guard = new GuardNode(nonZero,ctrl,pred).peephole(); - if( guard != pred ) { - pred.keep(); - guard.keep(); - _guards.add(pred); - _guards.add(guard); - replace(pred,guard,nonZero); - } + Node guard = new GuardNode(nonZero,ctrl,pred.keep()).peephole(); + if( guard==pred ) { pred.unkeep(); return; } + guard.keep(); + _guards.add(pred); + _guards.add(guard); + replace(pred,guard); } - // Remove matching pred/cast pairs from this guarded region. - public void removeGuards( Node ctrl ) { + public ScopeNode removeGuards( Node ctrl ) { assert ctrl instanceof CFGNode; // Pop the guards up to this region's marker. while( true ) { @@ -609,6 +481,7 @@ public class ScopeNode extends MemMergeNode { g .unkill(); // Pop/kill cast _guards.pop().unkill(); // Pop/kill pred } + return this; } // If we find a guarded instance of pred, replace with the upcasted version @@ -638,65 +511,11 @@ public class ScopeNode extends MemMergeNode { } - private void replace( Node old, Node cast, boolean nonZero ) { + private void replace( Node old, Node cast ) { assert old!=null && old!=cast; for( int i=0; i<nIns(); i++ ) - if( in(i)==old || nonZero && nullCheckedPhi(in(i),old) ) + if( in(i)==old ) setDef(i,cast); } - private static boolean nullCheckedPhi( Node n, Node old ) { - if( !(n instanceof PhiNode phi) || phi.nIns()!=3 ) - return false; - if( phi.in(1)==null || phi.in(2)==null ) - return false; - int zero = phi.in(1)._type == phi.in(1)._type.makeZero() ? 1 : - phi.in(2)._type == phi.in(2)._type.makeZero() ? 2 : -1; - if( zero == -1 ) - return false; - Node val = phi.in(3-zero); - return val==old || - val instanceof GuardNode guard && guard._nonZero && guard.in(1)==old; - } - - // ------------------------------------------------------------------------ - // Insert a top-level public external symbol, "as-if" some #include/import - // was executed at the top of tile. - public Var insertExtern( ExternNode ext ) { - // Size of top-level scope - int lexSize = _kinds.at(0)._lexSize; - Var var = new Var(lexSize,ext._extern,ext._con,true,null); - insert(var,ext,0); - return var; - } - - // ------------------------------------------------------------------------ - // Insert a Var with Node n in the lex scope just prior to kidx - private void insert( Var var, Node n, int kidx ) { - // Insert just prior to scope kidx - int nidx = _kinds.at(kidx)._lexSize; - _vars.insert(var,nidx); - insertDef(nidx,n); - // All outer Kinds lexical offsets bump by one - for( int i = kidx; i < depth(); i++ ) - _kinds.at(i)._lexSize++; - // All Vars outside the innermost lexical scope bump out one - for( int i=nidx+1; i<_vars._len; i++ ) - var(i)._idx++; - } - - // Save the existing Node state to an array - public Node[] save() { - Node[] olds = new Node[nIns()]; - for( int i=0; i<nIns(); i++ ) - olds[i] = in(i)==null ? null : in(i).keep(); - return olds; - } - - // Restore saved Node state back into scope - public void restore( Node[] olds ) { - assert olds.length<=nIns(); - for( int i=0; i<olds.length; i++ ) - setDef(i,olds[i]==null ? null : olds[i].unkeep()); - } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ShlNode.java index f657cf51..7d6e8e2e 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/ShlNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ShlNode.java @@ -7,7 +7,6 @@ import com.seaofnodes.simple.type.TypeInteger; public class ShlNode extends ArithNode { public ShlNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs, (byte)1); } - @Override public Tag serialTag() { return Tag.Shl; } @Override public String op() { return "<<"; } @Override long doOp( long x, long y ) { return x << y; } @@ -37,7 +36,7 @@ public class ShlNode extends ArithNode { if( lhs instanceof AddNode add && addDep(add).in(2)._type instanceof TypeInteger c && c.isConstant() ) { long sum = c.value() << shl.value(); if( Integer.MIN_VALUE <= sum && sum <= Integer.MAX_VALUE ) - return new AddNode(new ShlNode(_loc,add.in(1),rhs).peephole(), CodeGen.CODE.con(sum) ); + return new AddNode(new ShlNode(_loc,add.in(1),rhs).peephole(), com.seaofnodes.simple.Parser.con(sum) ); } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ShrNode.java index e93ea0e8..4ce5f5b4 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/ShrNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ShrNode.java @@ -6,7 +6,6 @@ import com.seaofnodes.simple.type.TypeInteger; public class ShrNode extends ArithNode { public ShrNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs, (byte)1); } - @Override public Tag serialTag() { return Tag.Shr; } @Override public String op() { return ">>>"; } @Override long doOp( long x, long y ) { return x >>> y; } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/SplitNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/SplitNode.java index 3042f857..a7fe6c5f 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/SplitNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/SplitNode.java @@ -3,16 +3,13 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.Encoding; -import com.seaofnodes.simple.codegen.LRG; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.util.SB; public abstract class SplitNode extends MachConcreteNode { - public final LRG _lrg; // Live range for split public final String _kind; // Kind of split - public final byte _round; // Debugging info for which RegAlloc round - public SplitNode(LRG lrg,String kind, byte round, Node[] nodes) { super(nodes); _lrg=lrg; _kind = kind; _round = round; } + public final byte _round; + public SplitNode(String kind, byte round, Node[] nodes) { super(nodes); _kind = kind; _round = round; } // A register-only transfer may need another split to reach stack storage. public boolean isNonStackSplit() { return false; } @Override public String op() { return "mov"; } @@ -22,7 +19,6 @@ public abstract class SplitNode extends MachConcreteNode { else p.n(in(1)); return p.close(); } - public FunNode fun(Encoding enc) { return enc._code._regAlloc.lrg(this)._fun; } @Override public Type compute() { return in(0)._type; } @Override public Node idealize() { return null; } @Override public void asm(CodeGen code, SB sb) { diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/StartCUNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/StartCUNode.java deleted file mode 100644 index cd51f603..00000000 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/StartCUNode.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.Serialize; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.util.BAOS; - -import java.util.HashMap; -import java.util.IdentityHashMap; - -// Start of a single compilation unit, distinct from the outer whole-program Start. -public class StartCUNode extends StartNode { - public final String _fname; - public StartCUNode(StartNode start, StopCUNode stop, Type arg, String fname) { super(start,stop,arg); _fname = fname; } - public StartCUNode(StartCUNode start) { super(start); _fname = start._fname; } - @Override public Tag serialTag() { return Tag.StartCU; } - @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node,Integer> anodes ) { - baos.packed2(types.get(_arg)); - baos.packed2(strs.get(_fname)); - } - static Node make( BAOS bais, String[] strs, Type[] types ) { - return new StartCUNode(null,null,types[bais.packed2()],strs[bais.packed2()]); - } - - @Override public CFGNode cfg0() { return (CFGNode)in(0); } - - // IDom is cfg0() and depth is always Start+1 which is 0+1 or 1 - @Override public int idepth() { return 1; } - @Override public CFGNode idom(Node dep) { return cfg0(); } - @Override public void gather( HashMap<String,Integer> strs ) { - Serialize.gather(strs,_fname); - } -} diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/StartNode.java index 495be0a2..e13d35c0 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -5,76 +5,39 @@ import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.Utils; -import java.util.Arrays; import java.util.HashSet; +import static com.seaofnodes.simple.util.Utils.TODO; -/** - * The Start node represents the external world calling, be it calling the - * entry <clinit> or just some linked caller calling. - * <p> - * Start initially has 1 input (arg) from outside and the initial control. - * In ch10 we also add mem aliases as structs get defined; each field in struct - * adds a distinct alias to Start's tuple. - * <p> - * By ch25, Start is the external world (or linker) calling with everything - * that has escaped. Start is NOT the entry <clinit> itself, which can assume - * no memory nor fidxs exist nor escaped. Start gets escaped things from Stop; - * together these close the Grand Cycle between the code we know and the code - * we don't. - */ +/** Start supplies control, whole memory, and the argument, in that order. */ public class StartNode extends LoopNode implements MultiNode { final Type _arg; - public StartNode(StartNode start, StopNode stop, Type arg) { super(null,start,stop); _arg = arg; } + public StartNode(Type arg) { super(null,null); _arg = arg; _type = compute(); } public StartNode(StartNode start) { super(start); _arg = start==null ? null : start._arg; } - @Override public Tag serialTag() { return Tag.Start; } - public Type arg() { return _arg; } + + @Override public String label() { return "Start"; } @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p(label()); } - @Override public CFGNode cfg0() { return null; } + @Override public CFGNode cfg0() { return null; } // Get the one control following; error to call with more than one e.g. an - // IfNode or other multi-way branch. For Start, it's the class init for - // this compilation unit. + // IfNode or other multi-way branch. For Start, its "main" @Override public CFGNode uctrl() { - // Find module.<clinit>, it's the start. + // Find "main", it's the start. CFGNode C = null; for( Node use : _outputs ) - if( use instanceof FunNode fun && CodeGen.CODE.entryClinitName().equals(fun._name) ) + if( use instanceof FunNode fun && fun.sig().isa(CodeGen.CODE._main) ) { assert C==null; C = fun; } - if( C==null ) - for( FunNode fun : CodeGen.CODE._linker ) - if( fun!=null && !fun.isDead() && CodeGen.CODE.entryClinitName().equals(fun._name) ) - { assert C==null; C = fun; } return C; } - boolean escapedFIDX(int fidx) { - // Public class fields are callable by the unknown external world - // regardless of the current SCCP approximation of Start memory. - if( CodeGen.CODE.publicFIDX(fidx) ) return true; - if( !(_type instanceof TypeTuple tt) ) - return !_type.isHigh(); // TOP never escapes, BOTTOM always escapes - TypeMem tmem = (TypeMem)tt._types[1]; - if( tmem == TypeMem.BOT ) return false; - if( tmem == TypeMem.TOP ) return false; - if( XInt.isHigh(tmem._escFs) ) - return false; - return XInt.bit(tmem._escFs,fidx); - } @Override public TypeTuple compute() { - StopNode stop = (StopNode)in(1); - TypeMem tmem = stop._type instanceof TypeTuple tt - ? tt.mem() - : stop._type.isHigh() ? TypeMem.START : TypeMem.BOT; - assert tmem._alias==1 && !tmem._one; - return TypeTuple.make(Type.CONTROL,tmem,_arg); + return TypeTuple.make(Type.CONTROL,TypeMem.BOT,_arg); } @Override public Node idealize() { return null; } @@ -83,5 +46,4 @@ public class StartNode extends LoopNode implements MultiNode { @Override public int idepth() { return 0; } @Override public CFGNode idom(Node dep) { return null; } - @Override public boolean eq( Node n ) { return true; } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/StopCUNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/StopCUNode.java deleted file mode 100644 index d3a356ed..00000000 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/StopCUNode.java +++ /dev/null @@ -1,124 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.BAOS; - -import java.util.HashMap; -import java.util.IdentityHashMap; - -// Stop of a single compilation unit, distinct from the outer whole-program Stop. -public class StopCUNode extends StopNode { - public StopCUNode() { super(); } - public StopCUNode(StopNode stop) { super(stop); } - @Override public Tag serialTag() { return Tag.StopCU; } - @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { baos.packed1(nIns()); } - static Node make( BAOS bais ) { - StopCUNode stop = new StopCUNode(); - stop.setDefX(bais.packed1()-1,null); - return stop; - } - - public StartCUNode start() { - for( Node use : _outputs ) - if( use instanceof StartCUNode start ) - return start; - return null; - } - - @Override - public TypeTuple compute() { - // During Parsing, new Stops and Returns are being added. This Stop - // cannot know if more are coming, so must assume the worst. - if( CodeGen.CODE._phase ==null || CodeGen.CODE._phase == CodeGen.Phase.Parse ) - return TypeTuple.STATE; - - StartCUNode start = start(); - // Memory start from all returns - TypeMem xmem = TypeMem.START; - // Escape summaries are monotone discovery facts. In Opto, a Return - // can widen from a precise set to FULL; the public-only normalization - // below must not forget private bits already discovered precisely from - // that Return on an earlier SCCP step. - if( CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Opto.ordinal() && - CodeGen.CODE._phase.ordinal() <= CodeGen.Phase.Lower.ordinal() && - _type instanceof TypeTuple old && old.mem() instanceof TypeMem omem ) - xmem = xmem.makeFrom(XInt.meet(xmem._escFs,omem._escFs), - XInt.meet(xmem._escAs,omem._escAs)); - - for( Node def : _inputs ) { - ReturnNode ret = (ReturnNode)def; - if( ret._type == Type.TOP ) continue; - if( !(ret._type instanceof TypeTuple tret) ) - return TypeTuple.STATE; // Must be Type.BOTTOM - - FunNode fun = ret.fun(); - boolean noUserCtor = fun.isInstance() && - !CodeGen.hasUserConstructor(((TypeMemPtr)fun.sig().arg(0))._obj); - // Instance <init> is an allocation helper, never an externally - // callable constructor when a user constructor exists. Otherwise - // the same no-arg <init> is the public constructor. - boolean pub = fun.isPublic() && (fun.isClz() || noUserCtor); - TypeFunPtr sig = fun.sig(); - // If public init, the function escapes and the related class escapes - if( pub ) { - xmem = xmem.escapes(sig); - // Classes also escape their public clazz pointer, which then - // escapes all their public fields. Private fields do not - // escape, and are only available from the local class code. - if( fun.isClz() ) - xmem = xmem.escapesAliases(sig._sig[0]); - } - - // The instance <init> Start edge supplies conservative pre-Opto - // inputs, but its private-memory result is consumed only by the - // allocation sequence and is never an externally visible return. - if( fun.isInstance() && !noUserCtor ) continue; - - // Does this function escape? If not, then no need to MEET - // here as its effects are not visible to the outside world. - addDepForwards(start); // Check again if function escapes - if( pub || start.escapedFIDX(sig.fidx()) ) { - if( tret.ctl().isHigh() ) continue; // Return not reachable - // Capture (precisely?) all escaping pointer aliases and fidxs. - // Escaped fidxs means the linked world can call that function; - // escaped aliases means the linked world can R/W those aliases. - xmem = xmem.escapes(tret.ret()); - xmem = publicBottom(xmem); - xmem = (TypeMem)xmem.meet(publicBottom(tret.mem(),xmem)); - } - } - assert xmem._alias==1 && !xmem._one; - return TypeTuple.make(Type.CONTROL,xmem,Type.BOTTOM); - } - - // FULL escape sets arise from global BOTTOM. Across a compilation-unit - // boundary global BOTTOM exposes every public field, while retaining any - // private escapes already discovered precisely. - private TypeMem publicBottom(TypeMem mem) { return publicBottom(mem,mem); } - private TypeMem publicBottom(TypeMem mem, TypeMem prior) { - // The pessimistic Iter pass naturally lifts transient FULL inputs to - // precise finite sets. Replacing FULL early there would let a later - // input refinement add bits, reversing Iter's monotonic direction. - // This normalization is needed only for Opto's optimistic grand cycle. - if( CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal() || - CodeGen.CODE._phase.ordinal() > CodeGen.Phase.Lower.ordinal() ) return mem; - int[] escFs = XInt.isHigh(mem._escFs) - ? XInt.meet(prior._escFs,CodeGen.CODE.publicFIDXs()) - : mem._escFs; - int[] escAs = XInt.isHigh(mem._escAs) - ? XInt.meet(prior._escAs,CodeGen.CODE.publicAliases()) - : mem._escAs; - return escFs==mem._escFs && escAs==mem._escAs ? mem : mem.makeFrom(escFs,escAs); - } - - @Override - public Node idealize() { - int len = nIns(); - for( int i=0; i<nIns(); i++ ) - if( in(i) instanceof ReturnNode ret && addDep(ret.fun()).isDead() ) - delDef(i--); - if( len != nIns() ) return this; - return null; - } -} diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/StopNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/StopNode.java index a7e52563..9f7a925b 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/StopNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/StopNode.java @@ -2,46 +2,34 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.Serialize; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.BAOS; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.util.Utils; -import java.util.HashMap; -import java.util.IdentityHashMap; - -// A collection of function ReturnNodes for a compilation unit. public class StopNode extends CFGNode { - public StopNode() { + public final String _src; + + public StopNode(String src) { super(); + _src = src; _type = compute(); } - public StopNode(StopNode stop) { super(stop); } - @Override public Tag serialTag() { return Tag.Stop; } - @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { baos.packed1(Serialize.nIns(this,anodes)); } - static Node make( BAOS bais ) { - StopNode stop = new StopNode(); - stop.setDefX(bais.packed1()-1,null); - return stop; + public StopNode(StopNode stop) { super(stop); _src = stop==null ? null : stop._src; } + + @Override + public String label() { + return "Stop"; } @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { // For the sake of many old tests, and single value prints as "return val" - ReturnNode ret1 = null; - for( Node n : _inputs ) { - if( n instanceof ReturnNode ret ) { - if( ret1 != null ) { ret1=null; break; } - ret1 = ret; - } - } + ReturnNode ret1 = ret(); if( ret1!=null ) return p.n(ret1); - p.p("Stop[ "); - for( Node n : _inputs ) - if( n instanceof ReturnNode ret ) { - String name = ret.fun()._name; + for( Node ret : _inputs ) + if( ret!=null ) { + String name = ((ReturnNode)ret).fun()._name; if( name== null || !name.startsWith("sys.") ) p.n(ret).p(" "); } @@ -54,36 +42,25 @@ public class StopNode extends CFGNode { // If a single Return, return it. // Otherwise, null because ambiguous. public ReturnNode ret() { - ReturnNode ret1 = null; - for( Node n : _inputs ) { - // TODO: remove isPublic - if( n instanceof ReturnNode ret && ret.fun().isPublic() ) { - if( ret1 != null ) return null; // Ambiguous - ret1 = ret; - } + Node ret1 = this; + for( Node ret : _inputs ) { + String name = ((ReturnNode)ret).fun()._name; + if( name==null || !name.startsWith("sys.") ) + ret1 = ret1==this ? ((ReturnNode)ret) : null; } - return ret1; + return ret1==this ? null : (ReturnNode)ret1; } @Override - public TypeTuple compute() { - // During Parsing, new Stops and Returns are being added. This Stop - // cannot know if more are coming, so must assume the worst. - if( CodeGen.CODE._phase ==null || CodeGen.CODE._phase == CodeGen.Phase.Parse ) - return TypeTuple.STATE; - - // Just meet-over-inputs. - TypeTuple tt = TypeTuple.STOP_HIGH; - for( Node def : _inputs ) - tt = (TypeTuple)tt.meet(def._type); - assert tt.mem()._alias==1 && !tt.mem()._one; - return tt; + public Type compute() { + return Type.BOTTOM; } - @Override public Node idealize() { + @Override + public Node idealize() { int len = nIns(); for( int i=0; i<nIns(); i++ ) - if( in(i).nIns()==0 ) + if( addDep(((ReturnNode)in(i)).fun()).isDead() ) delDef(i--); if( len != nIns() ) return this; return null; @@ -93,10 +70,7 @@ public class StopNode extends CFGNode { if( validIDepth() ) return _idepth; int d=0; for( Node ret : _inputs ) - // All public results are hooked on the Stop, including data - // values, so must check CFG - if( ret instanceof CFGNode cfg ) - d = Math.max(d,cfg.idepth()+1); + d = Math.max(d,((ReturnNode)ret).idepth()+1); return cacheIDepth(d); } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java index cc958348..a4abaa1d 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -2,14 +2,9 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.codegen.GlobalBits; import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.BAOS; -import com.seaofnodes.simple.util.Utils; -import java.util.HashMap; -import java.util.IdentityHashMap; /** * Store represents setting a value to a memory based object, in chapter 10 @@ -17,10 +12,7 @@ import java.util.IdentityHashMap; */ public class StoreNode extends MemOpNode { - final boolean _init; // Initializing writes are allowed to write null - // Semantic store width in bytes. Zero is the sole undecided state; - // otherwise hardware and ideal Stores are the 1/2/4/8-byte variants. - private byte _size; + private final boolean _init; // Initializing writes are allowed to write null /** * @param name The struct field we are assigning to @@ -29,43 +21,20 @@ public class StoreNode extends MemOpNode { * @param off The offset inside the struct base * @param value Value to be stored */ - public StoreNode(Parser.Lexer loc, String name, int alias, Type decl, Node ctrl, Node mem, Node ptr, Node off, Node value, boolean init) { - this(loc,name,alias,decl,ctrl,mem,ptr,off,value,init,(byte)0); - } - private StoreNode(Parser.Lexer loc, String name, int alias, Type decl, Node ctrl, Node mem, Node ptr, Node off, Node value, boolean init, byte size) { - super(loc, name, alias, false, decl, ctrl, mem, ptr, off, value); + public StoreNode(Parser.Lexer loc, String name, int alias, Type glb, Node mem, Node ptr, Node off, Node value, boolean init) { + super(loc, name, alias, false, glb, mem, ptr, off, value); _init = init; - _size = size; - } - StoreNode( BAOS bais, String[] strs, Type[] types, GlobalBits fileAliases, GlobalBits aliases ) { - super(bais,strs,types,fileAliases,aliases,false); - addDef(null); - _init = bais.read() != 0; - _size = (byte)bais.read(); - } - @Override public Tag serialTag() { return Tag.Store; } - @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { - super.packed(baos,strs,types, anodes ); - baos.write(_init ? 1 : 0); - baos.write(_size); } + // Debugger label @Override boolean canDrop(MemOpNode other, Node dep) { return super.canDrop(other,dep) && _init==((StoreNode)other)._init; } - // Debugger label @Override public String label() { return "st_"+mlabel(); } @Override public boolean isMem() { return true; } - public Node nnptr() { - if( !(ptr() instanceof GuardNode cast) ) return ptr(); - assert cast._nonZero; - return cast.in(1); - } public Node val() { return in(4); } - public int storeSize() { assert _size==1 || _size==2 || _size==4 || _size==8; return _size; } - public int log_size() { return Integer.numberOfTrailingZeros(storeSize()); }; @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { @@ -74,119 +43,52 @@ public class StoreNode extends MemOpNode { @Override public Type compute() { - Type val = val()._type; - Type mem0 = mem()._type; - Type ptr0 = ptr()._type; - // Validate argument types - if( ptr0.isHigh() ) - return TypeMem.TOP; - if( !(mem0 instanceof TypeMem mem) || - !(ptr0 instanceof TypeMemPtr ptr) ) - return mem0.isHigh() || ptr0==Type.NIL ? TypeMem.TOP : TypeMem.BOT; - - // Sharpen memory value; required for narrowing stores where the parser - // inserts zero/sign masking and somebody reads the TypeMem type. - Type decl = declaredType(); - if( decl != Type.BOTTOM ) - { assert decl != Type.TOP; val = val.join(decl); } - // A singleton class's final constant field lives in the static image. - // Its precise memory slice is a constant too; ordinary peepholes can - // replace this Store before scheduling or instruction selection. - // A constant runtime value alone is not enough: mutable fields (notably - // $init) still need their writes, and instance fields need allocation. - Field fld = ptr._obj.field(_name); - if( _alias!=1 && ptr._one && Parser.startsClzPrefix(ptr._obj._name) && - fld!=null && fld._final && decl.isConstant() ) - return TypeMem.make(_alias,val,true,true,true,mem._escFs,mem._escAs).escapesFrom(val); - - // Allocation uses a private TypeMem and nothing else does. This - // memory is truly private; a temporary singleton until it escapes - - // which is never does in a constructor. - if( !_name.equals("[]") && mem._one ) - // Just track the stored value - return TypeMem.make(_alias,val,true,false,_init,null,null).escapesFrom(val); - - // Normal aliasing Store. - assert mem._alias==1 || mem._alias==_alias; // Perfect aliasing - - // Single alias store. If memory is fat, attempt to get a narrow slice. - Type tfld = mem._alias==1 && mem._t instanceof TypeStruct ts - ? ts._fields[ts.findAlias(_alias)]._t - : mem._t; - - // Same alias, meet into other fields. - Type t = val.meet(tfld); - // Bulk memory's final bit describes its conservative remainder, not - // this newly precise slice. Slice finality comes from the field. - boolean xfinal = fld != null && fld._final; - return TypeMem.make(_alias,t,mem._one,mem._clz,xfinal,mem._escFs,mem._escAs).escapesFrom(val); + if( _alias==1 ) return TypeMem.BOT; + Type val = val()._type; + assert !_declaredType.isFRef(); + Type t = val.join(_declaredType).meet(MemMergeNode.contents(mem(),_alias,this)); + return TypeMem.make(_alias,t); } @Override public Node idealize() { - // Freeze the semantic Store variant as soon as the pointer exposes a - // canonical field declaration. Waiting until after structural memory - // peeps can lose the declaration to a flow-sensitive constant field - // state. Imported Stores already carry a serialized non-zero width. - if( _size==0 && (_size= storageType()) != 0 ) { - unlock(); // Width participates in the cached hash. - return init(); + if( _alias==1 ) { + if( !(ptr()._type instanceof TypeMemPtr ptr) || ptr._obj._open ) return null; + Field field = ptr._obj.field(_name); + if( field==null ) return null; + Node value = new ConvertNode(field._t,val()).peephole(); + StoreNode store = new StoreNode(_loc,_name,field._alias,field._t,mem(),ptr(),off(),value,_init); + store.setDef(0,in(0)); + return new MemMergeNode(mem(),field._alias,store.peephole()); } - - assert !(mem()._type instanceof TypeMem mem) || _alias == 1 || mem._alias == 1 || _alias == mem._alias; - - // Simple store-after-MemMerge to a known alias can bypass. Happens when inlining. - Node mem = mem(); - if( _alias != 1 && mem instanceof MemMergeNode merge ) { - setDef(1,merge.alias(_alias)); + if( mem() instanceof MemMergeNode merge ) { + setDef(1,CodeGen.CODE.add(merge.alias(_alias))); return this; } - Field fld; - if( _alias==1 && ptr()._type instanceof TypeMemPtr tmp && (fld=tmp._obj.field(_name)) != null && _size != 0 ) { - // Expand Store(bulkMem,alias#1) into MemMerge(bulkMem,#N:Store(bulkMem,alias#N)). - // Normally I'd like to just drop in this xform and move on, but - // users of the bulk Store memory might nontheless depend on the - // sharp memory type (e.g. I64) from the store's memory. Using the - // MemMerge means using the bulk memory which e.g. might be BOT. - // Check for store users with the same alias, and use the new store - // directly, preserving the sharper graph. - Node st = new StoreNode(_loc,_name,fld._alias,fld._t,in(0),mem,ptr(),off(),val(),_init,_size).peephole(); - for( int i=0; i<nOuts(); i++ ) { - Node use = out(i); - if( useAlias(use)==fld._alias ) { - int idx = use._inputs.find(this); - assert idx != -1; - CodeGen.CODE.add(use).setDef(idx,st); - i--; // setDef removed use from this Store's outputs - } - } - MemMergeNode mmm = mem instanceof BulkMemPhiNode bulk - ? bulk.aggregate(mem,fld._alias,st) - : new MemMergeNode(null,mem); - if( !(mem instanceof BulkMemPhiNode) ) - mmm.alias(fld._alias,st); - return mmm.init(); + if( mem() instanceof CastNode cast ) { + setDef(1,cast.in(1)); + return this; } // Simple store-after-store on same address. Should pick up the // required init-store being stomped by a first user store. - if( mem instanceof StoreNode st && - ptr()==st.ptr() && // Must check same object - off()==st.off() && // And same offset (could be "same alias" but this handles arrays to same index) + if( mem() instanceof StoreNode st && + ptr()==st.ptr() && _alias==st._alias && // Must check same object + off()==st.off() && // And same offset ptr()._type instanceof TypeMemPtr && // No bother if weird dead pointers // Must have exactly one use of "this" or you get weird // non-serializable memory effects in the worse case. checkOnlyUse(st) ) { - assert _name==st._name; // Equiv class aliasing is perfect + assert _name.equals(st._name); // Equiv class aliasing is perfect setDef(1,st.mem()); return this; } // Value is automatically truncated by narrow store - if( _size!=0 && val() instanceof AndNode and && and.in(2)._type.isConstant() ) { - int log = log_size(); + if( val() instanceof AndNode and && and.in(2)._type.isConstant() ) { + int log = _declaredType.log_size(); if( log<3 ) { // And-mask vs narrow store long mask = ((TypeInteger)and.in(2)._type).value(); long bits = (1L<<(8<<log))-1; @@ -198,7 +100,7 @@ public class StoreNode extends MemOpNode { } // Store will chop high order bits off; math to change those bits can be dropped. - if( _size!=0 && val() instanceof SarNode shr && + if( val() instanceof SarNode shr && shr.in(1) instanceof ShlNode shl && shr.in(2)._type.isConstant() && shl.in(2)._type.isConstant() ) { @@ -214,56 +116,15 @@ public class StoreNode extends MemOpNode { return this; } } - return null; } - // Flow-sensitive pointer types can carry a current field value (e.g. the - // constant zero in a u32 field). Storage width comes from the canonical - // struct declaration. If the pointer/definition is not available yet, - // leave the Store undecided instead of consulting the value or `_con`. - private byte storageType() { - if( !(ptr()._type instanceof TypeMemPtr tmp) ) - return 0; - TypeStruct obj = (TypeStruct)Parser.TYPES.get(tmp._obj._name); - Field fld = obj.field(_name); - if( fld == null || fld._t == null ) - return 0; - if( fld._t == Type.BOTTOM ) - return 0; // Store into a final field - assert fld._t != Type.TOP; - // Freshly parsed field stores retain the declaring structure on their - // symbolic offset even after the pointer's flow type sharpens. - if( fld._t.isConstant() ) { - if( !(off() instanceof ConFldOffNode coff) ) - return 0; - fld = coff._ts.field(_name); - if( fld == null || fld._t == null ) - return 0; - } - // Compute storage size from field type - byte size = (byte)(1 << fld._t.log_size()); - assert size==1 || size==2 || size==4 || size==8; - return size; - } - - // Alias required by a direct consumer of this Store's memory result. - // Keep in sync with BulkMemPhiNode.outputAlias. - private int useAlias(Node use) { - return switch( use ) { - case MemOpNode mem -> mem._alias; - case MemPhiNode phi -> phi._alias; - case EscapeNode esc -> throw Utils.TODO("Untested"); // esc.pub()==this ? esc.fld()._alias : 0; - default -> 0; - }; - } - // Phi(mem, Store(phi,ptr,off,val)) -> Store(mem,ptr,off,Phi(load(mem),val)). // With no observers of the backedge store, only the last value matters. // The entry load preserves the field even when the loop takes zero trips. Node sink(MemPhiNode phi) { phi.addDepForwards(this); - if( nOuts()!=1 || _alias==1 || _size==0 || _init ) return null; + if( nOuts()!=1 ) return null; phi.addDep(ptr()); // Start with a fixed field of an allocation dominating the loop. // In particular, do not sink stores through a loop-varying address. @@ -287,9 +148,9 @@ public class StoreNode extends MemOpNode { if( ctrl==null ) return null; Node mem = phi.in(1); - Node init = new LoadNode(_loc,_name,_alias,null,mem,ptr(),off()).peephole(); + Node init = new LoadNode(_loc,_name,_alias,mem,ptr(),off()).peephole(); Node value = CodeGen.CODE.add(new PhiNode(_name,loop,init,val()).peephole()); - Node sink = new StoreNode(_loc,_name,_alias,_con,null,mem,ptr(),off(),value,_init,_size); + Node sink = new StoreNode(_loc,_name,_alias,_declaredType,mem,ptr(),off(),value,_init); // Break the memory cycle, killing the old store. Keep the Phi alive // even if that store was its last use, until our caller replaces it. phi.keep(); @@ -313,12 +174,11 @@ public class StoreNode extends MemOpNode { public Parser.ParseException err() { Parser.ParseException err = super.err(); if( err != null ) return err; - assert _size>0 || CodeGen.CODE._phase.ordinal() <= CodeGen.Phase.FinalizeIR.ordinal(); - if( ptr()._type == Type.TOP ) // Dead store? - return null; - TypeMemPtr tmp = (TypeMemPtr)ptr()._type; - Field f = tmp._obj.field(_name); - if( f!=null && (tmp._ro || f._final) && !_init ) + if( !(ptr()._type instanceof TypeMemPtr tmp) ) + return null; // Report the bad or unresolved receiver at its source. + Field field = tmp._obj.field(_name); + if( field==null ) return Parser.error("Cannot resolve field '"+_name+"'",_loc); + if( (tmp._ro || field._final) && !_init ) return Parser.error("Cannot modify final field '"+_name+"'",_loc); return null; } @@ -331,5 +191,4 @@ public class StoreNode extends MemOpNode { // Identity also disables generic Phi factoring of Stores. // The closed-loop Store sink is a separate peep and still applies. @Override public boolean eq(Node n) { return this == n; } - @Override int hash() { return super.hash() ^ _size; } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/StructNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/StructNode.java new file mode 100644 index 00000000..a91ee5de --- /dev/null +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/StructNode.java @@ -0,0 +1,56 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.SB; + +/** + * Build a compound object + */ +public class StructNode extends Node { + + public TypeStruct _ts; + public StructNode(TypeStruct ts) { _ts=ts; assert !ts._open; } + + @Override boolean _upgradeType(java.util.HashMap<String,Type> types) { + if( _ts==null ) return false; + TypeStruct ts = (TypeStruct)_ts.upgradeType(types); + if( ts==_ts ) return false; + unlock(); + _ts = ts; + return true; + } + + @Override public String label() { return _ts==null ? "STRUCT?" : _ts.str(); } + + @Override + protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { + if( _ts==null ) return p.p("STRUCT?"); + p.p(_ts._name).p(" {"); + for( int i=0; i<nIns(); i++ ) { + p.p(_ts._fields[i]._fname).p(":"); + p.p(in(i)==null ? Type.BOTTOM : in(i)._type); + p.p("; "); + } + return p.unchar("; ").p("}"); + } + + @Override + public TypeStruct compute() { + if( _ts==null ) return TypeStruct.BOT; + Field[] fs = new Field[_ts._fields.length]; + for( int i=0; i<fs.length; i++ ) + fs[i] = _ts._fields[i].makeFrom(in(i)==null ? Type.TOP : in(i)._type); + return TypeStruct.make(_ts._name,false,fs); + } + + @Override + public Node idealize() { return null; } + + @Override + public boolean eq(Node n) { return _ts == ((StructNode)n)._ts; } + + @Override + int hash() { return _ts==null ? 0 : _ts.hashCode(); } +} diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/SubFNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/SubFNode.java new file mode 100644 index 00000000..d6a908ca --- /dev/null +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/SubFNode.java @@ -0,0 +1,28 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFloat; + +import java.util.BitSet; + +public class SubFNode extends ArithFNode { + public SubFNode(Node lhs, Node rhs) { super(lhs, rhs); } + public SubFNode(ArithNode n) { super(n); } + + @Override public String label() { return "SubF"; } + + @Override public String glabel() { return "-"; } + + @Override double doOp( double x, double y ) { return x-y; } + + @Override + public Node idealize() { + // Sub of 0. + Type t2 = in(2)._type; + if ( t2.isConstant() && t2 instanceof TypeFloat i && i.value()==0 ) + return in(1); + + return null; + } + @Override Node copy(Node lhs, Node rhs) { return new SubFNode(lhs,rhs); } +} diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/SubNode.java index 8eb1f4cc..c47ffb3b 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -9,7 +9,6 @@ public class SubNode extends ArithNode { @Override boolean allowFloat() { return true; } public SubNode(Node lhs, Node rhs) { super(null, lhs, rhs); } public SubNode(Node lhs, Node rhs, byte mode) { super(null, lhs, rhs,mode); } - @Override public Tag serialTag() { return Tag.Sub; } @Override public String op() { return "-"; } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java index 151a326e..840a95ec 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java @@ -8,7 +8,8 @@ import com.seaofnodes.simple.type.TypeInteger; public class ToFloatNode extends Node { public ToFloatNode(Node lhs) { super(null, lhs); } - @Override public Tag serialTag() { return Tag.ToFloat; } + + @Override public String label() { return "ToFloat"; } @Override protected String format() { return "(flt)%1"; } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java index 33806300..df740202 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java @@ -7,7 +7,6 @@ import com.seaofnodes.simple.type.TypeInteger; // Truncate toward zero, saturating overflow; NaN converts to zero. public class ToIntegerNode extends Node { public ToIntegerNode(Node lhs) { super(null,lhs); } - @Override public Tag serialTag() { return Tag.ToInteger; } @Override public String label() { return "ToInteger"; } @Override protected String format() { return "(int)%1"; } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/TypeNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/TypeNode.java deleted file mode 100644 index 3ab2d6f9..00000000 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/TypeNode.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.BAOS; -import com.seaofnodes.simple.util.Utils; -import java.util.BitSet; -import java.util.HashMap; - -// Node carries an extra internal type -public abstract class TypeNode extends Node { - public Type _con; - public TypeNode(Type t, Node... nodes) { super(nodes); assert t!=null; _con = t; } - public TypeNode(TypeNode tn) { super(tn); _con = tn._con; } - // Copy edges from ideal, but 'this' is a Type-Mach-Node (e.g. MemOpX86) and needs a _con - public TypeNode(Node ideal, Type con) { super(ideal); _con = con; } - - // Upgrade the internal type - @Override boolean _upgradeType( HashMap<String,Type> TYPES) { - return liftType(_con.upgradeType(TYPES)); - } - - // Lift type - public boolean liftType( Type t ) { - if( t == _con ) return false; - unlock(); // Unlock before changing _con - _con = t; - return true; - } - - @Override public boolean eq(Node n) { return _con==((TypeNode)n)._con; } - @Override int hash() { return _con.hashCode(); } -} diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java index 32e1deb9..477b9af8 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java @@ -8,7 +8,7 @@ import com.seaofnodes.simple.type.Type; public class XCtrlNode extends CFGNode { public XCtrlNode() { super(new Node[]{CodeGen.CODE._start}); } - @Override public Tag serialTag() { return Tag.XCtrl; } + @Override public String label() { return "Xctrl"; } @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p("Xctrl"); } @Override public boolean isConst() { return true; } @Override public Type compute() { return Type.XCONTROL; } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/XorNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/XorNode.java index c825f396..e3dac2d0 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/XorNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/XorNode.java @@ -6,7 +6,6 @@ import com.seaofnodes.simple.type.TypeInteger; public class XorNode extends ArithNode { public XorNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs, (byte)1); } - @Override public Tag serialTag() { return Tag.Xor; } @Override public String op() { return "^"; } @Override long doOp( long x, long y ) { return x ^ y; } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java index 31bbde86..127f17ff 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java @@ -9,11 +9,9 @@ import com.seaofnodes.simple.util.Utils; // Jump on flags, uses flags public class BranchARM extends IfNode implements MachNode, RIPRelSize { String _bop; - final boolean _fp; - BranchARM(IfNode iff, String bop, boolean fp ) { + BranchARM(IfNode iff, String bop ) { super(iff); _bop = bop; - _fp = fp; } @Override public String op() { return "j"+_bop; } @Override public String label() { return op(); } @@ -38,7 +36,7 @@ public class BranchARM extends IfNode implements MachNode, RIPRelSize { // by comparison (or sub). No need for regs because it uses flags if( in(1)!=null ) { // B.cond - enc.add4( Arm64.b_cond(Arm64.OP_BRANCH, 0, Arm64.make_condition(_bop, _fp)) ); + enc.add4( Arm64.b_cond(Arm64.OP_BRANCH, 0, Arm64.make_condition(_bop)) ); } else { if( _bop=="!=" ) return; // Inverted, no code enc.add4(Arm64.b(Arm64.OP_UJMP, 0)); @@ -58,7 +56,7 @@ public class BranchARM extends IfNode implements MachNode, RIPRelSize { @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { assert !( in(1)==null && _bop=="!=" ); // Inverted never-node, no code no patch if( opLen==4 ) { - enc.patch4(opStart,Arm64.b_cond(Arm64.OP_BRANCH, delta, Arm64.make_condition(_bop, _fp))); + enc.patch4(opStart,Arm64.b_cond(Arm64.OP_BRANCH, delta, Arm64.make_condition(_bop))); } else { throw Utils.TODO(); } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java index 4de28f3a..b6d82f09 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java @@ -14,7 +14,8 @@ public class CallARM extends CallNode implements MachNode, RIPRelSize { _inputs.pop(); // Pop constant target assert tfp.isConstant(); _tfp = tfp; - _name = CodeGen.CODE.funcName(tfp.fidx()); + FunNode fun = CodeGen.CODE.link(tfp); + _name = fun==null ? CodeGen.CODE.externFunc(tfp) : fun._name; } @Override public String op() { return "call"; } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/FltARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java index db2540f7..dc21e075 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/FltARM.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java @@ -4,22 +4,21 @@ import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.ConstantNode; import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.type.TypeFloat; import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; //FMOV (scalar, immediate) //Floating-point move immediate. -public class FltARM extends ConstantNode implements MachNode, RIPRelSize { - final String _ext; - FltARM( ConstantNode con, String ext ) { super(con); _ext = ext; } +public class FloatARM extends ConstantNode implements MachNode, RIPRelSize { + FloatARM(ConstantNode con) { super(con); } @Override public String op() { return "ld8"; } @Override public RegMask regmap(int i) { return null; } @Override public RegMask outregmap() { return arm.DMASK; } @Override public boolean isClone() { return true; } - @Override public FltARM copy() { return new FltARM(this,_ext); } + @Override public FloatARM copy() { return new FloatARM(this); } @Override public void encoding( Encoding enc ) { - if( _ext!=null ) throw Utils.TODO(); enc.largeConstant(this,_con,0,-1/*TODO: ARM-style ELF patching*/); short dst = (short)(enc.reg(this) - arm.D_OFFSET); enc.add4(Arm64.load_pc(Arm64.OPF_ARM, 0, dst)); diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java index 72b76edd..7a9e74c6 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java @@ -7,22 +7,19 @@ import com.seaofnodes.simple.node.MachNode; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; // Integer constants public class IntARM extends ConstantNode implements MachNode { - final String _ext; - IntARM( ConstantNode con, String ext ) { super(con); _ext = ext; } + IntARM( ConstantNode con ) { super(con); } @Override public String op() { return "ldi"; } @Override public RegMask regmap(int i) { return null; } @Override public RegMask outregmap() { return arm.WMASK; } @Override public boolean isClone() { return true; } - @Override public IntARM copy() { return new IntARM(this,_ext); } + @Override public IntARM copy() { return new IntARM(this); } @Override public void encoding( Encoding enc ) { - if( _ext!=null ) throw Utils.TODO(); short self = enc.reg(this); long x = _con==Type.NIL ? 0 : ((TypeInteger)_con).value(); Arm64.constant(enc,self,x); diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java index 53494092..8fc22a67 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java @@ -25,7 +25,7 @@ public class LoadARM extends MemOpARM { // ldr(immediate - unsigned offset) | ldr(register) @Override public void encoding( Encoding enc ) { - ldst_encode(enc, OP_LOADS[Integer.numberOfTrailingZeros(size())], OP_LOAD_RS[_con.log_size()], this, size()); + ldst_encode(enc, OP_LOADS[Integer.numberOfTrailingZeros(size())], OP_LOAD_RS[_declaredType.log_size()], this, size()); } @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)).p(","); diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java index 14eaf510..dfbe5e06 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java @@ -7,13 +7,13 @@ import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.util.Utils; import java.lang.StringBuilder; public abstract class MemOpARM extends MemOpNode implements MachNode { final int _off; // Limit 9 bits sized, or (13 bits<<logsize) unsigned final int _imm; // Limit ? bits - final int _bytes; - final char _sz; + final char _sz = (char)('0'+(1<<_declaredType.log_size())); MemOpARM(MemOpNode mop, Node ptr, Node idx, int off, int imm) { super(mop,mop); assert ptr._type instanceof TypeMemPtr && !(ptr instanceof AddNode); @@ -22,8 +22,6 @@ public abstract class MemOpARM extends MemOpNode implements MachNode { _inputs.setX(3, idx); _off = off; _imm = imm; - _bytes = mop instanceof StoreNode st ? st.storeSize() : 1<<_con.log_size(); - _sz = (char)('0'+_bytes); } // Store-based flavors have a value edge @@ -37,10 +35,10 @@ public abstract class MemOpARM extends MemOpNode implements MachNode { @Override protected ExprPrinter<Node> _printMach(ExprPrinter<Node> p) { return p.p(".").p(_name); } - @Override public Type compute() { throw new AssertionError("Do not call compute on "+getClass().getSimpleName()); } - @Override public Node idealize() { throw new AssertionError("Do not call idealize on "+getClass().getSimpleName()); } + @Override public Type compute() { throw Utils.TODO(); } + @Override public Node idealize() { throw Utils.TODO(); } - int size() { return _bytes; } + int size() { return 1<<_declaredType.log_size(); } // Wider mask to store both GPRs and FPRs @Override public RegMask regmap(int i) { diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java index 8bb8983e..7110df62 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java @@ -32,8 +32,7 @@ public class NewARM extends NewNode implements MachNode, RIPRelSize { } // General form: "alloc #bytes PC" @Override public void asm(CodeGen code, SB sb) { - sb.p("\n"); // Multi-op trigger - sb.p("ldi ").p(code._mach.regs()[_arg2Reg]).p(" = #1\n"); + sb.p("ldi x0=#1\n"); sb.p("call #calloc"); } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java index e5b06c39..afbbd3bf 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java @@ -9,14 +9,12 @@ import com.seaofnodes.simple.util.SB; // e.g CSET, stores flag info into GPRS after compare public class SetARM extends MachConcreteNode implements MachNode { final String _bop; // One of <,<=,== - final boolean _fp; // FP compare (unsigned encoding) - SetARM(Node cmp, String bop, boolean fp) { + SetARM(Node cmp, String bop) { super(cmp); _inputs.setLen(1); // Pop the cmp inputs // Replace with the matched cmp _inputs.push(cmp); _bop = bop; - _fp = fp; } @Override public String op() { return "set"+_bop; } @Override public RegMask regmap(int i) { assert i==1; return arm.FLAGS_MASK; } @@ -24,7 +22,7 @@ public class SetARM extends MachConcreteNode implements MachNode { @Override public void encoding( Encoding enc ) { // Using RM==RN==31 implies a *zero* value, not R31. - int body = Arm64.cset(Arm64.OP_CSET,0b11111, Arm64.make_condition(_bop, _fp), 0b011111, enc.reg(this)); + int body = Arm64.cset(Arm64.OP_CSET,0b11111, Arm64.make_condition(_bop), 0b011111, enc.reg(this)); enc.add4(body); } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java index dcfeb5d8..f93f0f24 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java @@ -9,7 +9,7 @@ import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; public class SplitARM extends SplitNode { - SplitARM(LRG lrg, String kind, byte round) { super(lrg,kind,round,new Node[2]);} + SplitARM(String kind, byte round) { super(kind,round,new Node[2]);} @Override public RegMask regmap(int i) { return arm.SPLIT_MASK; } @Override public RegMask outregmap() { return arm.SPLIT_MASK; } @@ -25,7 +25,7 @@ public class SplitARM extends SplitNode { if(src >= arm.MAX_REG) { throw Utils.TODO(); } - int off = fun(enc).computeStackOffset(enc._code,dst); + int off = enc._fun.computeStackOffset(enc._code,dst); if( srcX ) src -= arm.D_OFFSET; enc.add4(Arm64.load_str_imm(Arm64.OP_STORE_IMM_64, off, arm.RSP, src, 8)); return; @@ -33,7 +33,7 @@ public class SplitARM extends SplitNode { if(src >= arm.MAX_REG) { // Load from SP - int off = fun(enc).computeStackOffset(enc._code,src); + int off = enc._fun.computeStackOffset(enc._code,src); if( dstX ) dst -= arm.D_OFFSET; enc.add4(Arm64.load_str_imm(Arm64.OP_LOAD_IMM_64, off, arm.RSP, dst, 8)); return; @@ -57,4 +57,10 @@ public class SplitARM extends SplitNode { enc.add4(Arm64.f_mov_general(Arm64.OP_FMOV, 0b01, 0, 0b110, src - arm.D_OFFSET, dst)); } } + + // General form: "mov dst = src" + @Override public void asm(CodeGen code, SB sb) { + FunNode fun = code._encoding==null ? null : code._encoding._fun; + sb.p(code.reg(this,fun)).p(" = ").p(code.reg(in(1),fun)); + } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitFlagsARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitFlagsARM.java index 25e5d501..3e045a6f 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitFlagsARM.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitFlagsARM.java @@ -6,7 +6,7 @@ import com.seaofnodes.simple.codegen.*; // NZCV transfers require a GPR. Further splits can spill that GPR to the stack. public class SplitFlagsARM extends SplitARM { private static final RegMask MASK = new RegMask(arm.WR_BITS,1L); - SplitFlagsARM(LRG lrg,String kind,byte round) { super(lrg,kind,round); } + SplitFlagsARM(String kind,byte round) { super(kind,round); } @Override public boolean isNonStackSplit() { return true; } @Override public RegMask regmap(int i) { return MASK; } @Override public RegMask outregmap() { return MASK; } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitGPRARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitGPRARM.java index c9b2127a..46eff4ca 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitGPRARM.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitGPRARM.java @@ -5,7 +5,7 @@ import com.seaofnodes.simple.codegen.*; // Keep a saved NZCV value in GPRs and stack slots through subsequent spills. public class SplitGPRARM extends SplitARM { private static final RegMask MASK = new RegMask(arm.WR_BITS,-2L); - SplitGPRARM(LRG lrg,String kind,byte round) { super(lrg,kind,round); } + SplitGPRARM(String kind,byte round) { super(kind,round); } @Override public RegMask regmap(int i) { return MASK; } @Override public RegMask outregmap() { return MASK; } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java index 0b90ac8b..2cc54f76 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java @@ -28,7 +28,7 @@ public class StoreARM extends MemOpARM { private static final int[] OP_STORE_RS = new int[]{ Arm64.OP_STORE_R_8, Arm64.OP_STORE_R_16, Arm64.OP_STORE_R_32, Arm64.OP_STORE_R_64, }; @Override public void encoding( Encoding enc ) { - ldst_encode(enc, OP_STORES[Integer.numberOfTrailingZeros(size())], OP_STORE_RS[Integer.numberOfTrailingZeros(_bytes)], val(), size()); + ldst_encode(enc, OP_STORES[Integer.numberOfTrailingZeros(size())], OP_STORE_RS[_declaredType.log_size()], val(), size()); } @Override public void asm(CodeGen code, SB sb) { asm_address(code,sb).p(","); diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java index a053ba46..2c41dc57 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java @@ -2,30 +2,28 @@ package com.seaofnodes.simple.node.cpus.arm; import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.ConstantNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.Node; import com.seaofnodes.simple.type.TypeFunPtr; import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; -public class TFPARM extends FunPtrNode implements MachNode, RIPRelSize { - final String _ext; - // Pointer to a Simple function - TFPARM( FunPtrNode fptr ) { super(fptr); _ext = null; } - // Pointer to an Extern "C" function - TFPARM( TypeFunPtr fptr, String ext ) { super(fptr,CodeGen.CODE._start,null); _type = fptr; _ext = ext; } +public class TFPARM extends ConstantNode implements MachNode, RIPRelSize { + TFPARM( ConstantNode con ) { super(con); } @Override public String op() { return "ldx"; } @Override public RegMask regmap(int i) { return null; } @Override public RegMask outregmap() { return arm.WMASK; } @Override public boolean isClone() { return true; } - @Override public TFPARM copy() { return new TFPARM((TypeFunPtr)_con,_ext); } + @Override public TFPARM copy() { return new TFPARM(this); } @Override public void encoding( Encoding enc ) { - if( _ext==null ) enc.relo(this); // Internal patch - else enc.external(this,_ext); // ELF-file external patch - short dst = enc.reg(this); + enc.relo(this); + short self = enc.reg(this); // adrp x0, 0 - enc.add4(Arm64.adrp(1,0, Arm64.OP_ADRP, 0,dst)); + int adrp = Arm64.adrp(1,0, Arm64.OP_ADRP, 0,self); // add x0, x0, 0 - arm.imm_inst(enc,Arm64.OPI_ADD,0, dst); + enc.add4(adrp); + arm.imm_inst(enc,Arm64.OPI_ADD,0, 0); } @Override public byte encSize(int delta) { @@ -36,7 +34,16 @@ public class TFPARM extends FunPtrNode implements MachNode, RIPRelSize { @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { short rpc = enc.reg(this); if(opLen == 8 ) { - arm.patch_adrp_add(enc, opStart, delta, rpc); + // ARM encoding delta is from PC & 0xFFF + int target = opStart+delta; + int base = opStart & ~0xFFF; + delta = target-base; + int adrp_delta = delta >> 12; + // ADRP splits a signed page offset into two unsigned bit fields. + // Mask the high field too, including for an address below this PC. + enc.patch4(opStart, Arm64.adrp(1, adrp_delta & 0b11, 0b10000, (adrp_delta >> 2) & 0x7FFFF, rpc)); + // low 12 bits via add + enc.patch4(opStart+4, Arm64.imm_inst_l(Arm64.OPI_ADD, delta & 0xfff, rpc)); } else { // should not happen as one instruction is 4 byte, and TFP arm encodes 2. throw Utils.TODO(); @@ -45,7 +52,7 @@ public class TFPARM extends FunPtrNode implements MachNode, RIPRelSize { @Override public void asm(CodeGen code, SB sb) { String reg = code.reg(this); - _type.print(sb.p(reg).p(" #")); + _con.print(sb.p(reg).p(" #")); } @Override public boolean eq(Node n) { return this==n; } } 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 b2531f97..d1cafa96 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 @@ -10,16 +10,14 @@ import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; public class TMPARM extends ConstantNode implements MachNode, RIPRelSize { - final String _ext; - TMPARM( ConstantNode con, String ext ) { super(con); _ext = ext; } + TMPARM( ConstantNode con ) { super(con); } @Override public String op() { return "ldp"; } @Override public RegMask regmap(int i) { return null; } @Override public RegMask outregmap() { return arm.WMASK; } @Override public boolean isClone() { return true; } - @Override public TMPARM copy() { return new TMPARM(this,_ext); } + @Override public TMPARM copy() { return new TMPARM(this); } @Override public void encoding( Encoding enc ) { - if( _ext!=null ) enc.externalData(this,_ext); - else enc.largeConstant(this,((TypeMemPtr)_con)._obj,0,-1); + enc.largeConstant(this,((TypeMemPtr)_con)._obj,0,-1); short dst = enc.reg(this); // adrp x0, 0 enc.add4(Arm64.adrp(1,0, Arm64.OP_ADRP, 0,dst)); @@ -35,7 +33,15 @@ public class TMPARM extends ConstantNode implements MachNode, RIPRelSize { @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { short dst = enc.reg(this); if(opLen == 8 ) { - arm.patch_adrp_add(enc, opStart, delta, dst); + // ARM encoding delta is from PC & 0xFFF + int target = opStart+delta; + int base = opStart & ~0xFFF; + delta = target-base; + int adrp_delta = delta >> 12; + // patch upper 20 bits via adrp + enc.patch4(opStart, Arm64.adrp(1, adrp_delta & 0b11, 0b10000, adrp_delta >> 2, dst)); + // low 12 bits via add + enc.patch4(opStart+4, Arm64.imm_inst_l(Arm64.OPI_ADD, delta & 0xfff, dst)); } else { throw Utils.TODO(); } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java index 3c965da5..0d5b5440 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java @@ -212,14 +212,6 @@ public class arm extends Machine { enc.add4(body); } - public static void patch_adrp_add(Encoding enc, int opStart, int delta, int rd) { - int target = opStart + delta; - int pageDelta = ((target & ~0xFFF) - (opStart & ~0xFFF)) >> 12; - int imm21 = pageDelta & 0x1FFFFF; - enc.patch4(opStart , Arm64.adrp(1, imm21 & 3, OP_ADRP, imm21 >>> 2, rd)); - enc.patch4(opStart+4, Arm64.imm_inst_l(OPI_ADD, target & 0xFFF, rd)); - } - public static void f_cmp(Encoding enc, Node n) { short reg1 = (short)(enc.reg(n.in(1))-D_OFFSET); short reg2 = (short)(enc.reg(n.in(2))-D_OFFSET); @@ -233,16 +225,10 @@ public class arm extends Machine { if( idx==1 ) return null; // Count floats in signature up to index if( idx-2 >= tfp.nargs() ) return null; // Anti-dependence - boolean hidden = hiddenSelf(tfp); - int sigidx = idx-2; - if( hidden ) { - if( sigidx==0 ) return null; - sigidx--; - } // Count floats in signature up to index int fcnt=0; - for( int i=hidden ? 1 : 0; i<idx-2; i++ ) - if( tfp.arg(i) instanceof TypeFloat) + for( int i=2; i<idx; i++ ) + if( tfp.arg(i-2) instanceof TypeFloat) fcnt++; // Floats up to XMMS in XMM registers if( tfp.arg(idx-2) instanceof TypeFloat ) { @@ -250,27 +236,24 @@ public class arm extends Machine { return XMMS[fcnt]; } else { RegMask[] cargs = CALLINMASK; - if( sigidx-fcnt < cargs.length ) - return cargs[sigidx-fcnt]; + if( idx-2-fcnt < cargs.length ) + return cargs[idx-2-fcnt]; } // Pass on stack slot (8 and higher) if( maxArgSlot>0 ) throw Utils.TODO(); - return new RegMask(MAX_REG + 1 + sigidx); + return new RegMask(MAX_REG + 1 + (idx - 2)); } // Return the max stack slot used by this signature, or 0 @Override public short maxArgSlot( TypeFunPtr tfp ) { int icnt=0, fcnt=0; // Count of ints, floats - for( int i=hiddenSelf(tfp) ? 1 : 0; i<tfp.nargs(); i++ ) { + for( int i=0; i<tfp.nargs(); i++ ) { if( tfp.arg(i) instanceof TypeFloat ) fcnt++; else icnt++; } int nstk = Math.max(icnt-8,0)+Math.max(fcnt-8,0); return (short)nstk; } - private static boolean hiddenSelf(TypeFunPtr tfp) { - return tfp.nargs() > 0 && tfp.arg(0) == TypePtr.PTR; - } private static final long CALLEE_SAVE = 1L<<X19 | @@ -288,15 +271,15 @@ public class arm extends Machine { @Override public int rpc() { return X30; } // Create a split op; any register to any register, including stack slots - @Override public SplitNode split(LRG lrg, String kind, byte round) { + @Override public SplitNode split(String kind, byte round, LRG lrg) { RegMask def = lrg._machDef==null ? null : lrg._machDef.outregmap(), use = lrg.useMask(); if( (def!=null && def.size1() && def.test(FLAGS)) || (use!=null && use.size1() && use.test(FLAGS)) ) - return new SplitFlagsARM(lrg,kind,round); + return new SplitFlagsARM(kind,round); // Spilling the saved flags requires a GPR between NZCV and the stack. if( lrg._machDef instanceof SplitFlagsARM || lrg._machDef instanceof SplitGPRARM ) - return new SplitGPRARM(lrg,kind,round); - return new SplitARM(lrg,kind,round); + return new SplitGPRARM(kind,round); + return new SplitARM(kind,round); } // Return a MachNode unconditional branch @@ -307,34 +290,30 @@ public class arm extends Machine { // Instruction selection @Override public Node instSelect(Node n ) { return switch( n ) { + case AddFNode addf -> new AddFARM(addf); case AddNode add -> add(add); case AndNode and -> and(and); case BoolNode bool -> cmp(bool); case CallNode call -> call(call); - case PtrToIntNode ptr -> new PtrToIntMach(ptr); - case GuardNode guard -> new GuardMach(guard); + case CastNode cast -> new CastMach(cast); case CallEndNode cend-> new CallEndMach(cend); - case CLInitNode init -> new CLInitNode(init); case CProjNode c -> new CProjNode(c); case ConstantNode con-> con(con); - case DivNode div -> div.mode()==2 ? new DivFARM(div) : new DivARM(div); - case EscapeNode esc -> new EscapeNode(esc); + case DivFNode divf -> new DivFARM(divf); + case DivNode div -> new DivARM(div); case FunNode fun -> new FunARM(fun); - case FunPtrNode fptr -> fptr(fptr); case IfNode iff -> jmp(iff); case LoadNode ld -> ld(ld); case MemMergeNode mem-> new MemMergeNode(mem); - case MinusNode neg -> { - if( neg.mode()==2 ) throw Utils.TODO(); - yield new NegARM(neg); - } - case MulNode mul -> mul.mode()==2 ? new MulFARM(mul) : new MulARM(mul); + case MinusNode neg -> new NegARM(neg); + case MulFNode mulf -> new MulFARM(mulf); + case MulNode mul -> new MulARM(mul); case NewNode nnn -> new NewARM(nnn); case NotNode not -> new NotARM(not); case OrNode or -> or(or); case ParmNode parm -> new ParmARM(parm); case BulkMemPhiNode phi -> new BulkMemPhiNode(phi); - case MemPhiNode phi -> new MemPhiNode(phi); + case MemPhiNode phi -> new MemPhiNode(phi); case PhiNode phi -> new PhiNode(phi); case ProjNode prj -> prj(prj); case ReadOnlyNode read -> new ReadOnlyMach(read); @@ -342,12 +321,11 @@ public class arm extends Machine { case SarNode sar -> asr(sar); case ShlNode shl -> lsl(shl); case ShrNode shr -> lsr(shr); - case StartCUNode start -> new StartCUNode(start); - case StopCUNode stop -> new StopCUNode(stop); case StartNode start -> new StartNode(start); case StopNode stop -> new StopNode(stop); case StoreNode st -> st(st); - case SubNode sub -> sub.mode()==2 ? new SubFARM(sub) : sub(sub); + case SubFNode subf -> new SubFARM(subf); + case SubNode sub -> sub(sub); case ToFloatNode tfn -> new I2F8ARM(tfn); case ToIntegerNode cvt -> new F8ToIARM(cvt); case XorNode xor -> xor(xor); @@ -360,7 +338,7 @@ public class arm extends Machine { private Node cmp(BoolNode bool){ Node cmp = _cmp(bool); - return new SetARM(cmp, bool.op(), cmp instanceof CmpFARM); + return new SetARM(cmp, bool.op()); } private Node _cmp(BoolNode bool) { if( bool.isFloat() ) @@ -379,18 +357,13 @@ public class arm extends Machine { // Most general arith ops will also set flags, which the Jmp needs directly. // Loads do not set the flags, and will need an explicit TEST String op = "!="; - boolean fp = false; - if( iff.in(1) instanceof BoolNode bool ) { op = bool.op(); fp = bool.isFloat(); } + if( iff.in(1) instanceof BoolNode bool ) op = bool.op(); else if( iff.in(1)==null ) op = "=="; // Never-node cutout - else iff.setDef(1, new BoolNode.NE(iff.in(1), - ConstantNode.raw(TypeInteger.ZERO), - (byte)(iff.in(1)._type instanceof TypeFloat ? 2 : 1))); - return new BranchARM(iff, op, fp); + else iff.setDef(1, new BoolNode.NE(iff.in(1), new ConstantNode(TypeInteger.ZERO),(byte)1)); + return new BranchARM(iff, op); } private Node add(AddNode add) { - if( add.mode()==2 ) - return new AddFARM(add); return add.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && imm12(ti) ? new AddIARM(add, (int)ti.value()) : new AddARM(add); @@ -403,30 +376,22 @@ public class arm extends Machine { } private Node con( ConstantNode con ) { - if( !con._con.isConstant() ) - return ConstantNode.raw(con); // Default unknown caller inputs - String ext = con instanceof ExternNode ext0 ? ext0._extern : null; + if( !con._con.isConstant() ) return new ConstantNode( con ); // Default unknown caller inputs return switch( con._con ) { - case TypeInteger ti -> new IntARM(con,ext); - case TypeFloat tf -> new FltARM(con,ext); - case TypeMemPtr tmp -> new TMPARM(con,ext); - case TypeFunPtr tfp -> new TFPARM(tfp,ext); + case TypeInteger ti -> new IntARM(con); + case TypeFloat tf -> new FloatARM(con); + case TypeFunPtr tfp -> new TFPARM(con); + case TypeMemPtr tmp -> new TMPARM(con); case TypeNil tn -> throw Utils.TODO(); // TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code. - case Type t -> t==Type.NIL ? new IntARM(con,ext) : ConstantNode.raw(con); + case Type t -> t==Type.NIL ? new IntARM(con) : new ConstantNode(con); }; } - private Node fptr( FunPtrNode con ) { - return new TFPARM(con); - } - private Node call(CallNode call){ - return call.fptr() instanceof FunPtrNode fptr - ? new CallARM(call, (TypeFunPtr)fptr._type) - : call.fptr() instanceof ConstantNode con - ? new CallARM(call, (TypeFunPtr)con._type) - : new CallRRARM(call); + return call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp + ? new CallARM(call, tfp) + : new CallRRARM(call); } private Node or(OrNode or) { @@ -468,13 +433,6 @@ public class arm extends Machine { : new LsrARM(lsr); } - private Node prj(ProjNode prj) { - if( prj.in(0) instanceof CLInitNode ) return new ProjNode(prj); - TypeInteger ret = cReturn(prj); - if( ret!=null ) return new ExtendARM(prj,ret); - return prj.in(0) instanceof StartNode ? new ProjNode(prj) : new ProjARM(prj); - } - private static int off; private static Node idx; private Node st(StoreNode st) { @@ -500,4 +458,9 @@ public class arm extends Machine { return mop; } + private Node prj(ProjNode prj) { + TypeInteger ret = cReturn(prj); + return ret==null ? new ProjARM(prj) : new ExtendARM(prj,ret); + } + } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java index 59bad64e..14581111 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java @@ -2,19 +2,19 @@ package com.seaofnodes.simple.node.cpus.riscv; import com.seaofnodes.isa.RiscV; import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.ConstantNode; import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.FunPtrNode; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; // Add upper 20bits to PC. Immediate comes from the relocation info. -public class AUIPC extends FunPtrNode implements MachNode, RIPRelSize { - AUIPC( FunPtrNode fptr ) { super(fptr); } +public class AUIPC extends ConstantNode implements MachNode, RIPRelSize { + AUIPC( Type tfp ) { super(tfp); } @Override public RegMask regmap(int i) { return null; } @Override public RegMask outregmap() { return riscv.WMASK; } @Override public boolean isClone() { return true; } - @Override public AUIPC copy() { return new AUIPC(this); } + @Override public AUIPC copy() { return new AUIPC(_con); } @Override public String op() { return "auipc"; } @Override public void encoding( Encoding enc ) { enc.relo(this); @@ -37,6 +37,7 @@ public class AUIPC extends FunPtrNode implements MachNode, RIPRelSize { @Override public void asm(CodeGen code, SB sb) { String reg = code.reg(this); sb.p(reg).p(" = PC+#"); - _type.print(sb); + if( _con == null ) sb.p("---"); + else _con.print(sb); } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java index b1426a3f..03bedd0d 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java @@ -15,7 +15,8 @@ public class CallRISC extends CallNode implements MachNode, RIPRelSize { assert tfp.isConstant(); _inputs.pop(); // Pop constant target _tfp = tfp; - _name = CodeGen.CODE.funcName(tfp.fidx()); + FunNode fun = CodeGen.CODE.link(tfp); + _name = fun==null ? CodeGen.CODE.externFunc(tfp) : fun._name; } @Override public String op() { return "call"; } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java index cda4bc79..5eee596d 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java @@ -14,7 +14,7 @@ public class CallRRISC extends CallNode implements MachNode { ? riscv.RMASK // Function call target : riscv.callInMask(tfp(),i,fun()._maxArgSlot); // Normal argument } - @Override public RegMask outregmap() { return riscv.RPC_MASK; } + @Override public RegMask outregmap() { return null; } @Override public void encoding( Encoding enc ) { short rpc = enc.reg(this); diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java index a110d156..51ddc9db 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java @@ -9,13 +9,12 @@ import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; public class FltRISC extends ConstantNode implements MachNode, RIPRelSize { - final String _ext; - FltRISC( ConstantNode con, String ext ) { super(con); _ext = ext; } + FltRISC(ConstantNode con) { super(con); } @Override public String op() { return "ld8"; } @Override public RegMask regmap(int i) { return null; } @Override public RegMask outregmap() { return riscv.FMASK; } @Override public boolean isClone() { return true; } - @Override public FltRISC copy() { return new FltRISC(this,_ext); } + @Override public FltRISC copy() { return new FltRISC(this); } @Override public void encoding( Encoding enc ) { enc.largeConstant(this,_con, 0, -1); diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java index dd4a02a3..23ac8481 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java @@ -5,23 +5,21 @@ import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.ConstantNode; import com.seaofnodes.simple.node.MachNode; import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; // Special instruction for loading 8 byte constants from the constant pool. public class Int8RISC extends ConstantNode implements MachNode, RIPRelSize { - final String _ext; - Int8RISC( ConstantNode con, String ext ) { super(con); _ext = ext; } + public Int8RISC(ConstantNode con) { super(con); } + @Override public String op() { return "ld8"; } @Override public RegMask regmap(int i) { return null; } @Override public RegMask outregmap() { return riscv.WMASK; } @Override public boolean isClone() { return true; } - @Override public Int8RISC copy() { return new Int8RISC(this,_ext); } + @Override public Int8RISC copy() { return new Int8RISC(this); } @Override public byte encSize(int delta) { return 8; } @Override public void encoding( Encoding enc ) { - if( _ext!=null ) throw Utils.TODO(); short dst = enc.reg(this); short tmp = (short)riscv.T6; enc.largeConstant(this,_con, 0, -1); diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java index 35d2bc65..9088abbd 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java @@ -11,7 +11,7 @@ import com.seaofnodes.simple.util.SB; // 12-bit integer constant. Larger constants are made up in the instruction // selection by adding with a LUI. public class IntRISC extends ConstantNode implements MachNode { - IntRISC(ConstantNode con) { super(con); } + public IntRISC(ConstantNode con) { super(con); } @Override public String op() { return "ldi"; } @Override public RegMask regmap(int i) { return null; } @Override public RegMask outregmap() { return riscv.WMASK; } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java index e9f3ec02..2e3dfd8d 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java @@ -3,9 +3,7 @@ package com.seaofnodes.simple.node.cpus.riscv; import com.seaofnodes.isa.RiscV; import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.*; import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; // Load memory addressing on RISC // Support imm, reg(direct), or reg+off(indirect) addressing @@ -24,39 +22,6 @@ public class LoadRISC extends MemOpRISC { if( dst >= riscv.F_OFFSET ) dst -= riscv.F_OFFSET; enc.add4(RiscV.i_type(op, dst, func3(), ptr, _off)); } - @Override int func3() { - int func3 = -1; - // no unsigned flavour for store, so both signed and unsigned trigger the same - Type declType = _con; - if( declType == TypeInteger. I8 ) func3=0; // LB - if( declType == TypeInteger.I16 ) func3=1; // LH - if( declType == TypeInteger.I32 ) func3=2; // LW - if( declType == TypeInteger.BOT ) func3=3; // LD - if( declType == TypeInteger. U8 ) func3=4; // LBU - if( declType == TypeInteger.BOOL) func3=4; // LBU - if( declType == TypeInteger.U16 ) func3=5; // LHU - if( declType == TypeInteger.U32 ) func3=6; // LWU - if( declType instanceof TypeInteger ti ) { - if( -128 <= ti._min && ti._max < 128 ) func3 = 0; // LB - } - - // float - if( declType == TypeFloat.F32) func3 = 2; // fLW fSW - if( declType == TypeFloat.F64) func3 = 3; // fLD fSD - - if( declType instanceof TypeMemPtr) func3 = 3; // 8 byte pointers (pick ld) - if( declType instanceof TypeFunPtr) func3 = 3; // 8 byte function pointers - if( declType instanceof TypeConAry ary ) - func3 = switch( ary.log_size() ) { - case 0 -> 4; // LBU - case 1 -> 5; // LHU - case 2 -> 2; // LW - case 3 -> 3; // LD - default -> throw Utils.TODO("RISC-V constant-array load for "+declType); - }; - if( func3 == -1 ) throw Utils.TODO("RISC-V load for "+declType); - return func3; - } @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)).p(","); diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java index e0bbc219..adf21848 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java @@ -11,7 +11,6 @@ import com.seaofnodes.simple.type.*; public abstract class MemOpRISC extends MemOpNode implements MachNode { final int _off; // Limit 12 bits final char _sz; - final int _bytes; MemOpRISC(MemOpNode mop, Node base, int off, Node val) { super(mop,mop); assert base._type instanceof TypeMemPtr; @@ -19,8 +18,7 @@ public abstract class MemOpRISC extends MemOpNode implements MachNode { _inputs.setX(3, null); // Never an index _inputs.setX(4, val ); _off = off; - _bytes = mop instanceof StoreNode st ? st.storeSize() : 1<<_con.log_size(); - _sz = (char)('0'+_bytes); + _sz = (char)('0'+(1<<_declaredType.log_size())); } @Override public String label() { return op(); } @@ -28,11 +26,42 @@ public abstract class MemOpRISC extends MemOpNode implements MachNode { @Override protected ExprPrinter<Node> _printMach(ExprPrinter<Node> p) { return p.p(".").p(_name); } - @Override public Type compute() { throw new AssertionError("Do not call compute on "+getClass().getSimpleName()); } - @Override public Node idealize() { throw new AssertionError("Do not call idealize on "+getClass().getSimpleName()); } + @Override public Type compute() { throw Utils.TODO(); } + @Override public Node idealize() { throw Utils.TODO(); } // func3 is based on load/store size and extend - abstract int func3(); + int func3() { + int func3 = -1; + // no unsigned flavour for store, so both signed and unsigned trigger the same + if(this instanceof StoreRISC) { + if( _declaredType == TypeInteger. I8 || _declaredType == TypeInteger.U8 || _declaredType == TypeInteger.BOOL) func3=0; // SB + if( _declaredType == TypeInteger.I16 || _declaredType == TypeInteger.U16 ) func3=1; // SH + if( _declaredType == TypeInteger.I32 || _declaredType == TypeInteger.U32 ) func3=2; // SW + if( _declaredType instanceof TypeMemPtr) func3=3; // SD + if( _declaredType == TypeInteger.BOT ) func3=3; // SD + if( func3 == -1 ) throw Utils.TODO(); + return func3; + } + if( _declaredType == TypeInteger. I8 ) func3=0; // LB + if( _declaredType == TypeInteger.I16 ) func3=1; // LH + if( _declaredType == TypeInteger.I32 ) func3=2; // LW + if( _declaredType == TypeInteger.BOT ) func3=3; // LD + if( _declaredType == TypeInteger. U8 ) func3=4; // LBU + if( _declaredType == TypeInteger.BOOL) func3=4; // LBU + if( _declaredType == TypeInteger.U16 ) func3=5; // LHU + if( _declaredType == TypeInteger.U32 ) func3=6; // LWU + if( _declaredType instanceof TypeInteger ti ) { + if( -128 <= ti._min && ti._max < 128 ) func3 = 0; // LB + } + + // float + if( _declaredType == TypeFloat.F32) func3 = 2; // fLW fSW + if( _declaredType == TypeFloat.F64) func3 = 3; // fLD fSD + + if( _declaredType instanceof TypeMemPtr) func3 = 3; // 8 byte pointers (pick ld) + if( func3 == -1 ) throw Utils.TODO(); + return func3; + } // Register mask allowed on input i. @Override public RegMask regmap(int i) { diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java index 40d0359c..965a3c7c 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java @@ -41,9 +41,8 @@ public class NewRISC extends NewNode implements MachNode, RIPRelSize { // General form: "alloc #bytes PC" @Override public void asm(CodeGen code, SB sb) { - sb.p("\n"); // Multi-op trigger - sb.p("ldi ").p(code._mach.regs()[_arg2Reg]).p(" = #1\n"); + sb.p("ldi a1=#1\n"); sb.p("auipc a2=#calloc\n"); - sb.p("call a2+#calloc"); + sb.p("call a2+#calloc, a0"); } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java index d455337b..8a16f6b7 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java @@ -9,7 +9,7 @@ import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; public class SplitRISC extends SplitNode { - SplitRISC( LRG lrg, String kind, byte round ) { super(lrg, kind,round, new Node[2]); } + SplitRISC( String kind, byte round ) { super(kind,round, new Node[2]); } @Override public RegMask regmap(int i) { return riscv.SPLIT_MASK; } @Override public RegMask outregmap() { return riscv.SPLIT_MASK; } @Override public void encoding( Encoding enc ) { @@ -25,7 +25,7 @@ public class SplitRISC extends SplitNode { if( src >= riscv.MAX_REG ) { throw Utils.TODO(); // Very rare stack-stack move } - int off = fun(enc).computeStackOffset(enc._code,dst); + int off = enc._fun.computeStackOffset(enc._code,dst); int op = srcX ? RiscV.OP_STOREFP : RiscV.OP_STORE; if( srcX ) src -= riscv.F_OFFSET; enc.add4(RiscV.s_type(op, 0b011, riscv.RSP, src, off)); @@ -33,7 +33,7 @@ public class SplitRISC extends SplitNode { } if( src >= riscv.MAX_REG ) { // Load from SP - int off = fun(enc).computeStackOffset(enc._code,src); + int off = enc._fun.computeStackOffset(enc._code,src); int op = dstX ? RiscV.OP_LOADFP : RiscV.OP_LOAD; if( dstX ) dst -= riscv.F_OFFSET; enc.add4(RiscV.i_type(op, dst, 0b011, riscv.RSP, off)); @@ -58,4 +58,10 @@ public class SplitRISC extends SplitNode { enc.add4(RiscV.r_type(RiscV.OP_FP, dst, 0, src - riscv.F_OFFSET, 0, 0b1110001)); } } + + // General form: "mov dst = src" + @Override public void asm(CodeGen code, SB sb) { + FunNode fun = code._encoding==null ? null : code._encoding._fun; + sb.p(code.reg(this,fun)).p(" = ").p(code.reg(in(1),fun)); + } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java index c3fcf4d3..48cb7c78 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java @@ -4,7 +4,6 @@ import com.seaofnodes.isa.RiscV; import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.codegen.RegMask; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.*; import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; @@ -28,7 +27,7 @@ public class StoreRISC extends MemOpRISC { if( i==1 ) return null; // mem if( i==2 ) return riscv.RMASK; // ptr // 2 - index - if( i==4 ) return _bytes >= 4 + if( i==4 ) return _sz >= '4' ? riscv.MEM_MASK // Word/dword value in GPR or FPR : riscv.RMASK; // Byte/short stores require a GPR return null; // Anti-dependence @@ -43,11 +42,6 @@ public class StoreRISC extends MemOpRISC { enc.add4(RiscV.s_type(op, func3()&7, ptr, val == -1 ? riscv.ZERO : val, _off)); } - // func3 is based on load/store size and extend - @Override int func3() { - return Integer.numberOfTrailingZeros(_bytes); - } - @Override public void asm(CodeGen code, SB sb) { asm_address(code,sb).p(","); if( val()==null ) sb.p("#").p("0"); diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java index 44ef1a95..e57f6e09 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java @@ -2,27 +2,25 @@ package com.seaofnodes.simple.node.cpus.riscv; import com.seaofnodes.isa.RiscV; import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.ConstantNode; +import com.seaofnodes.simple.node.MachNode; +import com.seaofnodes.simple.node.Node; import com.seaofnodes.simple.type.TypeFunPtr; import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; -public class TFPRISC extends FunPtrNode implements MachNode, RIPRelSize { - final String _ext; - // Pointer to a Simple function - TFPRISC( FunPtrNode fptr ) { super(fptr); _ext = null; } - // Pointer to an Extern "C" function - TFPRISC( TypeFunPtr fptr, String ext ) { super(fptr,CodeGen.CODE._start,null); _type = fptr; _ext = ext; } +public class TFPRISC extends ConstantNode implements MachNode, RIPRelSize { + TFPRISC(ConstantNode con) { super(con); } @Override public String op() { return "ldx"; } @Override public RegMask regmap(int i) { return null; } @Override public RegMask outregmap() { return riscv.WMASK; } @Override public boolean isClone() { return true; } - @Override public TFPRISC copy() { return new TFPRISC((TypeFunPtr)_con,_ext); } + @Override public TFPRISC copy() { return new TFPRISC(this); } @Override public void encoding( Encoding enc ) { - if( _ext==null ) enc.relo(this); // Internal patch - else enc.external(this,_ext); // ELF-file external patch + enc.relo(this); // TODO: 1 op encoding, plus a TODO if it does not fit short dst = enc.reg(this); + TypeFunPtr tfp = (TypeFunPtr)_con; // auipc t0,0 enc.add4(RiscV.u_type(RiscV.OP_AUIPC, dst, 0)); // addi t1,t0 + #0 @@ -35,14 +33,9 @@ public class TFPRISC extends FunPtrNode implements MachNode, RIPRelSize { @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { short rpc = enc.reg(this); if( opLen==8 ) { - int hi20 = (delta + 0x800) >> 12; - int lo12 = delta - (hi20 << 12); - // AUIPC (upper 20 bits) - // opstart of add - enc.patch4(opStart ,RiscV.u_type(RiscV.OP_AUIPC, rpc, hi20)); - // addi(low 12 bits) - enc.patch4(opStart+4,RiscV.i_type(RiscV.OP_IMM, rpc, 0, rpc, lo12 & 0xFFF)); - // addi + // ADDI sign-extends its low 12 bits, so round the AUIPC high part. + enc.patch4(opStart ,RiscV.u_type(RiscV.OP_AUIPC, rpc, (delta+0x800)>>12)); + enc.patch4(opStart+4,RiscV.i_type(RiscV.OP_IMM, rpc, 0, rpc, delta & 0xFFF)); } else { // should not happen as one instruction is 4 byte, and TFP arm encodes 2. throw Utils.TODO(); @@ -50,7 +43,7 @@ public class TFPRISC extends FunPtrNode implements MachNode, RIPRelSize { } @Override public void asm(CodeGen code, SB sb) { - _type.print(sb.p(code.reg(this)).p(" #")); + _con.print(sb.p(code.reg(this)).p(" #")); } @Override public boolean eq(Node n) { return this==n; } } 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 a5ff3753..7bc2db7e 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 @@ -9,16 +9,14 @@ import com.seaofnodes.simple.type.TypeMemPtr; import com.seaofnodes.simple.util.SB; public class TMPRISC extends ConstantNode implements MachNode, RIPRelSize { - final String _ext; - TMPRISC( ConstantNode con, String ext ) { super(con); _ext = ext; } + TMPRISC(ConstantNode con) { super(con); } @Override public String op() { return "ldp"; } @Override public RegMask regmap(int i) { return null; } @Override public RegMask outregmap() { return riscv.WMASK; } @Override public boolean isClone() { return true; } - @Override public TMPRISC copy() { return new TMPRISC(this,_ext); } + @Override public TMPRISC copy() { return new TMPRISC(this); } @Override public void encoding( Encoding enc ) { - if( _ext!=null ) enc.externalData(this,_ext); - else enc.largeConstant(this,((TypeMemPtr)_con)._obj,0,-1); + 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)); @@ -31,9 +29,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 + // 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's low 12 bits are signed; round the AUIPC page accordingly. + // ADDI dst,dst,#low12_constant_pool 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/riscv/riscv.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java index 0195542c..7c994624 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java @@ -15,6 +15,7 @@ public class riscv extends Machine { } @Override public String name() {return "riscv";} + //@Override public int maxReg() { return MAX_REG; } // Using ABI names instead of register names public static int ZERO = 0, RPC= 1, RSP= 2, S12= 3, S13= 4, T0 = 5, T1 = 6, T2 = 7; @@ -33,24 +34,24 @@ public class riscv extends Machine { public static final int F_OFFSET = 32; static final String[] REGS = new String[] { - //"zero","rpc" , "rsp" , "s12" , "s13" , "t0" , "t1" , "t2" , - //"s0" , "s1" , "a0" , "a1" , "a2" , "a3" , "a4" , "a5" , - //"a6" , "a7" , "s2" , "s3" , "s4" , "s5" , "s6" , "s7" , - //"s8" , "s9" , "s10" , "s11" , "t3" , "t4" , "t5" , "t6" , - //"f0" , "f1" , "f2" , "f3" , "f4" , "f5" , "f6" , "f7" , - //"fs0" , "fs1" , "fa0" , "fa1" , "fa2" , "fa3" , "fa4" , "fa5" , - //"fa6" , "fa7" , "fs2" , "fs3" , "fs4" , "fs5" , "fs6" , "fs7" , - //"fs8" , "fs9" , "fs10", "fs11", "ft8" , "ft9" , "ft10", "ft11" - // Register-based naming hella easier to debug than "official" register - // names when single-stepping in debugger. - "zero", "r1" , "rsp" , "r3" , "r4" , "r5" , "r6" , "r7" , - "r8" , "r9" , "r10" , "r11", "r12", "r13", "r14", "r15", - "r16" , "r17" , "r18" , "r19", "r20", "r21", "r22", "r23", - "r24" , "r25" , "r26" , "r27", "r28", "r29", "r30", "r31", + "zero","rpc" , "rsp" , "s12" , "s13" , "t0" , "t1" , "t2" , + "s0" , "s1" , "a0" , "a1" , "a2" , "a3" , "a4" , "a5" , + "a6" , "a7" , "s2" , "s3" , "s4" , "s5" , "s6" , "s7" , + "s8" , "s9" , "s10" , "s11" , "t3" , "t4" , "t5" , "t6" , "f0" , "f1" , "f2" , "f3" , "f4" , "f5" , "f6" , "f7" , "fs0" , "fs1" , "fa0" , "fa1" , "fa2" , "fa3" , "fa4" , "fa5" , "fa6" , "fa7" , "fs2" , "fs3" , "fs4" , "fs5" , "fs6" , "fs7" , "fs8" , "fs9" , "fs10", "fs11", "ft8" , "ft9" , "ft10", "ft11" + // Register-based naming hella easier to debug than "official" register + // names when single-stepping in debugger. + //"zero", "r1" , "rsp" , "r3" , "r4" , "r5" , "r6" , "r7" , + //"r8" , "r9" , "r10" , "r11", "r12", "r13", "r14", "r15", + //"r16" , "r17" , "r18" , "r19", "r20", "r21", "r22", "r23", + //"r24" , "r25" , "r26" , "r27", "r28", "r29", "r30", "r31", + //"f0" , "f1" , "f2" , "f3" , "f4" , "f5" , "f6" , "f7" , + //"fs0" , "fs1" , "fa0" , "fa1" , "fa2" , "fa3" , "fa4" , "fa5" , + //"fa6" , "fa7" , "fs2" , "fs3" , "fs4" , "fs5" , "fs6" , "fs7" , + //"fs8" , "fs9" , "fs10", "fs11", "ft8" , "ft9" , "ft10", "ft11" }; @Override public String[] regs() { return REGS; } @@ -91,7 +92,7 @@ public class riscv extends Machine { public static RegMask FA6_MASK = new RegMask(FA6); public static RegMask FA7_MASK = new RegMask(FA7); - // Int/ptr arguments calling convention + // Int arguments calling conv static RegMask[] CALLINMASK = new RegMask[] { A0_MASK, A1_MASK, @@ -137,16 +138,10 @@ public class riscv extends Machine { if( idx==0 ) return RPC_MASK; if( idx==1 ) return null; if( idx-2 >= tfp.nargs() ) return null; // Anti-dependence - boolean hidden = hiddenSelf(tfp); - int sigidx = idx-2; - if( hidden ) { - if( sigidx==0 ) return null; - sigidx--; - } // Count floats in signature up to index int fcnt=0; - for( int i=hidden ? 1 : 0; i<idx-2; i++ ) - if( tfp.arg(i) instanceof TypeFloat ) + for( int i=2; i<idx; i++ ) + if( tfp.arg(i-2) instanceof TypeFloat ) fcnt++; // Floats up to XMMS in XMM registers if( tfp.arg(idx-2) instanceof TypeFloat ) { @@ -154,26 +149,23 @@ public class riscv extends Machine { return XMMS[fcnt]; } else { RegMask[] cargs = CALLINMASK; - if( sigidx-fcnt < cargs.length ) - return cargs[sigidx-fcnt]; + if( idx-2-fcnt < cargs.length ) + return cargs[idx-2-fcnt]; } // Pass on stack slot(8 and higher) if( maxArgSlot>0 ) throw Utils.TODO(); - return new RegMask(MAX_REG + 1 + sigidx); + return new RegMask(MAX_REG + 1 + (idx - 2)); } @Override public short maxArgSlot( TypeFunPtr tfp ) { int icnt=0, fcnt=0; // Count of ints, floats - for( int i=hiddenSelf(tfp) ? 1 : 0; i<tfp.nargs(); i++ ) { + for( int i=0; i<tfp.nargs(); i++ ) { if( tfp.arg(i) instanceof TypeFloat ) fcnt++; else icnt++; } int nstk = Math.max(icnt-8,0)+Math.max(fcnt-8,0); return (short)nstk; } - private static boolean hiddenSelf(TypeFunPtr tfp) { - return tfp.nargs() > 0 && tfp.arg(0) == TypePtr.PTR; - } static final long CALLER_SAVE = (1L<<RPC) | @@ -194,7 +186,7 @@ public class riscv extends Machine { @Override public CFGNode jump() { return new UJmpRISC(); } // Create a split op; any register to any register, including stack slots - @Override public SplitNode split(LRG lrg, String kind, byte round ) { return new SplitRISC(lrg, kind,round); } + @Override public SplitNode split(String kind, byte round, LRG lrg) { return new SplitRISC(kind,round); } // True if signed 12-bit immediate public static boolean imm12(TypeInteger ti) { @@ -211,34 +203,30 @@ public class riscv extends Machine { @Override public Node instSelect( Node n ) { return switch (n) { + case AddFNode addf -> addf(addf); case AddNode add -> add(add); case AndNode and -> and(and); case BoolNode bool -> cmp(bool); case CallNode call -> call(call); - case PtrToIntNode ptr -> new PtrToIntMach(ptr); - case GuardNode guard -> new GuardMach(guard); + case CastNode cast -> new CastMach(cast); case CallEndNode cend -> new CallEndMach(cend); - case CLInitNode init -> new CLInitNode(init); case CProjNode c -> new CProjNode(c); case ConstantNode con -> con(con); - case DivNode div -> div.mode()==2 ? new DivFRISC(div) : new DivRISC(div); - case EscapeNode esc -> new EscapeNode(esc); + case DivFNode divf -> new DivFRISC(divf); + case DivNode div -> new DivRISC(div); case FunNode fun -> new FunRISC(fun); - case FunPtrNode fptr -> fptr(fptr); case IfNode iff -> jmp(iff); case LoadNode ld -> ld(ld); case MemMergeNode mem -> new MemMergeNode(mem); - case MinusNode neg -> { - if( neg.mode()==2 ) throw Utils.TODO(); - yield new NegRISC(neg); - } - case MulNode mul -> mul.mode()==2 ? new MulFRISC(mul) : new MulRISC(mul); + case MinusNode neg -> new NegRISC(neg); + case MulFNode mulf -> new MulFRISC(mulf); + case MulNode mul -> new MulRISC(mul); case NewNode nnn -> nnn(nnn); case NotNode not -> new NotRISC(not); case OrNode or -> or(or); case ParmNode parm -> new ParmRISC(parm); - case BulkMemPhiNode phi-> new BulkMemPhiNode(phi); - case MemPhiNode phi -> new MemPhiNode(phi); + case BulkMemPhiNode phi -> new BulkMemPhiNode(phi); + case MemPhiNode phi -> new MemPhiNode(phi); case PhiNode phi -> new PhiNode(phi); case ProjNode prj -> prj(prj); case ReadOnlyNode read-> new ReadOnlyMach(read); @@ -246,12 +234,11 @@ public class riscv extends Machine { case SarNode sar -> sra(sar); case ShlNode shl -> sll(shl); case ShrNode shr -> srl(shr); - case StartCUNode start -> new StartCUNode(start); - case StopCUNode stop -> new StopCUNode(stop); case StartNode start -> new StartNode(start); case StopNode stop -> new StopNode(stop); case StoreNode st -> st(st); - case SubNode sub -> sub.mode()==2 ? new SubFRISC(sub) : sub(sub); + case SubFNode subf -> new SubFRISC(subf); + case SubNode sub -> sub(sub); case ToFloatNode tfn -> i2f8(tfn); case ToIntegerNode cvt -> new F8ToIRISC(cvt); case XorNode xor -> xor(xor); @@ -262,9 +249,11 @@ public class riscv extends Machine { }; } + private Node addf(AddFNode addf) { + return new AddFRISC(addf); + } + private Node add(AddNode add) { - if( add.mode()==2 ) - return new AddFRISC(add); if( add.in(2) instanceof ConstantNode off2 && off2._con instanceof TypeInteger ti && imm12(ti) ) return new AddIRISC(add, (int)ti.value(),true); return new AddRISC(add); @@ -282,10 +271,8 @@ public class riscv extends Machine { } private Node call(CallNode call) { - return call.fptr() instanceof FunPtrNode fptr - ? new CallRISC(call, (TypeFunPtr)fptr._type) - : call.fptr() instanceof ConstantNode con - ? new CallRISC(call, (TypeFunPtr)con._type) + return call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp + ? new CallRISC(call, tfp) : new CallRRISC(call); } private Node nnn(NewNode nnn) { @@ -324,13 +311,9 @@ public class riscv extends Machine { } private Node con( ConstantNode con ) { - if( !con._con.isConstant() ) - return ConstantNode.raw(con); // Default unknown caller inputs - String ext = con instanceof ExternNode ext0 ? ext0._extern : null; + if( !con._con.isConstant() ) return new ConstantNode( con ); // Default unknown caller inputs return switch( con._con ) { case TypeInteger ti -> { - // External; without knowing the size assume we have to load something - if( ext!=null ) yield new Int8RISC(con,ext); if( imm12(ti) ) yield new IntRISC(con); long x = ti.value(); if( imm20Exact(ti) ) yield new LUI((int)x); @@ -343,22 +326,18 @@ public class riscv extends Machine { } // Need more complex sequence for larger constants... or a load // from a constant pool, which does not need an extra register - yield new Int8RISC(con,null); + yield new Int8RISC(con); } // Load from constant pool - case TypeFloat tf -> new FltRISC(con,ext); - case TypeMemPtr tmp -> new TMPRISC(con,ext); - case TypeFunPtr tfp -> new TFPRISC(tfp,ext); + case TypeFloat tf -> new FltRISC(con); + case TypeFunPtr tfp -> new TFPRISC(con); + case TypeMemPtr tmp -> new TMPRISC(con); case TypeNil tn -> throw Utils.TODO(); // TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code. - case Type t -> t==Type.NIL ? new IntRISC(con) : ConstantNode.raw(con); + case Type t -> t==Type.NIL ? new IntRISC(con) : new ConstantNode(con); }; } - private Node fptr( FunPtrNode con ) { - return new TFPRISC(con); - } - private Node jmp( IfNode iff ) { if( iff.in(1) instanceof BoolNode bool && !bool.isFloat() ) { // if less than or equal switch inputs @@ -413,10 +392,9 @@ public class riscv extends Machine { } private Node prj(ProjNode prj) { - if( prj.in(0) instanceof CLInitNode ) return new ProjNode(prj); TypeInteger ret = cReturn(prj); if( ret!=null ) return new ExtendRISC(prj,ret); - return prj.in(0) instanceof StartNode ? new ProjNode(prj) : new ProjRISC(prj); + return new ProjRISC(prj); } private Node ld(LoadNode ld) { diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java index 74af9266..974f690f 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java @@ -7,7 +7,7 @@ import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; public class AddFMemX86 extends MemOpX86 { - AddFMemX86( AddNode add, LoadNode ld , Node base, Node idx, int off, int scale, Node val ) { + AddFMemX86( AddFNode add, LoadNode ld , Node base, Node idx, int off, int scale, Node val ) { super(add,ld, base, idx, off, scale, 0, val ); } @Override public String op() { return "addf"+_sz; } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java index 6bafbf37..cd4d2452 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java @@ -9,27 +9,21 @@ import com.seaofnodes.simple.util.SB; public class CallX86 extends CallNode implements MachNode, RIPRelSize { final TypeFunPtr _tfp; final String _name; - final boolean _extern; CallX86( CallNode call, TypeFunPtr tfp ) { super(call); _inputs.pop(); // Pop constant target assert tfp.isConstant(); _tfp = tfp; - _name = CodeGen.CODE.funcName(tfp.fidx()); - _extern = CodeGen.CODE.externFunc(tfp.fidx()) != null; + FunNode fun = CodeGen.CODE.link(tfp); + _name = fun==null ? CodeGen.CODE.externFunc(tfp) : fun._name; } @Override public String op() { return "call"; } @Override public String label() { return op(); } @Override public String name() { return _name; } @Override public TypeFunPtr tfp() { return _tfp; } - @Override public RegMask regmap(int i) { - return _extern - ? x86_64_v2.callInMask(_tfp,i,fun()._maxArgSlot,CodeGen.CODE._cCallingConv) - : x86_64_v2.callInMask(_tfp,i,fun()._maxArgSlot); - } + @Override public RegMask regmap(int i) { return x86_64_v2.callInMask(_tfp,i,fun()._maxArgSlot); } @Override public RegMask outregmap() { return null; } @Override public int nargs() { return nIns()-2; } // Minus control, memory, fptr - @Override public boolean external() { return _extern; } @Override public void encoding( Encoding enc ) { FunNode fun = CodeGen.CODE.link(_tfp); diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java index 4014045b..7b7d98d5 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java @@ -20,8 +20,8 @@ public class CmpMemX86 extends MemOpX86 { short ptr=enc.reg(ptr()), idx=enc.reg(idx()), src=enc.reg(val()); if( src==-1 ) { assert !_swap; - X86.cmpImm(enc,_con.log_size(),_imm,ptr,idx,_off,_scale); - } else encVal(enc,_con,ptr,idx,src,_off,_scale,_swap); + X86.cmpImm(enc,_declaredType.log_size(),_imm,ptr,idx,_off,_scale); + } else encVal(enc,_declaredType,ptr,idx,src,_off,_scale,_swap); } static void encVal(Encoding enc, Type decl, short ptr, short idx, short src, int off, int scale, boolean _swap) { diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java index 59f06341..da9bdce7 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java @@ -4,20 +4,17 @@ import com.seaofnodes.isa.X86; import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; public class FltX86 extends ConstantNode implements MachNode, RIPRelSize{ private byte _encSize; - final String _ext; - FltX86( ConstantNode con, String ext ) { super(con); _ext = ext; } + FltX86(ConstantNode con ) { super(con); } @Override public String op() { return "ld8"; } @Override public RegMask regmap(int i) { return null; } @Override public RegMask outregmap() { return x86_64_v2.XMASK; } @Override public boolean isClone() { return true; } - @Override public Node copy() { return new FltX86(this,_ext); } + @Override public Node copy() { return new FltX86(this); } @Override public void encoding( Encoding enc ) { - if( _ext!=null ) throw Utils.TODO(); _encSize=X86.rip(enc,0xF2,0x0F10,false,enc.reg(this)-x86_64_v2.XMM_OFFSET); enc.largeConstant(this,_con,_encSize-4,2/*ELF PC32*/); } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java index 0e44aee7..c7a20e4d 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java @@ -11,7 +11,6 @@ public class FunX86 extends FunNode implements MachNode { FunX86( FunNode fun ) { super(fun); } @Override public void postSelect(CodeGen code) { super.postSelect(code); - _maxArgSlot = code._mach.maxArgSlot(sig(),code._callingConv); // One more for RPC pre-pushed; X86 special _maxArgSlot++; } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java index 2ffaa3d1..656b1543 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java @@ -6,17 +6,15 @@ import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; // Integer constants public class IntX86 extends ConstantNode implements MachNode { - final String _ext; - IntX86( ConstantNode con, String ext ) { super(con); _ext = ext; } + IntX86( ConstantNode con ) { super(con); } @Override public String op() { return _con == Type.NIL || _con == TypeInteger.ZERO ? "xor" : "ldi"; } @Override public boolean isClone() { return true; } - @Override public Node copy() { return new IntX86(this,_ext); } + @Override public Node copy() { return new IntX86(this); } @Override public RegMask regmap(int i) { return null; } @Override public RegMask outregmap() { return x86_64_v2.WMASK; } // Zero-set uses XOR kills flags @@ -25,7 +23,6 @@ public class IntX86 extends ConstantNode implements MachNode { } @Override public void encoding( Encoding enc ) { - if( _ext!=null ) throw Utils.TODO(); X86.constant(enc,enc.reg(this),_con==Type.NIL ? 0 : ((TypeInteger)_con).value()); } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java index 5c472dbe..d4f1413e 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java @@ -13,7 +13,7 @@ public class LoadX86 extends MemOpX86 { super(ld,ld, base, idx, off, scale, 0); } @Override public String op() { return "ld"+_sz; } - @Override public RegMask outregmap() { return _con instanceof TypeFloat ? x86_64_v2.XMASK : x86_64_v2.WMASK; } + @Override public RegMask outregmap() { return x86_64_v2.MEM_MASK; } @Override public void encoding( Encoding enc ) { // REX.W + 8B /r MOV r64, r/m64 // Zero extension for u8, u16 and u32 but sign extension i8, i16, i32 @@ -21,7 +21,7 @@ public class LoadX86 extends MemOpX86 { short dst = enc.reg(this ); short ptr = enc.reg(ptr()); short idx = enc.reg(idx()); - enc(enc, _con, dst, ptr, idx, _off, _scale); + enc(enc, _declaredType, dst, ptr, idx, _off, _scale); } static void enc( Encoding enc, Type decl, short dst, short ptr, short idx, int off, int scale ) { diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java index fcca7f8f..309e62d9 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java @@ -6,6 +6,7 @@ import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.util.Utils; import java.lang.StringBuilder; // Generic X86 memory operand base. @@ -41,8 +42,7 @@ public abstract class MemOpX86 extends MemOpNode implements MachNode { _off = off; _scale = scale; _imm = imm; - int size = mop instanceof StoreNode st ? st.storeSize() : 1<<_con.log_size(); - _sz = (char)('0'+size); + _sz = (char)('0'+(1<<_declaredType.log_size())); } // Store-based flavors have a value edge @@ -59,8 +59,8 @@ public abstract class MemOpX86 extends MemOpNode implements MachNode { } @Override public String label() { return op(); } - @Override public Type compute() { throw new AssertionError("Do not call compute on "+getClass().getSimpleName()); } - @Override public Node idealize() { throw new AssertionError("Do not call idealize on "+getClass().getSimpleName()); } + @Override public Type compute() { throw Utils.TODO(); } + @Override public Node idealize() { throw Utils.TODO(); } // Register mask allowed on input i. @Override public RegMask regmap(int i) { diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java index d6a06c2f..1cfc1c42 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java @@ -16,8 +16,7 @@ public class NewX86 extends NewNode implements MachNode { } // General form: "alloc #bytes PC" @Override public void asm(CodeGen code, SB sb) { - sb.p("\n"); // Multi-op trigger - sb.p("ldi ").p(code._mach.regs()[_arg2Reg]).p(" = #1\n"); + sb.p("ldi rcx = #1\n"); sb.p("call #calloc"); } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitFlagsX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitFlagsX86.java index cb7694f9..4650b535 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitFlagsX86.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitFlagsX86.java @@ -6,7 +6,7 @@ import com.seaofnodes.simple.codegen.*; // Flag snapshots can use GPRs or stack slots, but never XMM registers. public class SplitFlagsX86 extends SplitX86 { private static final RegMask MASK = new RegMask(x86_64_v2.WR_BITS | (1L<<x86_64_v2.FLAGS) | x86_64_v2.SPILLS,-1L); - SplitFlagsX86(LRG lrg,String kind,byte round) { super(lrg,kind,round); } + SplitFlagsX86(String kind,byte round) { super(kind,round); } @Override public RegMask regmap(int i) { return MASK; } @Override public RegMask outregmap() { return MASK; } @@ -17,7 +17,7 @@ public class SplitFlagsX86 extends SplitX86 { boolean toFlags = dst==x86_64_v2.FLAGS; int reg = toFlags ? src : dst; if( reg>=x86_64_v2.MAX_REG ) - X86.flagsStack(enc,fun(enc).computeStackOffset(enc._code,reg),toFlags); + X86.flagsStack(enc,enc._fun.computeStackOffset(enc._code,reg),toFlags); else X86.flags(enc,reg,toFlags); } else super.encoding(enc); diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java index 46f2cb16..4e9eda7d 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java @@ -12,21 +12,22 @@ import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; public class SplitX86 extends SplitNode { - SplitX86( LRG lrg, String kind, byte round ) { super(lrg,kind,round, new Node[2]); } + SplitX86( String kind, byte round ) { super(kind,round, new Node[2]); } + @Override public String op() { return "mov"; } @Override public RegMask regmap(int i) { return x86_64_v2.SPLIT_MASK; } @Override public RegMask outregmap() { return x86_64_v2.SPLIT_MASK; } - // Moves among GPRs, XMM registers, and stack slots. + // Ordinary GPR, XMM, and stack moves. Flags use SplitFlagsX86. @Override public void encoding( Encoding enc ) { short dst=enc.reg(this), src=enc.reg(in(1)); assert dst!=x86_64_v2.FLAGS && src!=x86_64_v2.FLAGS; boolean dstX=dst>=x86_64_v2.XMM_OFFSET, srcX=src>=x86_64_v2.XMM_OFFSET; // Stack spills if( dst >= x86_64_v2.MAX_REG ) { - int off = fun(enc).computeStackOffset(enc._code,dst); + int off = enc._fun.computeStackOffset(enc._code,dst); if( src >= x86_64_v2.MAX_REG ) { // Rare stack-stack move. push [RSP+soff]; pop [RSP+doff] - int soff = fun(enc).computeStackOffset(enc._code,src); + int soff = enc._fun.computeStackOffset(enc._code,src); X86.stackCopy(enc,soff,off); return; } @@ -34,7 +35,7 @@ public class SplitX86 extends SplitNode { return; } if( src >= x86_64_v2.MAX_REG ) { - int off = fun(enc).computeStackOffset(enc._code,src); + int off = enc._fun.computeStackOffset(enc._code,src); LoadX86.enc(enc, dstX ? TypeFloat.F64 : TypeInteger.BOT, dst, (short)x86_64_v2.RSP, (short)-1, off, 0); return; } @@ -42,4 +43,10 @@ public class SplitX86 extends SplitNode { X86.move(enc,dstX,srcX,dstX ? dst-x86_64_v2.XMM_OFFSET : dst, srcX ? src-x86_64_v2.XMM_OFFSET : src); } + + // General form: "mov dst = src" + @Override public void asm(CodeGen code, SB sb) { + FunNode fun = code._encoding==null ? null : code._encoding._fun; + sb.p(code.reg(this,fun)).p(" = ").p(code.reg(in(1),fun)); + } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java index 9ffa25fc..c83dfd44 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java @@ -27,8 +27,8 @@ public class StoreX86 extends MemOpX86 { @Override public RegMask outregmap() { return null; } @Override public void encoding( Encoding enc ) { short ptr=enc.reg(ptr()), idx=enc.reg(idx()), src=enc.reg(val()); - if( src==-1 ) X86.storeImm(enc,Integer.numberOfTrailingZeros(_sz-'0'),_imm,ptr,idx,_off,_scale); - else encVal(enc,_con,ptr,idx,src,_off,_scale); + if( src==-1 ) X86.storeImm(enc,_declaredType.log_size(),_imm,ptr,idx,_off,_scale); + else encVal(enc,_declaredType,ptr,idx,src,_off,_scale); } // Non-immediate encoding diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java index e3563713..fff2fb04 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java @@ -3,39 +3,39 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; import com.seaofnodes.isa.X86; import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.cpus.riscv.riscv; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeFunPtr; import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; // Function constants -public class TFPX86 extends FunPtrNode implements MachNode, RIPRelSize { - private byte _opLen; - final String _ext; // External name - // Pointer to a Simple function - TFPX86( FunPtrNode fptr ) { this((TypeFunPtr)fptr._type,null); } - // Spill clone: copy inputs without registering their reverse edges; - // insertBefore will place and register the clone in its use block. - private TFPX86( TFPX86 fptr ) { super(fptr); _ext = fptr._ext; } - // Pointer to an Extern "C" function - TFPX86( TypeFunPtr fptr, String ext ) { super(fptr,CodeGen.CODE._start,null); _type = fptr; _ext = ext; } - @Override public String op() { return "ldx"; } - @Override public String label() { return op(); } +public class TFPX86 extends ConstantNode implements MachNode, RIPRelSize { + TFPX86( ConstantNode con ) { super(con); } + @Override public String op() { + return _con == Type.NIL ? "xor" : "ldx"; + } @Override public boolean isClone() { return true; } - @Override public TFPX86 copy() { return new TFPX86(this); } + @Override public Node copy() { return new TFPX86(this); } @Override public RegMask regmap(int i) { return null; } @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + // Zero-set uses XOR kills flags + @Override public RegMask killmap() { + return _con == Type.NIL ? x86_64_v2.FLAGS_MASK : null; + } + @Override public void encoding( Encoding enc ) { - if( _ext==null ) enc.relo(this); - else enc.external(this,_ext); - _opLen=X86.rip(enc,0,0x8D,true,enc.reg(this)); + if( _con==Type.NIL ) { X86.constant(enc,enc.reg(this),0); return; } + enc.relo(this); + X86.rip(enc,0,0x8D,true,enc.reg(this)); } - @Override public byte encSize(int delta) { return _opLen; } + @Override public byte encSize(int delta) { + return 7; + } - // Delta is from opcode start, X86 expects it from opcode end + // Delta is from opcode start @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { - enc.patch4(opStart + opLen - 4, delta - opLen); + enc.patch4(opStart + 3, delta - 7); } // Human-readable form appended to the SB. Things like the encoding, @@ -43,9 +43,11 @@ public class TFPX86 extends FunPtrNode implements MachNode, RIPRelSize { // Just something like "ld4\tR17=[R18+12] // Load array base". // General form: "op\tdst=src+src" @Override public void asm(CodeGen code, SB sb) { - sb.p(code.reg(this)); - if( _ext == null ) _type.print(sb.p(" #")); - else sb.p(_ext); + String reg = code.reg(this); + if( _con == Type.NIL ) + sb.p(reg).p(",").p(reg); + else + _con.print(sb.p(reg).p(" = #")); } @Override public boolean eq(Node n) { return this==n; } } 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 682597f4..c78ac6de 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 @@ -7,29 +7,30 @@ import com.seaofnodes.simple.type.TypeMemPtr; import com.seaofnodes.simple.util.SB; public class TMPX86 extends ConstantNode implements MachNode, RIPRelSize{ - private byte _opLen; - final String _ext; - TMPX86( ConstantNode con, String ext ) { super(con); _ext = ext; } + TMPX86(ConstantNode con ) { super(con); } @Override public String op() { return "ldp"; } @Override public RegMask regmap(int i) { return null; } @Override public RegMask outregmap() { return x86_64_v2.WMASK; } @Override public boolean isClone() { return true; } - @Override public Node copy() { return new TMPX86(this,_ext); } + @Override public Node copy() { return new TMPX86(this); } @Override public void encoding( Encoding enc ) { short dst = enc.reg(this); - // 0 or 1 for REX depending on the dst. - _opLen = X86.rip(enc,0,0x8D,true,dst); - if( _ext!=null ) enc.externalData(this,_ext); - else enc.largeConstant(this,((TypeMemPtr)_con)._obj, _opLen-4, 2/*ELF encoding PC32*/); + X86.rexF(dst, 0, 0, true, enc); + enc.add1(0x8D).add1(X86.modrm(X86.MOD.INDIRECT, dst, 0b101)).add4(0); + int opLen = 1/*rexF*/+1/*opcode 0x8D*/+1/*modrm*/+4/*pc rel offset*/; + // Base of constant array + TypeMemPtr tmp = (TypeMemPtr)_con; + int baseOff = tmp._obj.aryBase(); + enc.largeConstant(this,tmp._obj,opLen-baseOff,2/*ELF encoding PC32*/); } // Delta is from opcode start - @Override public byte encSize(int delta) { return _opLen; } + @Override public byte encSize(int delta) { return (byte)7; } // Delta is from opcode start @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { - enc.patch4(opStart + opLen - 4, delta - opLen); + enc.patch4(opStart+opLen-4,delta-opLen); } @Override public void asm(CodeGen code, SB sb) { diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java index 3dc93bf6..f0598422 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java @@ -97,29 +97,22 @@ public class x86_64_v2 extends Machine { // Map from function signature and argument index to register. // Used to set input registers to CallNodes, and ParmNode outputs. @Override public RegMask callArgMask( TypeFunPtr tfp, int idx, int maxArgSlot ) { return callInMask(tfp,idx,maxArgSlot); } - @Override public RegMask callArgMask( TypeFunPtr tfp, int idx, int maxArgSlot, String conv ) { return callInMask(tfp,idx,maxArgSlot,conv); } static RegMask callInMask( TypeFunPtr tfp, int idx, int maxArgSlot ) { - return callInMask(tfp,idx,maxArgSlot,CodeGen.CODE._callingConv); - } - static RegMask callInMask( TypeFunPtr tfp, int idx, int maxArgSlot, String conv ) { if( idx==0 ) return RPC_MASK; if( idx==1 ) return null; if( idx-2 >= tfp.nargs() ) return null; // Anti-dependence - return switch( conv ) { + return switch( CodeGen.CODE._callingConv ) { case "SystemV" -> callSys5 (tfp,idx,maxArgSlot); - case "win64" -> callWin64(tfp,idx,maxArgSlot); + case "Win64" -> callWin64(tfp,idx,maxArgSlot); default -> throw Utils.TODO(); }; } // Maximum stack args used by this signature @Override public short maxArgSlot( TypeFunPtr tfp ) { - return maxArgSlot(tfp,CodeGen.CODE._callingConv); - } - @Override public short maxArgSlot( TypeFunPtr tfp, String conv ) { - return switch( conv ) { + return switch( CodeGen.CODE._callingConv ) { case "SystemV" -> maxArgSlotSys5 (tfp); - case "win64" -> maxArgSlotWin64(tfp); + case "Win64" -> maxArgSlotWin64(tfp); default -> throw Utils.TODO(); }; } @@ -152,22 +145,14 @@ public class x86_64_v2 extends Machine { static RegMask callWin64(TypeFunPtr tfp, int idx, int maxArgSlot ) { // idx 2,3,4,5 passed in registers, with stack slot mirrors. // idx >= 6 passed on stack, starting at slot#1 (#0 reserved for RPC). - int sigidx = idx-2; - if( hiddenSelf(tfp) ) { - if( sigidx==0 ) return null; - sigidx--; - } - if( sigidx >= 4 ) - return new RegMask(MAX_REG+maxArgSlot+sigidx); + if( idx >= 6 ) + return new RegMask(MAX_REG+maxArgSlot+(idx-2)); return tfp.arg(idx-2) instanceof TypeFloat - ? XMMS8 [sigidx] - : WIN64_CALL[sigidx]; + ? XMMS8 [idx-2] + : WIN64_CALL[idx-2]; } static short maxArgSlotWin64(TypeFunPtr tfp) { - return (short)Math.max(4,tfp.nargs() - (hiddenSelf(tfp) ? 1 : 0)); - } - static short maxIncomingArgSlotWin64(TypeFunPtr tfp) { - return (short)Math.max(0,tfp.nargs() - (hiddenSelf(tfp) ? 1 : 0) - 4); + return (short)tfp.nargs(); } // Sys5: max 6 GPRs and 8 FPRS filled first. Extra args land in increasing @@ -201,14 +186,9 @@ public class x86_64_v2 extends Machine { static RegMask callSys5(TypeFunPtr tfp, int idx, int maxArgSlot ) { // First 6 integers passed in registers: rdi,rsi,rdx,rcx,r08,r09 // First 8 floats passed in registers: xmm0-xmm7 - int sigidx = idx-2; - if( hiddenSelf(tfp) ) { - if( sigidx==0 ) return null; - sigidx--; - } int icnt=0, fcnt=0; // Count of ints, floats - for( int i=hiddenSelf(tfp) ? 1 : 0; i<idx-2; i++ ) { - if( tfp.arg(i) instanceof TypeFloat ) fcnt++; + for( int i=2; i<idx; i++ ) { + if( tfp.arg(i-2) instanceof TypeFloat ) fcnt++; else icnt++; } int nstk = Math.max(icnt-6,0)+Math.max(fcnt-8,0); @@ -218,16 +198,13 @@ public class x86_64_v2 extends Machine { } static short maxArgSlotSys5(TypeFunPtr tfp) { int icnt=0, fcnt=0; // Count of ints, floats - for( int i=hiddenSelf(tfp) ? 1 : 0; i<tfp.nargs(); i++ ) { + for( int i=0; i<tfp.nargs(); i++ ) { if( tfp.arg(i) instanceof TypeFloat ) fcnt++; else icnt++; } int nstk = Math.max(icnt-6,0)+Math.max(fcnt-8,0); return (short)nstk; } - private static boolean hiddenSelf(TypeFunPtr tfp) { - return tfp.nargs() > 0 && tfp.arg(0) == TypePtr.PTR; - } static final long SYSTEM5_CALLER_SAVE = (1L << RAX) | (1L << RCX) | (1L << RDX) | @@ -246,18 +223,12 @@ public class x86_64_v2 extends Machine { (1L<<XMM4) | (1L<<XMM5) ; @Override public long callerSave() { - return callerSave(CodeGen.CODE._callingConv); - } - @Override public long callerSave(String conv) { - return switch (conv) { + return switch (CodeGen.CODE._callingConv) { case "SystemV" -> SYSTEM5_CALLER_SAVE; - case "win64" -> WIN64_CALLER_SAVE; - default -> throw new IllegalArgumentException("Unknown calling convention: " + conv); + case "Win64" -> WIN64_CALLER_SAVE; + default -> throw new IllegalArgumentException("Unknown calling convention: " + CodeGen.CODE._callingConv); }; } - @Override public String cCallingConv(String callingConv) { - return callingConv; - } @Override public long neverSave() { return 1L<<RSP; } @Override public RegMask retMask( TypeFunPtr tfp ) { return tfp.ret() instanceof TypeFloat ? XMM0_MASK : RAX_MASK; @@ -266,10 +237,10 @@ public class x86_64_v2 extends Machine { // Create a split op; any register to any register, including stack slots - @Override public SplitNode split(LRG lrg, String kind, byte round ) { + @Override public SplitNode split(String kind, byte round, LRG lrg) { return lrg._machDef!=null && lrg._machDef.outregmap().test(FLAGS) - ? new SplitFlagsX86(lrg,kind,round) - : new SplitX86(lrg,kind,round); + ? new SplitFlagsX86(kind,round) + : new SplitX86(kind,round); } // Return a MachNode unconditional branch @@ -281,34 +252,30 @@ public class x86_64_v2 extends Machine { @Override public Node instSelect(Node n) { return switch (n) { + case AddFNode addf -> addf(addf); case AddNode add -> add(add); case AndNode and -> and(and); case BoolNode bool -> cmp(bool); - case CLInitNode init -> new CLInitNode(init); case CProjNode c -> new CProjNode(c); case CallEndNode cend -> new CallEndMach(cend); case CallNode call -> call(call); - case PtrToIntNode ptr -> new PtrToIntMach(ptr); - case GuardNode guard -> new GuardMach(guard); + case CastNode cast -> new CastMach(cast); case ConstantNode con -> con(con); - case DivNode div -> div.mode()==2 ? new DivFX86(div) : new DivX86(div); - case EscapeNode esc -> new EscapeNode(esc); + case DivFNode divf -> new DivFX86(divf); + case DivNode div -> new DivX86(div); case FunNode fun -> new FunX86(fun); - case FunPtrNode fptr -> fptr(fptr); case IfNode iff -> jmp(iff); case LoadNode ld -> ld(ld); case MemMergeNode mem -> new MemMergeNode(mem); - case MinusNode neg -> { - if( neg.mode()==2 ) throw Utils.TODO(); - yield new NegX86(neg); - } - case MulNode mul -> mul.mode()==2 ? new MulFX86(mul) : mul(mul); + case MinusNode neg -> new NegX86(neg); + case MulFNode mulf -> new MulFX86(mulf); + case MulNode mul -> mul(mul); case NewNode nnn -> new NewX86(nnn); case NotNode not -> new NotX86(not); case OrNode or -> or(or); case ParmNode parm -> new ParmX86(parm); - case BulkMemPhiNode phi-> new BulkMemPhiNode(phi); - case MemPhiNode phi -> new MemPhiNode(phi); + case BulkMemPhiNode phi -> new BulkMemPhiNode(phi); + case MemPhiNode phi -> new MemPhiNode(phi); case PhiNode phi -> new PhiNode(phi); case ProjNode prj -> prj(prj); case ReadOnlyNode read-> new ReadOnlyMach(read); @@ -316,15 +283,14 @@ public class x86_64_v2 extends Machine { case SarNode sar -> sar(sar); case ShlNode shl -> shl(shl); case ShrNode shr -> shr(shr); - case StartCUNode start -> new StartCUNode(start); - case StopCUNode stop -> new StopCUNode(stop); case StartNode start -> new StartNode(start); case StopNode stop -> new StopNode(stop); case StoreNode st -> st(st); - case SubNode sub -> sub.mode()==2 ? new SubFX86(sub) : sub(sub); + case SubFNode subf -> new SubFX86(subf); + case SubNode sub -> sub(sub); case ToFloatNode tfn -> i2f8(tfn); case ToIntegerNode cvt -> new F8ToIX86(cvt); - case XCtrlNode x -> ConstantNode.raw(Type.XCONTROL); + case XCtrlNode x -> new ConstantNode(Type.XCONTROL); case XorNode xor -> xor(xor); case LoopNode loop -> new LoopNode(loop); @@ -339,12 +305,14 @@ public class x86_64_v2 extends Machine { // Attempt a full LEA-style break down. private Node add(AddNode add) { - if( add.mode()==2 ) - return new AddFX86(add); Node lhs = add.in(1); Node rhs = add.in(2); - if( lhs instanceof LoadNode ld && ld.nOuts() == 1 && ld.declaredType().log_size() >= 3) + if( lhs instanceof LoadNode ld && ld.nOuts() == 1 && ld._declaredType.log_size() >= 3) return new AddMemX86(add, address(ld), ld.ptr(), idx, off, scale, 0, rhs); + + if( rhs instanceof LoadNode ld && ld.nOuts()==1 && ld._declaredType.log_size()>=3 ) + return new AddMemX86(add,address(ld),ld.ptr(),idx,off,scale, 0,lhs); + // Attempt a full LEA-style break down. // Returns one of AddX86, AddIX86, LeaX86, or LHS if( rhs instanceof ConstantNode off2 && off2._con instanceof TypeInteger toff ) { @@ -367,6 +335,17 @@ public class x86_64_v2 extends Machine { } + private Node addf(AddFNode addf) { + if(addf.in(1) instanceof LoadNode ld && ld.nOuts() == 1) + return new AddFMemX86(addf, address(ld), ld.ptr(), idx, off, scale, addf.in(2)); + +// if(addf.in(2) instanceof LoadNode ld && ld.nOuts() == 1) +// throw Utils.TODO(); // Swap load sides + + return new AddFX86(addf); + } + + private Node _lea(Node add, Node base, Node idx, int off) { int scale = 0; if( base instanceof ShlNode && !(idx instanceof ShlNode) ) @@ -390,10 +369,8 @@ public class x86_64_v2 extends Machine { } private Node call(CallNode call) { - return call.fptr() instanceof FunPtrNode con - ? new CallX86(call, (TypeFunPtr)con._type) - : call.fptr() instanceof ConstantNode con && con._type instanceof TypeFunPtr - ? new CallX86(call, (TypeFunPtr)con._type) + return call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp + ? new CallX86(call, tfp) : new CallRX86(call); } @@ -418,13 +395,12 @@ public class x86_64_v2 extends Machine { // Since cmp does not record a BOP, SetX/Jmp need to know if the CMP is swapped. // Vs memory - if( lhs instanceof LoadNode ld && ld.nOuts() == 1 && rhs._type.isa(ld.declaredType()) ) + if( lhs instanceof LoadNode ld && ld.nOuts() == 1 && rhs._type.isa(ld._declaredType) ) return new CmpMemX86(bool, address(ld), ld.ptr(), idx, off, scale, imm(rhs), val, false); // Operands swap in the encoding directly, no need for Set/Jmp to swap `bop` - if( rhs instanceof LoadNode ld && ld.nOuts() == 1 && lhs._type.isa(ld.declaredType()) && - (!(lhs instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value())) || - bool.op()=="==" || bool.op()=="!=") ) + if( rhs instanceof LoadNode ld && ld.nOuts() == 1 && lhs._type.isa(ld._declaredType) && + (val!=null || bool.op()=="==" || bool.op()=="!=") ) return new CmpMemX86(bool, address(ld), ld.ptr(), idx, off, scale, imm(lhs), val, true ); // Vs immediate @@ -441,23 +417,18 @@ public class x86_64_v2 extends Machine { private Node con( ConstantNode con ) { if( !con._con.isConstant() ) - return ConstantNode.raw(con); // Default unknown caller inputs - String ext = con instanceof ExternNode ext0 ? ext0._extern : null; + return new ConstantNode(con); // Default unknown caller inputs return switch (con._con) { - case TypeInteger ti -> new IntX86(con,ext); - case TypeFloat tf -> new FltX86(con,ext); - case TypeMemPtr tmp -> new TMPX86(con,ext); - case TypeFunPtr tfp -> new TFPX86(tfp,ext); + case TypeInteger ti -> new IntX86(con); + case TypeFloat tf -> new FltX86(con); + case TypeFunPtr tfp -> new TFPX86(con); + case TypeMemPtr tmp -> new TMPX86(con); case TypeNil tn -> throw Utils.TODO(); // TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code. - case Type t -> t == Type.NIL ? new IntX86(con,null) : ConstantNode.raw(con); + case Type t -> t == Type.NIL ? new IntX86(con) : new ConstantNode(con); }; } - private Node fptr( FunPtrNode con ) { - return new TFPX86(con); - } - private Node i2f8(ToFloatNode tfn) { assert tfn.in(1)._type instanceof TypeInteger ti; return new I2f8X86(tfn); @@ -470,9 +441,7 @@ public class x86_64_v2 extends Machine { String op = "!="; if( iff.in(1) instanceof BoolNode bool ) op = swap ? IfNode.swap(bool.op()) : bool.op(); else if( iff.in(1)==null ) op = "=="; // Never-node cutout - else iff.setDef(1, new BoolNode.NE(iff.in(1), - ConstantNode.raw(TypeInteger.ZERO), - (byte)(iff.in(1)._type instanceof TypeFloat ? 2 : 1))); + else iff.setDef(1, new BoolNode.NE(iff.in(1), new ConstantNode(TypeInteger.ZERO),(byte)1)); return new JmpX86(iff, op); } @@ -493,10 +462,9 @@ public class x86_64_v2 extends Machine { } private Node prj( ProjNode prj ) { - if( prj.in(0) instanceof CLInitNode ) return new ProjNode(prj); TypeInteger ret = cReturn(prj); if( ret!=null ) return new ExtendX86(prj,ret); - return prj.in(0) instanceof StartNode ? new ProjNode(prj) : new ProjX86(prj); + return new ProjX86(prj); } private Node sar(SarNode sar) { @@ -583,7 +551,7 @@ public class x86_64_v2 extends Machine { private int imm( Node xval ) { assert val == null && imm == 0; - if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti && ti.isConstant() && imm32(ti.value()) ) { + if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti) { val = null; imm = (int) ti.value(); assert imm == ti.value(); // In 32-bit range 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 e8218afd..48296ce7 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java @@ -9,8 +9,6 @@ import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; import java.util.ArrayList; import com.seaofnodes.simple.codegen.Encoding; -import com.seaofnodes.simple.util.BAOS; -import java.util.HashMap; /** Machine-specific facts; traversal, columns and byte display live in print/. */ public final class ASMPrinter extends AssemblyAdapter<Node> { @@ -26,7 +24,7 @@ public final class ASMPrinter extends AssemblyAdapter<Node> { } @Override public ArrayList<Node> blocks() { var blocks=new ArrayList<Node>(); - var cfg=_enc==null ? _code._cfg : _enc._cfg; + var cfg=_code._cfg; if( cfg!=null ) for( Node n : cfg ) blocks.add(n); return blocks; } @@ -52,24 +50,26 @@ public final class ASMPrinter extends AssemblyAdapter<Node> { if( n._type instanceof TypeMem || n._type instanceof TypeRPC ) return true; if( postAlloc() && n instanceof CalleeSaveNode ) return true; if( n instanceof MemMergeNode ) return true; - if( n instanceof EscapeNode ) return true; return n.getClass()==ConstantNode.class; } - @Override public boolean postAlloc() { return _code._phase.ordinal()>CodeGen.Phase.RegAlloc.ordinal() || - (_code._phase==CodeGen.Phase.RegAlloc && _code._regAlloc!=null && _code._regAlloc.done()); } + @Override public boolean postAlloc() { return _code._phase.ordinal()>CodeGen.Phase.RegAlloc.ordinal(); } @Override public boolean encoded() { return _enc!=null && _code._phase.ordinal()>=CodeGen.Phase.Encoding.ordinal(); } @Override public int defaultSize() { return _code._mach==null ? 2 : _code._mach.defaultOpSize(); } @Override public boolean littleEndian() { return _code._asmLittle; } @Override public boolean prologue(Node n) { return ((FunNode)n)._frameAdjust!=0; } @Override public byte[] bytes() { return encoded() ? _enc._bits.buf() : null; } - @Override public int offset(Node n) { return encoded() ? _enc.opStart(n) : -1; } - @Override public int size(Node n) { return encoded() ? _enc.opLen(n)&255 : 0; } - private static ArrayList<Data> entries(Iterable<Encoding.Relo> relos, boolean ro) { + @Override public int offset(Node n) { + return encoded() && _enc._opStart!=null && n._nid<_enc._opStart.length ? _enc._opStart[n._nid] : -1; + } + @Override public int size(Node n) { + return encoded() && _enc._opLen!=null && n._nid<_enc._opLen.length ? _enc._opLen[n._nid]&255 : 0; + } + private static ArrayList<Data> entries(Iterable<Encoding.Relo> relos) { var data=new ArrayList<Data>(); for( Encoding.Relo r : relos ) { - if( r.readOnly()!=ro ) continue; int align=1<<r._align; int size=r._t instanceof TypeStruct ? (r._structSize+align-1)&-align : align; + if( r._t instanceof TypeTuple t ) size=align*t._types.length; data.add(new Data(r._t,r._t.str(),r._align,size)); } return data; @@ -78,24 +78,8 @@ public final class ASMPrinter extends AssemblyAdapter<Node> { var pools=new ArrayList<Pool>(); 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,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,objects(false))); + if( !_enc._bigCons.isEmpty() && base<_enc._bits.size() ) + pools.add(new Pool("Constant Pool",_enc._bits.buf(),base,base,entries(_enc._bigCons.values()))); 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) { - sb.p(com.seaofnodes.print.ASMPrinter.pool(new Pool(name,bits.buf(),0,address,entries(relos.values(),ro)))); - return address+bits.size(); - } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/print/IRPrinter.java b/chapter25/src/main/java/com/seaofnodes/simple/print/IRPrinter.java index 531a549a..c912ab77 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/print/IRPrinter.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/print/IRPrinter.java @@ -14,8 +14,6 @@ public final class IRPrinter extends IRAdapter<Node> { @Override public String type(Node n) { return n._type==null ? "" : n._type.str(); } @Override public String inputMark(Node n, Node def) { return n instanceof MemMergeNode && def instanceof MemMergeNode ? "^" : " "; } @Override public Kind kind(Node n) { - if( n instanceof StartCUNode ) return Kind.UNIT; - if( n instanceof StopCUNode ) return Kind.UNIT; if( n instanceof StartNode ) return Kind.START; if( n instanceof StopNode ) return Kind.STOP; if( n instanceof FunNode ) return Kind.FUN; @@ -49,12 +47,15 @@ public final class IRPrinter extends IRAdapter<Node> { public static String prettyPrint(CodeGen code) { var units=new ArrayList<com.seaofnodes.print.IRPrinter.Unit<Node>>(); - for( var cu : code._compunits.values() ) { - var funs=new ArrayList<Node>(); - for( FunNode fun : code._linker ) - if( fun!=null && fun._compunit==cu ) funs.add(fun); - units.add(new com.seaofnodes.print.IRPrinter.Unit<>(cu._cname,cu._start,cu._stop,funs)); - } + var funs=new ArrayList<Node>(); + for( Node n : code._start._outputs ) if( n instanceof FunNode ) funs.add(n); + units.add(new com.seaofnodes.print.IRPrinter.Unit<>(null,null,null,funs)); return PRINT.program(code._start,code._stop,units); } + public static String _prettyPrint(CodeGen code) { + if( code._cfg==null ) return prettyPrint(code); + var blocks=new ArrayList<Node>(); + for( Node n : code._cfg ) blocks.add(n); + return PRINT.scheduled(blocks); + } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index 9ca0ec51..8dc6ea47 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -2,30 +2,22 @@ package com.seaofnodes.simple.print; import com.seaofnodes.graph.GraphAdapter; import com.seaofnodes.graph.GraphSnapshot.*; -import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.node.Node; import com.seaofnodes.simple.type.TypeMem; -/** Chapter 25's view of the IR, including partially constructed parse-time graphs. */ +/** Chapter 24's view of the IR; browser, transport and layout are shared. */ public class SimpleGraphAdapter extends GraphAdapter<Node> { - @Override protected boolean show(Node n) { - if( n != CodeGen.CODE.ZERO && n != CodeGen.CODE.XCTRL ) return true; - for( int i=0; i<n.nOuts(); i++ ) - if( n.out(i) != null ) return true; - return false; // The parser's keep is not a graph use. - } - @Override protected String edgeName(Node n, int i) { return n instanceof ScopeNode scope && i < scope._vars.size() - ? scope.var(i)._name : null; + ? scope._vars.get(i)._name : null; } @Override protected int edgeJump(Node n, int i) { return n instanceof CallEndNode && i>0 && n.in(i) instanceof ReturnNode ret ? ref(ret.fun()) : 0; } - @Override protected int projectionIndex(Node n) { return n instanceof Proj p ? p.idx() : -1; } + @Override protected int projectionIndex(Node n) { return n instanceof ProjNode p ? p._idx : n instanceof CProjNode p ? p._idx : -1; } @Override protected boolean folding(Node n) { if( n instanceof FunNode fun ) return fun.folding(); @@ -40,41 +32,26 @@ public class SimpleGraphAdapter extends GraphAdapter<Node> { @Override protected Kind kind(Node n) { if( n instanceof StopNode ) return Kind.STOP; if( n instanceof ScopeNode ) return Kind.SCOPE; - if( n instanceof StartCUNode ) return Kind.UNIT; if( n instanceof FunNode ) return Kind.FUN; - // Start inherits Loop for whole-program analysis, but is not a source loop. - if( n instanceof StartNode ) return Kind.START; + if( n instanceof StartNode ) return Kind.CTRL; if( n instanceof LoopNode ) return Kind.LOOP; if( n instanceof RegionNode ) return Kind.REGION; if( n instanceof PhiNode ) return Kind.PHI; - if( n instanceof CFGNode ) return Kind.CTRL; - return isMem(n) ? Kind.MEM : Kind.DATA; + return n instanceof CFGNode ? Kind.CTRL : isMem(n) ? Kind.MEM : Kind.DATA; } @Override protected Role role(Node n, int i) { - if( n instanceof ScopeNode || n instanceof ConstantNode || n instanceof FunPtrNode || - n instanceof CtrlNode || n instanceof XCtrlNode ) - return Role.ASSOC; - if( n instanceof PhiNode ) - return i == 0 ? Role.ASSOC : isMem(n) ? Role.MEM : Role.DATA; - if( n instanceof Proj ) + if( n instanceof ScopeNode || n instanceof ConstantNode || n instanceof CtrlNode || n instanceof XCtrlNode ) return Role.ASSOC; + if( n instanceof PhiNode ) return i == 0 ? Role.ASSOC : isMem(n) ? Role.MEM : Role.DATA; + if( n instanceof ProjNode || n instanceof CProjNode ) return n instanceof CFGNode ? Role.CTRL : isMem(n) ? Role.MEM : Role.DATA; - // StartCU inherits Region, but slot 0 really connects it to Start. - if( n instanceof StartCUNode && i == 0 ) return Role.CTRL; if( n instanceof RegionNode && i == 0 ) return Role.ASSOC; - // Preserve known slot roles even when an input is not attached/typed yet. if( i == 0 || n instanceof RegionNode || n instanceof StopNode ) return Role.CTRL; - if( n instanceof MemMergeNode || - (i == 1 && (n instanceof MemOpNode || n instanceof ReturnNode || n instanceof CallNode)) || - (n instanceof EscapeNode && i >= 2) ) return Role.MEM; + if( n instanceof MemMergeNode || (i == 1 && (n instanceof MemOpNode || n instanceof ReturnNode || n instanceof CallNode || n instanceof NewNode)) ) return Role.MEM; Node def = n.in(i); - if( def instanceof CFGNode ) return Role.CTRL; if( def != null && isMem(def) ) return Role.MEM; return Role.DATA; } - private boolean isMem(Node n) { - // These Phi subclasses are memory even before their first type computation. - return n instanceof MemPhiNode || n instanceof BulkMemPhiNode || n.isMem() || n._type instanceof TypeMem; - } + private boolean isMem(Node n) { return n.isMem() || n._type instanceof TypeMem; } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java index f2ed2f72..b2c5492e 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java @@ -7,7 +7,7 @@ import com.seaofnodes.simple.node.Node; import java.util.ArrayList; import java.io.IOException; -/** Chapter 25's compilation context; capture and event bookkeeping are shared. */ +/** Chapter 24's compilation context; capture and event bookkeeping are shared. */ public class SimpleGraphObserver extends GraphCapture<Node> { private CodeGen _code; public SimpleGraphObserver() { super(new SimpleGraphAdapter()); } 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 beaa719c..1396f1de 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.BAOS; import com.seaofnodes.simple.util.SB; import java.util.*; @@ -9,7 +8,6 @@ import java.util.*; * Represents a field in a struct. This is not a Type in the type system. */ public class Field extends Type { - static final Field[] EMPTY = new Field[0]; // The pair {fieldName,type} uniquely identifies a field. @@ -19,28 +17,29 @@ public class Field extends Type { public Type _t; // Unique memory alias, not sensibly part of a "type" but very convenient here. public int _alias; - // Field is only written in the constructor + // Field must be written to exactly once, no more, no less public boolean _final; - // Native C storage, outside the containing object's layout. - public boolean _extern; + // Field is final in the declaration; value is the same for all instances + // and will be moved to the class object + public boolean _one; private static final Ary<Field> FREE = new Ary<>(Field.class); - 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 ) { + private Field(String fname, Type type, int alias, boolean xfinal, boolean one ) { super(TFLD); init(fname,type,alias,xfinal,one); } + private Field init(String fname, Type type, int alias, boolean xfinal, boolean one ) { _fname = fname; _t = type; _alias = alias; _final = xfinal; - _extern = external; + _one = one; 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, boolean external ) { - return FREE.isEmpty() ? new Field(fname,type,alias,xfinal,external) : FREE.pop().init(fname,type,alias,xfinal,external); + private static Field malloc(String fname, Type type, int alias, boolean xfinal, boolean one ) { + return FREE.isEmpty() ? new Field(fname,type,alias,xfinal,one) : FREE.pop().init(fname,type,alias,xfinal,one); } // Malloc-from - public Field malloc( ) { return malloc(_fname,null,_alias,_final,_extern); } + public Field malloc( ) { return malloc(_fname,null,_alias,_final,_one); } @Override Field free(Type t) { Field f = (Field)t; assert !f.isFree() && !f._terned; @@ -53,31 +52,27 @@ public class Field extends Type { @Override boolean isFree() { return _t ==null; } // Make and intern with listed fields - public static Field make( String fname, Type type, int alias, boolean 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); + public static Field make( String fname, Type type, int alias, boolean xfinal, boolean one ) { + Field f = malloc(fname,type,alias,xfinal,one); 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,_extern); + return type == _t ? this : make(_fname,type,_alias,_final,_one); } public Field makeFrom( boolean xfinal ) { - return xfinal == _final ? this : make(_fname,_t,_alias,xfinal,_extern); + return xfinal == _final ? this : make(_fname,_t,_alias,xfinal,_one); } // Cycle-making-breaking void setType(Type t) { assert _t ==null; _t = t; } - public static final Field BOT = make(" ",Type.BOTTOM,-999,false); - 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 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); } + public static final Field BOT = make(" ",Type.BOTTOM,-999,true,false); + public static final Field TEST = make("test",Type.NIL,-2,false,false); + public static final Field TEST2 = make("test",Type.NIL,-2,true, false); + public static final Field FLT = make("flt ",TypeFloat.F32,-2,false,false); + public static void gather(ArrayList<Type> ts) { ts.add(TEST); ts.add(TEST2); ts.add(FLT); } @Override Field xmeet( Type that ) { Field fld = (Field)that; // Invariant @@ -86,17 +81,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; - if( _extern!=fld._extern ) return BOT; - return make(_fname, _t.meet(fld._t ), _alias, _final | fld._final,_extern); + assert _one ==fld._one ; + return make(_fname, _t.meet(fld._t ),_alias,_final | fld._final, _one); } @Override - Field xdual() { return malloc(_fname, _t.dual(),_alias,!_final,_extern); } + Field xdual() { return malloc(_fname, _t.dual(),_alias,!_final,_one); } @Override Field rdual() { if( _dual!=null ) return dual(); assert !_terned; - Field d = malloc(_fname,null,_alias,!_final,_extern); + Field d = malloc(_fname,null,_alias,!_final,_one); (_dual = d)._dual = this; // Cross link duals d._t = _t._terned ? _t.dual() : _t.rdual(); return d; @@ -104,51 +99,38 @@ 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,_extern); } - @Override Field _close( String name, HashMap<String, Type> TYPES ) { return makeFrom( _t._close(name, TYPES )); } - - @Override Type _upgradeType(HashMap<String,Type> TYPES) { - return makeFrom(_t._upgradeType(TYPES)); + @Override Field _makeRO() { return make(_fname, _t._makeRO(),_alias,true,_one); } + boolean isGLB2() { return _final && _t._isGLB(true); } + Field glb2() { + Type glb = _t._glb(true); + return (glb== _t && _final ) ? this : make(_fname,glb,_alias,true,_one); } + @Override Field _close(java.util.HashMap<String,Type> types) { return makeFrom( _t._close(types)); } // Override in subclasses - int hash() { return _fname.hashCode() ^ _t.hashCode() ^ _alias ^ (_final ? 1024 : 0) ^ (_extern ? 2048 : 0); } + int hash() { return _fname.hashCode() ^ _t.hashCode() ^ _alias ^ (_final ? 1024 : 0) ^ (_one ? 2048 : 0); } private boolean static_eq( Field f ) { - return _fname.equals(f._fname) && _alias==f._alias && _final==f._final && _extern==f._extern && _t !=null && f._t !=null; + return _fname.equals(f._fname) && _alias==f._alias && _final==f._final && _one==f._one && _t !=null && f._t !=null; } @Override boolean eq( Type t ) { Field f = (Field)t; return static_eq(f) && _t ==f._t; } @Override boolean cycle_eq(Type t) { - if( this==t ) return true; Field f = (Field)t; return static_eq(f) && _t.cycle_eq(f._t ); } - @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+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,bais.read()!=0); - } + @Override int nkids() { return 1; } + @Override Type at( int idx ) { return _t; } + @Override void set( int idx, Type t ) { _t = t; } @Override public SB _print( SB sb, BitSet visit, boolean html ) { - sb.p(_extern ? "extern C " : "").p(_final?"":"!").p(_fname).p(":").p(_alias).p(" : "); + sb.p(_final?"":"!").p(_one?"$":"").p(_fname).p(":").p(_alias).p(" : "); return _t ==null ? sb.p("---") : _t.print(sb,visit,html); } - @Override public String str() { return (_final?"":"!")+_fname; } + @Override public String str() { return (_final?"":"!")+(_one?"$":"")+_fname; } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/Type.java b/chapter25/src/main/java/com/seaofnodes/simple/type/Type.java index 980daf0f..9fad2767 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/type/Type.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/type/Type.java @@ -1,6 +1,5 @@ package com.seaofnodes.simple.type; -import com.seaofnodes.simple.codegen.GlobalBits; import com.seaofnodes.simple.util.*; import java.util.*; @@ -57,38 +56,35 @@ public class Type /*implements Cloneable*/ { static final byte TXCTRL = 3; // Ctrl flow top (mini-lattice: any-xctrl-ctrl-all) static final byte TNIL = 4; // low null of all flavors static final byte TXNIL = 5; // high or choice null - static final byte TSIMPLE = 5; // End of the Simple Types + static final byte TSIMPLE = 6; // End of the Simple Types + static final byte TSCALAR = 6; // All register/field values, excluding control and memory - static final byte TSCALAR = 6; // Integer, float, memory pointer or function pointer - static final byte TPTR = 7; // All nil-able pointer values + static final byte TPTR = 7; // All nil-able scalar values static final byte TINT = 8; // All Integers; see TypeInteger static final byte TFLT = 9; // All Floats ; see TypeFloat static final byte TCONARY =10; // Constant array static final byte TRPC =11; // Return Program Control (Return PC or RPC) + static final byte TTUPLE =12; // Tuples; finite collections of unrelated Types, kept in parallel - static final byte TCYCLIC =12; // Has internal pointers, needs recursive treatment - static final byte TMEMPTR =12; // Memory pointer to a struct type - static final byte TFUNPTR =13; // Function pointer; unique signature and code address (just a bit) - static final byte TMEM =14; // All memory (alias 0) or A slice of memory - with specific alias - static final byte TFLD =15; // Named fields in structs + static final byte TCYCLIC =13; // Has internal pointers, needs recursive treatment + static final byte TMEMPTR =13; // Memory pointer to a struct type + static final byte TFUNPTR =14; // Function pointer; unique signature and code address (just a bit) + static final byte TMEM =15; // All memory (alias 0) or A slice of memory - with specific alias static final byte TSTRUCT =16; // Structs; tuples with named fields - static final byte TTUPLE =17; // Tuples; finite collections of unrelated Types, kept in parallel - - static final byte TMAX =18; + static final byte TFLD =17; // Named fields in structs // Basic RTTI, useful for a lot of fast tests. public final byte _type; public boolean _terned; - public boolean is_simple() { return _type <= TSIMPLE; } - public boolean is_nokids() { return _type < TCYCLIC; } + public boolean is_simple() { return _type < TSIMPLE; } private static final String[] STRS = new String[]{"Bot","Top","Ctrl","~Ctrl","null","~nil"}; + static final int[] CNTS = new int[TFLD+1]; protected Type(byte type) { _type = type; // RTTI _uid = (char)UID++; // A unique ID for every type - if( UID > 32000 ) - type = type; assert _uid!=0; // Overflow + CNTS[type]++; } public static final Type BOTTOM = new Type( TBOT ).intern(); // ALL @@ -141,7 +137,7 @@ public class Type /*implements Cloneable*/ { // Factory method which interns "this" @SuppressWarnings("unchecked") - public <T extends Type> T intern() { + <T extends Type> T intern() { //assert check(); assert !_terned; // Do not ask for already-interned T t2 = (T)INTERN.get(this); @@ -222,16 +218,16 @@ public class Type /*implements Cloneable*/ { } // Overridden in subclasses; subclass can assume "this!=t" and java classes are same boolean eq(Type t) { return this==t; } - boolean cycle_eq(Type t) { assert is_nokids(); return _type==t._type && eq(t); } + boolean cycle_eq(Type t) { assert _type < TCYCLIC; return _type==t._type && eq(t); } // A pair of uids int pid( Type that ) { return _uid < that._uid ? (_uid<<16 | that._uid) : (that._uid<<16 | _uid); } // At/Set child at 'idx' to t - public Type at( int idx ) { throw Utils.TODO("Should not reach here: type has no children"); } - public void set( int idx, Type t ) { throw Utils.TODO("Should not reach here: type has no children"); } - public int nkids() { return 0; } // Number of kids + Type at ( int idx ) { throw Utils.TODO(); } + void set( int idx, Type t ) { throw Utils.TODO(); } + int nkids() { assert _type < TTUPLE; return 0; } // Number of kids // Clear and re-insert the basic Type INTERN table @@ -260,7 +256,6 @@ public class Type /*implements Cloneable*/ { // ---------------------------------------------------------- public final Type meet(Type t) { - assert _terned && t._terned; // Shortcut for the self case if( t == this ) return this; // Same-type is always safe in the subclasses @@ -271,14 +266,12 @@ public class Type /*implements Cloneable*/ { // Reverse; xmeet 2nd arg is never "is_simple" and never equal to "this". if( is_simple() ) return this.xmeet(t ); if( t.is_simple() ) return t .xmeet(this); - // TypeConAry meet its element type is OK + // Constant arrays meet their integer element types. if( this instanceof TypeConAry tcon && t instanceof TypeInteger ti ) return tcon.ymeet(ti); if( t instanceof TypeConAry tcon && this instanceof TypeInteger ti ) return tcon.ymeet(ti); - // Two distinct scalar families meet at the scalar envelope. if( this instanceof TypeScalar scalar0 && t instanceof TypeScalar scalar1 ) return scalar0.smeet(scalar1); - // Mixing 2 unrelated types - return Type.BOTTOM; + return Type.BOTTOM; // Mixing 2 unrelated types } // Compute meet right now. Overridden in subclasses. @@ -293,7 +286,7 @@ public class Type /*implements Cloneable*/ { // RHS TypeNil vs NIL/XNIL if( _type==TNIL || _type==TXNIL ) { if( t instanceof TypeNil ptr ) return _type==TNIL ? ptr.meet0() : ptr.meetX(); - if( t instanceof TypeScalar scalar ) { + if( t instanceof TypeScalar ) { if( t._type==TSCALAR ) return t.isHigh() ? this : t; return TypeScalar.BOT; } @@ -322,7 +315,7 @@ public class Type /*implements Cloneable*/ { case TXCTRL-> TCTRL; case TNIL -> TXNIL; case TXNIL -> TNIL; - default -> throw Utils.TODO("Should not reach here: unknown simple type"); + default -> throw Utils.TODO(); // Should not reach here }); } @@ -336,13 +329,24 @@ public class Type /*implements Cloneable*/ { } // True if this "isa" t; e.g. 17 isa TypeInteger.BOT - public boolean isa( Type t ) { return meet(t)==t; } + // Applies for pessimistic case + public boolean isa( Type t ) { return meet(t)==t; } + + // True if this "isa" t up to named structures + public boolean shallowISA( Type t ) { return isa(t); } - public Type nonZero() { return this==NIL ? TypePtr.XNPTR : TypePtr.NPTR; } + public Type nonZero() { return TypePtr.NPTR; } // Make a zero version of this type, 0 for integers and null for pointers. public Type makeZero() { return Type.NIL; } + // Writable array slots are invariant in their element access permissions. + // Otherwise an alias could insert a readonly pointer into a writable slot. + public boolean accessISA(Type dst) { return true; } + + // Is forward-reference + public boolean isFRef() { return false; } + // Cap at limits public Type oob() { return isHigh() ? TOP : BOTTOM; } @@ -350,7 +354,7 @@ public class Type /*implements Cloneable*/ { // Cyclic types! Flag the start of a cyclic type by putting a sentinel in // VISIT (and eventually clearing VISIT when done). Then do a normal - // recursive descent visit of all types; this will accumulate types without + // recursive descent visit of all types; this will accumilate types without // interning them. When done we have to visit the possibly cyclic type and // intern the whole cycle, possibly hitting the entire cycle on a prior // interned cycle. Any sub-part might also be interned, including whole @@ -363,8 +367,7 @@ public class Type /*implements Cloneable*/ { // "C0<->D0" cycle. Type recurOpen() { assert VISIT.isEmpty(); VISIT.put(0L,BOTTOM); return this; } - Type recurClose() { return recurClose(null); } - Type recurClose(Type[] types) { + Type recurClose() { VISIT.remove(0L); // Just ignore the sentinel Ary<Type> ts = new Ary<>(Type.class); ts.addAll(VISIT.values()); @@ -388,9 +391,6 @@ public class Type /*implements Cloneable*/ { // Upgrade the result Type rez = INTERN.get(this); assert rez!=null; - if( types!=null ) - for( int i=0; i<types.length; i++ ) - types[i] = INTERN.get(types[i]); // Pass#4?: Free up any created-but-already interned for( Type t : FREES ) @@ -432,12 +432,10 @@ public class Type /*implements Cloneable*/ { free(this); } - Type free(Type free) { - return this; - } + Type free(Type free) { return this; } boolean isFree() { return false; } - final <T extends Type> T delayFree(Type free) { + <T extends Type> T delayFree(Type free) { assert !free._terned; FREES.push(free); return (T)this; @@ -446,7 +444,7 @@ public class Type /*implements Cloneable*/ { final Type install() { if( this instanceof TypeStruct ) { Type x = _intern(); // Stop the recursion - if( x!=this ) return x.delayFree(this); + assert x==this; } int nkids = nkids(); for( int i=0; i<nkids; i++ ) { @@ -459,7 +457,7 @@ public class Type /*implements Cloneable*/ { } } } - return this instanceof TypeStruct || _terned ? this : _intern(); + return this instanceof TypeStruct ? this : _intern(); } // Strict constant values, things on the lattice centerline. @@ -468,12 +466,8 @@ public class Type /*implements Cloneable*/ { boolean _isConstant() { return _type==TNIL; } // Are all reachable struct Fields are final? - // Writable array slots are invariant in their element access permissions. - // Otherwise an alias could insert a readonly pointer into a writable slot. - public boolean accessISA(Type dst) { return true; } - public final boolean isFinal() { return recurClose(recurOpen()._isFinal()); } - boolean _isFinal() { assert is_nokids(); return true; } + boolean _isFinal() { assert _type < TCYCLIC; return true; } public final Type makeRO() { if( isFinal() ) return this; @@ -481,154 +475,30 @@ public class Type /*implements Cloneable*/ { } Type _makeRO() { return this; } - /** Declared shape used when a value is published into mutable memory. */ - public final Type makeStorage() { - return recurOpen()._makeStorage().recurClose(); - } - Type _makeStorage() { assert is_simple(); return BOTTOM; } - - // Bulk close-over all recursive types - public static TypeStruct[] closeOver(TypeStruct[] ts, HashMap<String,Type> TYPES) { - BOTTOM.recurOpen(); - for( TypeStruct t : ts ) - t._close(null, TYPES); - for( int i=0; i<ts.length; i++ ) { - // Parser only tracks the mutable name of arrays, so the closeOver - // returns that one. However, internal types might have immutable - // versions. - String name = ts[i].isAry() ? (ts[i]._name+"!").intern() : ts[i]._name; - ts[i] = (TypeStruct)VISIT.get(name); - } - BOTTOM.recurClose(ts); - return ts; - } - - Type _close( String name, HashMap<String, Type> TYPES ) { return this; } - public Type widen() { return this; } - - // Replace recursively all TypeBuilders with cyclic TypeStructs - public Type upgradeType(HashMap<String,Type> TYPES) { - return recurOpen()._upgradeType(TYPES).recurClose(); - } - Type _upgradeType(HashMap<String,Type> TYPES) { assert is_nokids(); return this; } - - - // Recursively gather all types - public void gather(HashMap<Type,Integer> types ) { - if( types.containsKey(this) ) return; // TEST - types.put(this,types.size()); // SET - int nkids = nkids(); - for( int i=0; i<nkids; i++ ) - at(i).gather(types); - } - - // Compute serialization byte tag compression space - final static int[] TAGOFFS = new int[TMAX+1]; - public static int[] TAGOFFS() { - if( TAGOFFS[TMAX-1]==0 ) - // One entry per unique tag-space, using the BOT value from that - // tag-space except for the first tag-space which uses 1 tag for - // multiple Types instead of 1 Type having multiple tags. Has to - // be ordered to match _type enum. - for( Type t : new Type[]{Type.XNIL,TypeScalar.BOT,TypePtr.PTR,TypeInteger.BOT,TypeFloat.F64,TypeConAryB.ABC,TypeRPC.BOT,TypeMemPtr.BOT,TypeFunPtr.BOT,TypeMem.BOT,Field.BOT,TypeStruct.BOT,TypeTuple.BOT} ) - TAGOFFS[t._type+1] = TAGOFFS[t._type] + t.TAGOFF(); - assert TAGOFFS[TMAX] <= 255; - return TAGOFFS; - } - // Reserve 6 tags, 0-5, for plain Types - int TAGOFF() { assert is_simple(); return 6; } - - public void packed( BAOS baos, HashMap<String,Integer> strs ) { - assert is_simple(); - baos.write(_type); - } - static Type packed( int tag ) { - return switch( tag ) { - case TBOT -> BOTTOM; - case TTOP -> TOP; - case TCTRL -> CONTROL; - case TXCTRL->XCONTROL; - case TNIL -> NIL; - default -> throw Utils.TODO("Should not reach here: unknown simple type tag"); + // Compute greatest lower bound in the lattice. If values are in memory, + // ints and floats cannot widen. + public final boolean isGLB(boolean mem) { return recurClose(recurOpen()._isGLB(mem)); }; + boolean _isGLB(boolean mem) { + return switch(_type) { + case TBOT -> false; + case TNIL -> false; + case TCTRL -> true; + case TXCTRL -> false; + case TXNIL -> false; + case TTOP -> false; + default -> throw Utils.TODO(); }; } - - // Produce a type with null child types - static Type packed( BAOS bais, String[] strs ) { - int x = bais.read(); - int type = Arrays.binarySearch( TAGOFFS, x ); - if( type < 0 ) type = -(type+1)-1; - int off = x - TAGOFFS[type]; - return switch( type ) { - case 0, 1, 2, 3, 4, 5 -> Type.packed( off ); - case TSCALAR -> TypeScalar .packed( off ); - case TPTR -> TypePtr .packed( off ); - case TINT -> TypeInteger.packed( off, bais ); - case TFLT -> TypeFloat .packed( off, bais ); - case TCONARY -> TypeConAry .packed( off, bais ); - case TRPC -> TypeRPC .packed( off, bais ); - case TMEMPTR -> TypeMemPtr .packed( off, bais ); - case TFUNPTR -> TypeFunPtr .packed( off, bais ); - case TMEM -> TypeMem .packed( off, bais ); - case TFLD -> Field .packed( off, bais, strs ); - case TSTRUCT -> TypeStruct .packed( off, bais, strs ); - case TTUPLE -> TypeTuple .packed( off, bais ); - default -> throw Utils.TODO("Should not reach here: unknown packed type tag"); - }; - } - - // Read a packed Type array - public static Type[] packed( BAOS bais, String[] strs, int ntypes, HashMap<String,Type> existingTypes, - GlobalBits fileAliases, GlobalBits aliases, - GlobalBits fileFidxs , GlobalBits fidxs , - GlobalBits fileRpcs , GlobalBits rpcs ) { - Type[] types = new Type[ntypes]; - // Read Types in ID# order, no children - for( int i=0; i<ntypes; i++ ) - types[i] = Type.packed(bais,strs); - // Start possibly recursive type collection - BOTTOM.recurOpen(); - // Read Types in ID# order, only children - for( int i=0; i<ntypes; i++ ) { - VISIT.put(types[i]._uid,types[i]); - int nkids = types[i].nkids(); - for( int j=0; j<nkids; j++ ) - types[i].set(j,types[bais.packed4()]); - } - // Remap file-local aliases/fidxs/rpcs into this compilation unit's - // local dense indexes via their global identities. - for( int i=0; i<ntypes; i++ ) { - if( types[i] instanceof Field fld ) { - if( fld._alias >= GlobalBits.RESERVED ) - fld._alias = aliases.map(fileAliases,fld._alias); - } - if( types[i] instanceof TypeFunPtr tfp ) - tfp._fidxs = remapBits(fileFidxs,fidxs,tfp._fidxs,GlobalBits.RESERVED); - if( types[i] instanceof TypeRPC rpc ) - rpc._rpcs = remapBits(fileRpcs,rpcs,rpc._rpcs,GlobalBits.RESERVED); - if( types[i] instanceof TypeMem mem ) { - if( mem._alias >= GlobalBits.RESERVED ) - mem._alias = aliases.map(fileAliases,mem._alias); - mem._escFs = remapBits(fileFidxs ,fidxs ,mem._escFs,GlobalBits.RESERVED); - mem._escAs = remapBits(fileAliases,aliases,mem._escAs,GlobalBits.RESERVED); - } - } - - // Intern them all at once - BOTTOM.recurClose(types); - return types; - } - - private static int[] remapBits(GlobalBits fileBits, GlobalBits bits, int[] fileLocals, int firstMapped ) { - if( fileLocals == XInt.EMPTY || fileLocals == XInt.FULL ) - return fileLocals; - assert !XInt.isHigh(fileLocals); - int[] locals = XInt.EMPTY; - for( int fileLocal = XInt.next(fileLocals,-1); fileLocal >=0; fileLocal = XInt.next(fileLocals,fileLocal) ) - locals = XInt.make(locals,fileLocal < firstMapped ? fileLocal : bits.map(fileBits,fileLocal)); - return locals; + public final Type glb(boolean mem) { + if( isGLB(mem) ) return this; + Type glb = recurOpen()._glb(mem).recurClose(); + assert this.isa(glb); + return glb; } + Type _glb(boolean mem) { assert is_simple(); return Type.BOTTOM; } + Type _close(HashMap<String,Type> types) { return this; } + public Type widen() { return this; } // ---------------------------------------------------------- @@ -663,4 +533,11 @@ public class Type /*implements Cloneable*/ { // This is used by error messages, and is a shorted print. public String str() { return STRS[_type]; } + + // Closing declarations changes the meaning of forward shapes. Upgrade + // the whole graph together before starting the pessimistic fixed point. + public final Type upgradeType(HashMap<String,Type> types) { + return recurOpen()._upgradeType(types).recurClose(); + } + Type _upgradeType(HashMap<String,Type> types) { return this; } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeConAry.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeConAry.java index c9093e3b..d7ce8503 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeConAry.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeConAry.java @@ -1,9 +1,8 @@ package com.seaofnodes.simple.type; -import com.seaofnodes.simple.util.BAOS; +import java.io.ByteArrayOutputStream; import com.seaofnodes.simple.util.Utils; import java.util.ArrayList; -import java.util.HashMap; /** * Represents a constant array of primitives @@ -56,7 +55,8 @@ public abstract class TypeConAry<A> extends TypeScalar { @Override public boolean isHigh() { return this==TOP; } @Override boolean _isConstant() { return true; } - @Override Type _makeStorage() { return this; } + @Override Type _glb(boolean mem) { return this; } + @Override boolean _isGLB(boolean mem) { return true; } // Meet-over-elements type public Type elem() { @@ -74,17 +74,6 @@ public abstract class TypeConAry<A> extends TypeScalar { public abstract long at8(int idx); public abstract int len(); @Override public abstract int log_size(); - public void write( BAOS baos ) { throw Utils.TODO("Should not reach here: abstract constant array cannot be written"); } + public void write( ByteArrayOutputStream baos ) { throw Utils.TODO("Should not reach here: abstract constant array cannot be written"); } - // Reserve tags for u8 array - @Override int TAGOFF() { return 1; } - @Override public void packed( BAOS baos, HashMap<String,Integer> strs ) { - assert log_size()==0 && !_any; - baos.write(TAGOFFS[TCONARY]+0); - baos.packed4(len()); - baos.write((byte[])_ary); - } - static TypeConAry packed( int tag, BAOS bais ) { - return TypeConAryB.make(bais.read(new byte[bais.packed4()])); - } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java index 5877c309..095cdeca 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java @@ -1,6 +1,6 @@ package com.seaofnodes.simple.type; -import com.seaofnodes.simple.util.BAOS; +import java.io.ByteArrayOutputStream; import java.util.Arrays; public class TypeConAryB extends TypeConAry<byte[]> { @@ -16,5 +16,5 @@ public class TypeConAryB extends TypeConAry<byte[]> { @Override public String str() { return "[\""+new String(_ary)+"\"]"; } @Override boolean eq(Type t) { return t instanceof TypeConAryB ary && _widen==ary._widen && Arrays.equals(_ary,ary._ary); } @Override int hash() { return Arrays.hashCode(_ary); } - @Override public void write( BAOS baos ) { baos.write(_ary); } + @Override public void write( ByteArrayOutputStream baos ) { baos.write(_ary,0,_ary.length); } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeFloat.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeFloat.java index d410e9cf..bb00ee91 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeFloat.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeFloat.java @@ -1,11 +1,9 @@ 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 com.seaofnodes.simple.util.Utils; +import com.seaofnodes.simple.util.Ary; import java.util.ArrayList; -import java.util.HashMap; /** * Float Type @@ -53,7 +51,7 @@ public class TypeFloat extends TypeScalar { case 0 -> ""+_con+((float)_con==_con ? "f" : ""); case 32 -> "f32"; case 64 -> "flt"; - default -> throw Utils.TODO("Should not reach here: unknown float size"); + default -> throw Utils.TODO(); }; } private boolean isF32() { return ((float)_con)==_con; } @@ -90,36 +88,16 @@ public class TypeFloat extends TypeScalar { return _isConstant() ? this : new TypeFloat((byte)-_sz,0); // Constants are a self-dual } - @Override boolean _isConstant() { return _sz==0; } - - @Override Type _makeStorage() { + @Override boolean _isGLB(boolean mem) { return _glb(mem)==this; } + @Override Type _glb(boolean mem) { + if( !mem ) return F64; if( _isConstant() ) return isF32() ? F32 : F64; return isHigh() ? dual() : this; } - @Override public Type makeZero() { return FZERO; } - - // Reserve tags for F64,F32,constant - @Override int TAGOFF() { return 3; } - @Override public void packed( BAOS baos, HashMap<String,Integer> strs ) { - if( this==F64 ) baos.write(TAGOFFS[_type] + 0); - else if( this==F32 ) baos.write(TAGOFFS[_type] + 1); - else { - assert isConstant(); - baos.write(TAGOFFS[_type] + 2); - baos.packed8(Double.doubleToLongBits(_con)); - } - } - - static Type packed( int tag, BAOS bais ) { - return switch( tag ) { - case 0 -> F64; - case 1 -> F32; - case 2 -> constant(Double.longBitsToDouble(bais.packed8())); - default -> throw Utils.TODO("Should not reach here: unknown float type tag"); - }; - } + @Override boolean _isConstant() { return _sz==0; } + @Override public Type makeZero() { return FZERO; } @Override int hash() { return (int)(Double.hashCode(_con) ^ _sz ^ (1<<17)); } @Override diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java index 56f47bff..602aaaae 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.type; -import com.seaofnodes.simple.util.*; +import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.util.Utils; +import com.seaofnodes.simple.util.Ary; import java.util.*; /** @@ -19,25 +21,27 @@ public class TypeFunPtr extends TypeNil { // A TypeFunPtr is Signature and a set of functions. public Type[] _sig; public Type _ret; - boolean _open; // Extra args are true:scalar BOT, false:scalar TOP - // Set of functions. Bit zero is used for mutable/immutable safety. Sign - // bit repeats infinitely. - public int[] _fidxs; + // TODO: + boolean _open; // Extra args are true:BOTTOM, false:TOP + // Cheesy easy implementation for a small set; 1 bit per unique function + // within the same Type[]. Can be upgraded to a BitSet for larger classes + // of functions. Negative means "these 63 concrete bits plus infinite + // unknown more" + public long _fidxs; // 63 unique functions per signature private static final Ary<TypeFunPtr> FREE = new Ary<>(TypeFunPtr.class); - private TypeFunPtr(byte nil) { super(TFUNPTR,nil); } - private TypeFunPtr init(byte nil, boolean open, Type[] sig, Type ret, int[] fidxs ) { + private TypeFunPtr(byte nil, boolean open, Type[] sig, Type ret, long fidxs) { super(TFUNPTR,nil); init(nil,open,sig,ret,fidxs); } + private TypeFunPtr init(byte nil, boolean open, Type[] sig, Type ret, long fidxs ) { assert sig != null; _nil = nil; _open = open; _sig = sig; _ret = ret; - assert _fidxs == null; - _fidxs= XInt.intern(fidxs); + _fidxs= fidxs; return this; } - static TypeFunPtr malloc( byte nil, boolean open, Type[] sig, Type ret, int[] fidxs ) { - return (FREE.isEmpty() ? new TypeFunPtr(nil) : FREE.pop()).init(nil,open,sig,ret,fidxs); + static TypeFunPtr malloc( byte nil, boolean open, Type[] sig, Type ret, long fidxs ) { + return FREE.isEmpty() ? new TypeFunPtr(nil,open,sig,ret,fidxs) : FREE.pop().init(nil,open,sig,ret,fidxs); } @Override TypeFunPtr free(Type t) { TypeFunPtr fun = (TypeFunPtr)t; @@ -46,107 +50,72 @@ public class TypeFunPtr extends TypeNil { fun._ret = null; fun._dual = null; fun._hash = 0; - fun._fidxs = null; FREE.push(fun); return this; } @Override boolean isFree() { return _sig==null; } // All fields directly listed - public static TypeFunPtr make( byte nil, boolean open, Type[] sig, Type ret, int[] fidxs ) { - TypeFunPtr fun = malloc(nil,open,sig,ret,fidxs); - TypeFunPtr f2 = fun.intern(); - if( f2==fun ) return fun; - return VISIT.isEmpty() ? f2.free(fun) : f2.delayFree(fun); - } - // Trim trailing args which match the default. - private static TypeFunPtr makeAdjusted( byte nil, boolean open, Type[] sig, Type ret, int[] fidxs ) { - Type def = open ? TypeScalar.BOT : TypeScalar.TOP; + public static TypeFunPtr make( byte nil, boolean open, Type[] sig, Type ret, long fidxs ) { + // Trim trailing args which match the implicit default. + Type def = open ? Type.BOTTOM : Type.TOP; int nargs = sig.length; while( nargs > 0 && sig[nargs-1] == def ) nargs--; if( nargs != sig.length ) - sig = nargs == 0 ? ARG_EMPTY : Arrays.copyOf(sig,nargs); - return make(nil,open,sig,ret,fidxs); - } - public static TypeFunPtr make( boolean nil, Type[] sig, Type ret ) { return makeAdjusted((byte)(nil ? 3 : 2),true,sig,ret, XInt.FULL); } - public static TypeFunPtr make1( byte nil, boolean open, Type[] sig, Type ret, int fidx ) { - return makeAdjusted(nil,open,sig,ret,XInt.make(fidx)); + sig = nargs == 0 ? TEMPTY : Arrays.copyOf(sig,nargs); + TypeFunPtr fun = malloc(nil,open,sig,ret,fidxs); + TypeFunPtr f2 = fun.intern(); + if( f2==fun ) return fun; + return VISIT.isEmpty() ? f2.free(fun) : f2.delayFree(fun); } - + public static TypeFunPtr make( boolean nil, boolean open, Type[] sig, Type ret ) { return make((byte)(nil ? 3 : 2),open,sig,ret,-1); } @Override TypeFunPtr makeFrom( byte nil ) { return nil ==_nil ? this : make( nil,_open,_sig,_ret, _fidxs); } public TypeFunPtr makeFrom( Type ret ) { return ret ==_ret ? this : make( _nil,_open,_sig, ret, _fidxs); } - public TypeFunPtr makeFrom( int fidx ) { return make1(_nil,_open,_sig,_ret,fidx); } - public TypeFunPtr makeFrom( Type arg, int idx ) { - // Alter named arg - Type[] sig = _sig.clone(); - sig[idx] = arg; - return make(_nil,_open,sig,_ret,_fidxs); - } - @Override public Type nonZero() { return makeFrom((byte)(_nil <= 1 ? 1 : 2)); } + public TypeFunPtr makeFrom( int fidx ) { return make((byte)2, _open, _sig,_ret,1L<<fidx ); } + public static final Type[] TEMPTY = new Type[0]; static final Type[] TINT = new Type[]{TypeInteger.BOT}; static final Type[] TINTMEM = new Type[]{TypeInteger.BOT,TypeFloat.F32}; static final Type[] TINTINT = new Type[]{TypeInteger.BOT,TypeInteger.BOT}; - public static TypeFunPtr BOT = make((byte)3,true,TEMPTY,Type.BOTTOM, XInt.FULL); - public static TypeFunPtr TEST = make((byte)2,true,TINTMEM,TypeInteger.BOT, XInt.SET1); - public static TypeFunPtr TEST0 = make((byte)3,true,TINTMEM,TypeInteger.BOT, XInt.SET3); - public static TypeFunPtr MAIN = make((byte)3,true,TINT ,Type.BOTTOM, XInt.FULL); // Main can return anything - public static TypeFunPtr CALLOC= make((byte)3,true,TINTINT,TypeMemPtr .BOT, XInt.FULL); + public static TypeFunPtr BOT = make((byte)3,true ,TEMPTY,Type.BOTTOM,-1); + public static TypeFunPtr TEST = make((byte)2,false,TINTMEM,TypeInteger.BOT, 1); + public static TypeFunPtr TEST0 = make((byte)3,false,TINTMEM,TypeInteger.BOT, 3); + public static TypeFunPtr MAIN = make((byte)3,false,TINT ,Type.BOTTOM,-1); // Main can return anything + public static TypeFunPtr CALLOC= make((byte)3,false,TINTINT,TypeMemPtr .BOT,-1); public static void gather(ArrayList<Type> ts) { - ts.add(TEST); - ts.add(TEST0); - ts.add(BOT); - ts.add(MAIN); - ts.add(CALLOC); - ts.add(make((byte)2,false,TINT,TypeInteger.I32, XInt.SET1)); - ts.add(make((byte)2,true ,TINT,TypeInteger.BOT, XInt.SET3)); - ts.add(make1((byte)2,true, - new Type[]{TypeInteger.BOT}, - TypeInteger.BOT, 65)); - ts.add(make1((byte)2,true, - new Type[]{TypeInteger.BOT, TypeScalar.BOT}, - TypeInteger.BOT, 65)); + ts.add(TEST); ts.add(TEST0); ts.add(BOT); ts.add(MAIN); ts.add(CALLOC); // Explicit trailing defaults must agree with the implicit argument tail. for( boolean open : new boolean[]{false,true} ) { - Type def = open ? TypeScalar.BOT : TypeScalar.TOP; - ts.add(make1((byte)2,open,TINT,TypeInteger.I32,1)); - ts.add(make1((byte)2,open,new Type[]{TypeInteger.BOT,def,def},TypeInteger.I32,1)); - ts.add(make1((byte)2,open,new Type[]{def,def},TypeInteger.I32,1)); - ts.add(make1((byte)2,open,new Type[]{def,TypeInteger.BOT},TypeInteger.I32,1)); + Type def = open ? Type.BOTTOM : Type.TOP; + ts.add(make((byte)2,open,TINT,TypeInteger.I32,1)); + ts.add(make((byte)2,open,new Type[]{TypeInteger.BOT,def,def},TypeInteger.I32,1)); + ts.add(make((byte)2,open,new Type[]{def,def},TypeInteger.I32,1)); + ts.add(make((byte)2,open,new Type[]{def,TypeInteger.BOT},TypeInteger.I32,1)); } } - private static final Type[] ARG_EMPTY = new Type[0]; @Override Type xmeet(Type t) { TypeFunPtr that = (TypeFunPtr) t; - boolean open = _open | that._open; - Type def0 = this._open ? TypeScalar.BOT : TypeScalar.TOP; - Type def1 = that._open ? TypeScalar.BOT : TypeScalar.TOP; - Type def = open ? TypeScalar.BOT : TypeScalar.TOP; - - int nargs = Math.max(_sig.length,that._sig.length); - Type[] args = new Type[nargs]; - - for( int i=0; i<nargs; i++ ) { - Type t0 = i < this._sig.length ? this._sig[i] : def0; - Type t1 = i < that._sig.length ? that._sig[i] : def1; - args[i] = t0.meet(t1); - } - - while( nargs>0 && args[nargs-1] == def ) - nargs--; - - if( nargs != args.length ) - args = nargs == 0 ? ARG_EMPTY : Arrays.copyOf(args,nargs); - - return make(xmeet0(that), open, args, _ret.meet(that._ret), XInt.meet(_fidxs,that._fidxs)); + // if equal, no matters. + // if short is BOT, chop ; recurPre on short. + // if short is TOP, copy long; recurPre on long. + TypeFunPtr min = _sig.length < that._sig.length ? this : that; + if( _sig.length != that._sig.length && (min._open ^ min==this) ) + return that.xmeet(this); + // Recurse all common fields + Type[] args = new Type[_sig.length]; + for( int i=0; i<min._sig.length; i++ ) + args[i] = _sig[i].meet(that._sig[i]); + // Handle extra args + for( int i=min._sig.length; i<_sig.length; i++ ) + args[i] = _sig[i]; + return make(xmeet0(that),_open | that._open, args, _ret.meet(that._ret), _fidxs | that._fidxs); } - static Type[] xmeet( Type[] ts0, Type[] ts1) { if( ts0==ts1 ) return ts0; assert ts0.length==ts1.length; @@ -156,7 +125,7 @@ public class TypeFunPtr extends TypeNil { return ts; } - @Override TypeFunPtr xdual() { return malloc(dual0(), !_open, xdual(_sig), _ret.dual(), XInt.dual(_fidxs)); } + @Override TypeFunPtr xdual() { return malloc(dual0(), !_open, xdual(_sig), _ret.dual(), ~_fidxs); } static Type[] xdual(Type[] ts) { Type[] dual = new Type[ts.length]; for( int i=0; i<ts.length; i++ ) @@ -168,7 +137,7 @@ public class TypeFunPtr extends TypeNil { if( _dual!=null ) return dual(); assert !_terned; Type[] sigs = new Type[_sig.length]; - TypeFunPtr d = malloc(dual0(),!_open,sigs,null, XInt.dual(_fidxs)); + TypeFunPtr d = malloc(dual0(),!_open,sigs,null,~_fidxs); (_dual = d)._dual = this; // Cross link duals // Return rdual d._ret = _ret._terned ? _ret.dual() : _ret.rdual(); @@ -178,96 +147,53 @@ public class TypeFunPtr extends TypeNil { return d; } - // RHS is NIL; deep-dual when crossing the centerline - @Override public Type meet0() { - if( _nil==3 ) return this; - if( _nil==2 ) return make((byte)3,_open,_sig,_ret,_fidxs); + // RHS is NIL; do not deep-dual when crossing the centerline + @Override public Type meet0() { return _nil==3 ? this : make((byte)3,_open,_sig,_ret,_fidxs); } - return make((byte)3,_open,_sig,_ret,_fidxs); - } - - @Override public boolean isHigh() { return _nil <= 1 || (_nil==2 && _fidxs== XInt.EMPTY); } + @Override public boolean isHigh() { return _nil <= 1 || (_nil==2 && _fidxs==0); } - @Override public boolean _isConstant() { return (_nil==2 && XInt.isConstant(_fidxs)) || (_nil==3 && _fidxs== XInt.EMPTY); } + @Override public boolean _isConstant() { return (_nil==2 && Long.bitCount(_fidxs)==1) || (_nil==3 && _fidxs==0); } @Override boolean _isFinal() { return true; } - @Override TypeFunPtr _makeStorage() { return this; } - - @Override TypeFunPtr _close( String name, HashMap<String, Type> TYPES ) { + @Override boolean _isGLB(boolean mem) { return true; } + @Override TypeFunPtr _glb(boolean mem) { return this; } + @Override TypeFunPtr _close(java.util.HashMap<String,Type> types) { Type[] sig = new Type[_sig.length]; - TypeFunPtr fun = malloc(_nil,true,sig,null,_fidxs); + TypeFunPtr fun = malloc(_nil,false,sig,null,_fidxs); // Now start the recursion - fun._ret = _ret._close(name, TYPES ); + fun._ret = _ret._close(types); for( int i=0; i<sig.length; i++ ) - sig[i] = _sig[i]._close(name, TYPES ); - return fun; - } + sig[i] = _sig[i]._close(types); - // Replace recursively all TypeBuilders with cyclic TypeStructs - @Override Type _upgradeType(HashMap<String,Type> TYPES) { - Type[] sig = new Type[_sig.length]; - TypeFunPtr fun = malloc(_nil,_open,sig,null,_fidxs); - // Now start the recursion - fun._ret = _ret._upgradeType(TYPES); - for( int i=0; i<sig.length; i++ ) - sig[i] = _sig[i]._upgradeType(TYPES); return fun; } @Override public int log_size() { return 2; } // (1<<2)==4-byte pointers public Type arg(int i) { return _sig[i]; } - public int[] fidxs() { return _fidxs; } + public long fidxs() { return _fidxs; } public Type ret() { return _ret; } public int nargs() { return _sig.length; } - public int fidx() { return XInt.onlyBit(_fidxs); } + public int fidx() { assert Long.bitCount(_fidxs)==1; return Long.numberOfTrailingZeros(_fidxs); } public int nfcns() { - if( XInt.isHigh(_fidxs) ) return Integer.MAX_VALUE; // Infinite choices - return XInt.bitCount(_fidxs); + if( _fidxs < 0 ) return Integer.MAX_VALUE; // Infinite choices + return Long.bitCount(_fidxs); } // Used to walk over type structures - @Override public int nkids() { return _sig.length+1; } - @Override public Type at( int idx ) { + @Override int nkids() { return _sig.length+1; } + @Override Type at( int idx ) { return idx == _sig.length ? _ret : _sig[idx]; } - @Override public void set( int idx, Type t ) { + @Override void set( int idx, Type t ) { if( idx == _sig.length ) _ret = t; else _sig[idx] = t; } - // Reserve tags for null/not and open/close, one fidx/bitset, 0-6 args - // Tags 0-5 - not,close,1 fidx, 0-5 args (+fidx) - // Tag 6 - not ,close+fidxs+nargs - // Tag 7 - null,close+fidxs+nargs - @Override int TAGOFF() { return 8; } - @Override public void packed( BAOS baos, HashMap<String,Integer> strs ) { - assert _open; // Open: Passing extra args is fine, and extras ignored. - if( _nil==2 && nargs()<6 && nfcns() == 1 && fidx() < 64 ) { - baos.write(TAGOFFS[_type] + nargs()); - baos.packed2(fidx()); - } else { - baos.write(TAGOFFS[_type] + 6 + (_nil==2 ? 0 : 1)); - baos.packed1(nargs()); - XInt.packed(baos,_fidxs); - } - } - static TypeFunPtr packed( int tag, BAOS bais ) { - if( tag < 6 ) { - int fidx = bais.read(); - assert fidx < 64; // Need a larger fidx space - return malloc((byte)2,true,new Type[tag],null, XInt.make(fidx)); - } - byte nil = (byte)(tag==6 ? 2 : 3); - int nargs = bais.packed1(); - int[] fidxs = XInt.packed(bais); - return malloc(nil,true,new Type[nargs],null,fidxs); - } - @Override int hash() { - long hash = _ret.hashCode() ^ XInt.hash(_fidxs) ^ super.hash(); + long hash = _ret.hashCode() ^ _fidxs ^ super.hash(); for( Type arg : _sig ) hash = hash*19 ^ arg.hashCode(); return Utils.fold(hash); @@ -316,26 +242,39 @@ public class TypeFunPtr extends TypeNil { return "{"+printFIDX()+"}"; } - @Override SB _print(SB sb, BitSet visit, boolean html ) { if( this==BOT ) return sb.p("FunBot"); if( this==BOT.dual() ) return sb.p("FunTop"); sb.p(x()).p("{ "); if( _sig!=null ) for( Type t : _sig ) - sb.p(t==null ? "---" : t.str()).p(" "); // Short form in signature - if( !_open ) sb.p("^ "); + sb.p(t.str()).p(" "); // Short form in signature _ret.print(sb.p(html ? "→ " : "-> "),visit,html).p(" #"); // Print fidxs return sb.p(printFIDX()).p("}").p(q()); } String printFIDX() { String tilde = isHigh() ? "~" : ""; - String fidx = XInt.str(_fidxs); + long fidxs = isHigh() ? ~_fidxs : _fidxs; + String fidx = fidxs==0 ? "" + : Long.bitCount(fidxs) == 1 ? ""+Long.numberOfTrailingZeros(fidxs) + : fidxs == -1 ? "ALL" + : "b"+Long.toBinaryString(fidxs); // Just some function bits return tilde+fidx; } // Usage: for( long fidxs=fidxs(); fidxs!=0; fidxs=nextFIDX(fidxs) ) { int fidx = Long.numberOfTrailingZeros(fidxs); ... } - //public static long nextFIDX(long fidxs) { return fidxs & (fidxs-1); } + public static long nextFIDX(long fidxs) { return fidxs & (fidxs-1); } + + @Override Type _upgradeType(java.util.HashMap<String,Type> types) { + TypeFunPtr old = (TypeFunPtr)VISIT.get(_uid); + if( old!=null ) return old; + Type[] sig = new Type[_sig.length]; + TypeFunPtr fun = malloc(_nil,_open,sig,null,_fidxs); + VISIT.put(_uid,fun); + fun._ret = _ret._upgradeType(types); + for( int i=0; i<sig.length; i++ ) sig[i] = _sig[i]._upgradeType(types); + return fun; + } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeInteger.java index a0c2266d..1b0019d4 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeInteger.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeInteger.java @@ -1,11 +1,9 @@ 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 com.seaofnodes.simple.util.Utils; import java.util.ArrayList; -import java.util.HashMap; /** * Integer Type @@ -52,8 +50,8 @@ public class TypeInteger extends TypeScalar { return this; } - public Type same_but_slightly_wider_than( ) { - return _widen < 3 ? make(_min,_max, (byte)(_widen+1)) : BOT; + public Type same_but_slightly_wider_than( Type minType ) { + return _widen < 3 ? make(_min,_max, (byte)(_widen+1)) : minType; } // Force max widen @Override public Type widen() { @@ -123,17 +121,14 @@ public class TypeInteger extends TypeScalar { if( this==I16 || this==U16 ) return 1; // 1<<1 == 2 bytes if( this==I32 || this==U32 ) return 2; // 1<<2 == 4 bytes if( this==BOT ) return 3; // 1<<3 == 8 bytes - if( _isConstant() ) return log(_min); // just const here - int lo = log(_min); - int hi = log(_max); - return Math.max(lo,hi); - } - - private static int log(long x) { - if( -0xFFL <= x && x <= 0xFFL) return 0; - if( -0xFFFFL <= x && x <= 0xFFFFL) return 1; - if(-0xFFFFFFFFL <= x && x <= 0xFFFFFFFFL) return 2; - return 3; + if( _isConstant() ) { // just const here + if (-0xFF <= _min && _min <= 0xFF) return 0; + else if (-0xFFFF <= _min && _min <= 0xFFFF) return 1; + else if (-0xFFFFFFFFL <= _min && _min <= 0xFFFFFFFFL) return 2; + else return 3; + } + if( -128 <= _min && _max < 128 ) return 0; // 1 byte + throw Utils.TODO(); } public long value() { assert _isConstant(); return _min; } @@ -162,7 +157,7 @@ public class TypeInteger extends TypeScalar { return make(Math.min(_min,i._min), Math.max(_max,i._max), (byte)Math.max(_widen,i._widen)); } - @Override TypeInteger xdual() { return malloc(_max,_min,(byte)(3-_widen)); } + @Override TypeInteger xdual() { return malloc(_max,_min,(byte)(3-_widen)); } @Override public TypeInteger nonZero() { if( isHigh() ) return this; @@ -173,42 +168,13 @@ public class TypeInteger extends TypeScalar { } @Override public Type makeZero() { return ZERO; } @Override boolean _isConstant() { return _min == _max; } - @Override Type _makeStorage() { + @Override boolean _isGLB(boolean mem) { return _glb(mem)==this; } + @Override Type _glb(boolean mem) { + if( !mem ) return BOT; + // GLB is not well-defined in memory; depends on the size of the memory field if( _isConstant() ) return BOT; return isHigh() ? dual() : this; } - // Reserve tags for I64,I32,U32,0,constant,generic - @Override int TAGOFF() { return 7; } - @Override public void packed( BAOS baos, HashMap<String,Integer> strs ) { - if( this==BOT ) baos.write(TAGOFFS[_type] + 0); - else if( this==I32 ) baos.write(TAGOFFS[_type] + 1); - else if( this==U32 ) baos.write(TAGOFFS[_type] + 2); - else if( this==BOOL) baos.write(TAGOFFS[_type] + 3); - else if( this==ZERO) baos.write(TAGOFFS[_type] + 4); - else if( isConstant() ) { - baos.write(TAGOFFS[_type] + 5); - baos.packed8(_min); - } else { - baos.write(TAGOFFS[_type] + 6); - baos.packed8(_min); - baos.packed8(_max); - assert -3 <= _widen && _widen <= 3; - baos.packed1(_widen + 3); - } - } - - static Type packed( int tag, BAOS bais ) { - return switch( tag ) { - case 0 -> BOT; - case 1 -> I32; - case 2 -> U32; - case 3 -> BOOL; - case 4 -> ZERO; - case 5 -> constant(bais.packed8()); - case 6 -> make(bais.packed8(),bais.packed8(),(byte)(bais.packed1()-3)); - default -> throw Utils.TODO("Should not reach here: unknown integer type tag"); - }; - } @Override int hash() { return Utils.fold(_min) * Utils.fold(_max) + _widen; } @Override public boolean eq( Type t ) { diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMem.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMem.java index 9ae68bcf..02cfc57b 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMem.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMem.java @@ -1,9 +1,10 @@ package com.seaofnodes.simple.type; -import com.seaofnodes.simple.util.*; +import com.seaofnodes.simple.util.Ary; +import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.util.Utils; import java.util.ArrayList; import java.util.BitSet; -import java.util.HashMap; /** @@ -17,35 +18,13 @@ public class TypeMem extends Type { // N means alias slice#N. public int _alias; public Type _t; // Memory contents, some scalar type - public boolean _one; // Singleton memory, corresponding to TypeMemPtr._one. - public boolean _clz; // Class-object singleton memory; true singletons stay constant after construction. - public boolean _final;// Memory is only set in the constructor - - // Escaped aliases and functions - escaped into this memory. These are - // reachable from some chain of indirections from `_t`. Bit zero is - // used for mutable/immutable safety. Sign bit repeats infinitely. - public int[] _escFs; - public int[] _escAs; - - static public int CNT; private static final Ary<TypeMem> FREE = new Ary<>(TypeMem.class); - - private TypeMem() { super(TMEM); CNT++; } - private TypeMem init(int alias, Type t, boolean one, boolean clz, boolean xfinal, int[] fidxs, int[] aliases) { + private TypeMem(int alias, Type t) { super(TMEM); init(alias,t); } + private TypeMem init(int alias, Type t) { + assert alias>1 || (t==Type.TOP || t==Type.BOTTOM); _alias = alias; _t = t; - _one = one; - _clz = clz; - // Public bulk memory is an aggregate of otherwise-unsplit aliases; - // a single final bit cannot describe it. Keep the lattice-dual high - // form distinct, and retain finality for private singleton memory and - // precise alias slices. - _final = t!=null && alias==1 && !one && !t.isHigh() ? false : xfinal; - - assert _escFs == null && _escAs == null; - _escFs = XInt.intern(fidxs); - _escAs = XInt.intern(aliases); return this; } @Override TypeMem free(Type t) { @@ -53,62 +32,25 @@ public class TypeMem extends Type { mem._alias= -99; mem._dual = null; mem._hash = 0; - mem._t = null; - mem._one = false; - mem._clz = false; - mem._final = false; - mem._escFs = null; - mem._escAs = null; FREE.push(mem); return this; } @Override boolean isFree() { return _alias == -99; } - public static TypeMem malloc(int alias, Type t, boolean one, boolean clz, boolean xfinal, int[] fidxs, int[] aliases) { - return (FREE.isEmpty() ? new TypeMem() : FREE.pop()).init(alias,t,one,clz,xfinal,fidxs,aliases); + public static TypeMem malloc(int alias, Type t) { + return FREE.isEmpty() ? new TypeMem(alias,t) : FREE.pop().init(alias,t); } - public static TypeMem make(int alias, Type t, boolean one, boolean clz, boolean xfinal, int[] fidxs, int[] aliases) { - if( fidxs ==null ) fidxs = XInt.EMPTY; - if( aliases==null ) aliases= XInt.EMPTY; - TypeMem f = malloc(alias,t,one,clz,xfinal, fidxs, aliases); + public static TypeMem make(int alias, Type t) { + TypeMem f = malloc(alias,t); TypeMem f2 = f.intern(); if( f2==f ) return f; return VISIT.isEmpty() ? f2.free(f) : f2.delayFree(f); } - public static TypeMem make(int alias, Type t) { return make(alias,t,false,false,false,null,null); } - // Make a private memory - public static TypeMem makePrivate(Type t) { return make(1,t,true,false,false,escapeFIDX(XInt.EMPTY,t), escapeAlias(XInt.EMPTY,t)); } - public TypeMem makeFrom(int alias) { return make(alias,_t,_one,_clz,_final,_escFs,_escAs); } - public TypeMem makeFrom(int[] fidxes, int[] aliases) { - return make(_alias,_t,_one,_clz,_final,fidxes,aliases); - } - // Existing escapes plus `t` escapes - public TypeMem escapes(Type t) { - return make(_alias,_t,_one,_clz,_final, escapeFIDX(_escFs,t), escapeAlias(_escAs,t)); - } - // Existing escapes plus `t` escapes - public TypeMem escapesFrom(Type t) { - return make(_alias,_t,_one,_clz,_final, escapeFIDX(_escFs,t), escapeAlias(_escAs,t)); - } - public TypeMem escapesAliases(Type t) { - return make(_alias,_t,_one,_clz,_final, _escFs, escapeAlias(_escAs,t)); - } + public static final TypeMem TOP = make(1, Type.TOP ); + public static final TypeMem BOT = make(1, Type.BOTTOM); - public static final TypeMem TOP = make(1, Type.TOP ,true ,true, true , XInt.EMPTY, XInt.EMPTY); - public static final TypeMem BOT = make(1, Type.BOTTOM,false,false,true , XInt.FULL , XInt.FULL ); - public static final TypeMem START= make(1,Type.BOTTOM,false,false,false, XInt.EMPTY, XInt.EMPTY); - public static final TypeMem SELF_MEM = make(1, TypeStruct.BOT); - - public static void gather(ArrayList<Type> ts) { - ts.add(make(2,Type.NIL)); - ts.add(make(2,TypeInteger.ZERO)); - ts.add(BOT); - ts.add(SELF_MEM); - ts.add(START); - ts.add(make(2,TypeInteger.ZERO,true,false,true,XInt.EMPTY,XInt.make(2))); - ts.add(make(2,TypeInteger.BOT,false,false,false,XInt.EMPTY,XInt.FULL)); - } + public static void gather(ArrayList<Type> ts) { ts.add(make(2,Type.NIL)); ts.add(make(2,TypeInteger.ZERO)); ts.add(BOT); } @Override TypeMem xmeet(Type t) { @@ -117,137 +59,59 @@ public class TypeMem extends Type { if( that==TOP ) return this; if( this==BOT ) return BOT; if( that==BOT ) return BOT; - // Treat a alias-1 has either very-high or very-low, according to "_t" - int alias = _alias==that._alias ? _alias // Same-same - : this._alias==1 && this._t.isHigh() ? that._alias // this is high-1 alias, so take that - : that._alias==1 && that._t.isHigh() ? this._alias // that is high-1 alias, so take this - : 1; + // Different slices lose their alias and contents together. Their + // scalar meet alone cannot describe the unsplit bulk memory. + if( _alias!=that._alias ) return BOT; + int alias = _alias; Type mt = _t.meet(that._t); - int[] fidxs = XInt.meet( _escFs, that._escFs ); - int[] aliases= XInt.meet( _escAs, that._escAs ); - // Singleton and class facts are kept only when both sides agree. - // Finality is a low-side fact, and flips on the dual side. - boolean highAlias1 = (this._alias==1 && this._t.isHigh()) || (that._alias==1 && that._t.isHigh()); - boolean xfinal = highAlias1 ? (_final & that._final) : (_final | that._final); - return make(alias, mt, _one & that._one, _clz & that._clz, xfinal, fidxs, aliases); + return make(alias,mt); } @Override - Type xdual() { return malloc(_alias,_t.dual(),!_one,!_clz,!_final, XInt.dual(_escFs), XInt.dual(_escAs)); } + Type xdual() { return _t._dual==_t ? this : malloc(_alias,_t.dual()); } @Override TypeMem rdual() { if( _dual!=null ) return dual(); assert !_terned; - TypeMem d = malloc(_alias,null,!_one,!_clz,!_final, XInt.dual(_escFs), XInt.dual(_escAs)); + TypeMem d = malloc(_alias,null); (_dual = d)._dual = this; // Cross link duals d._t = _t._terned ? _t.dual() : _t.rdual(); - if( d._alias==1 && !d._one ) { assert !d._t.isHigh(); d._final = false; } return d; } - @Override public TypeMem makeZero() { return TOP; } @Override public boolean isHigh() { return _t.isHigh(); } - @Override boolean _isConstant() { return _one && _clz && _alias!= 1 && _t._isConstant(); } - @Override public int log_size() { throw Utils.TODO("Should not reach here: memory has no scalar storage size"); } - @Override boolean _isFinal() { throw Utils.TODO("Should not reach here"); } - @Override TypeMem _makeStorage() { throw Utils.TODO("Should not reach here"); /*return make(_alias,_t._makeStorage(),_one,_clz,_final,_escFs,_escAs);*/ } - - @Override TypeMem _close( String name, HashMap<String, Type> TYPES ) { return malloc(_alias,_t._close(name, TYPES ),_one,_clz,_final,_escFs,_escAs); } + @Override public int log_size() { throw Utils.TODO(); } + @Override boolean _isFinal() { return _t._isFinal(); } + @Override boolean _isGLB(boolean mem) { return _t._isGLB(true); } + @Override public Type _glb(boolean mem) { return make(_alias,_t._glb(true)); } - @Override Type _upgradeType(HashMap<String,Type> TYPES) { - return make(_alias,_t._upgradeType(TYPES),_one,_clz,_final,_escFs,_escAs); - } - - @Override int hash() { return 9876543 + _alias + _t.hashCode() + (_one ? 8196 : 0) + (_clz ? 4098 : 0) + (_final ? 16392 : 0) + XInt.hash(_escFs) + XInt.hash(_escAs); } + @Override int hash() { return 9876543 + _alias + _t.hashCode(); } @Override boolean eq(Type t) { TypeMem that = (TypeMem) t; // Invariant - return _alias == that._alias && _t == that._t && _one == that._one && _clz == that._clz && _final == that._final && _escFs == that._escFs && _escAs == that._escAs; + return _alias == that._alias && _t == that._t; } @Override boolean cycle_eq(Type t) { TypeMem that = (TypeMem) t; // Invariant - return _alias == that._alias && _one == that._one && _clz == that._clz && _final == that._final && _escFs == that._escFs && _escAs == that._escAs && _t.cycle_eq(that._t); + return _alias == that._alias && _t.cycle_eq(that._t); } - @Override public int nkids() { return 1; } - @Override public Type at( int idx ) { return _t; } - @Override public void set( int idx, Type t ) { _t = t; } - // one tag for alias#1, one tag for generic alias. - // tag bit for _one, another bit for _final. - @Override int TAGOFF() { return 12; } - @Override public void packed( BAOS baos, HashMap<String,Integer> strs ) { - baos.write(TAGOFFS[_type] - + (_alias==1 ? 0 : 1) - + (!_final ? 0 : 2) - + (!_one ? 0 : (_clz ? 4 : 8))); - if( _alias!=1 ) { - // generic alias - assert _alias <= 255; - baos.packed1( _alias ); - } - XInt.packed(baos,_escFs); - XInt.packed(baos,_escAs); - } - static TypeMem packed( int tag, BAOS bais ) { - int alias = (tag&1)==0 ? 1 : bais.packed1(); - boolean xfinal= (tag&2)==2; - boolean one = (tag&12)!=0; - boolean clz = (tag&4)==4; - int[] escFs = XInt.packed(bais); - int[] escAs = XInt.packed(bais); - return malloc(alias,null,one,clz,xfinal,escFs,escAs); - } + @Override int nkids() { return 1; } + @Override Type at( int idx ) { return _t; } + @Override void set( int idx, Type t ) { _t = t; } @Override public SB _print(SB sb, BitSet visit, boolean html) { if( this==BOT ) return sb.p("MemBot"); if( this==TOP ) return sb.p("MemTop"); - sb.p('#'); - if( !_final ) sb.p('!'); // Mutable memory - if( _one ) sb.p(_clz ? 'z' : '-'); + sb.p("#"); if( _alias==0 ) return sb.p(_t._type==TTOP ? "TOP" : "BOT"); - sb.p(_alias).p(":["); - _t.print(sb,visit,html).p(","); - XInt.str(sb,_escFs).p(","); - return XInt.str(sb,_escAs).p("]"); + return _t.print(sb.p(_alias).p(":"),visit,html); } - @Override public String str() { - if( this==BOT ) return "MemBot"; - if( this==TOP ) return "MemTop"; - SB sb = new SB().p('#'); - if( !_final ) sb.p('!'); // Mutable memory - if( _one ) sb.p(_clz ? 'z' : '-'); - if( _alias==0 ) return sb.p(_t._type==TTOP ? "TOP" : "BOT").toString(); - sb.p(_alias).p(":[").p(_t.str()).p(","); - XInt.str(sb,_escFs).p(","); - return XInt.str(sb,_escAs).p("]").toString(); - } - - - // Tracking bulk escaped aliases and fidxs - - // Escape all FIDXs from t - private static int[] escapeFIDX( int[] xs, Type t ) { - return switch(t) { - case TypeMemPtr tmp -> escapeFIDX(xs,tmp._obj); - case TypeStruct ts -> ts._open && !ts.isAry() - ? XInt.FULL // Open structs can have any fields - : XInt.meet(xs,ts.fidxs()); - case TypeFunPtr tfp -> XInt.meet(xs,tfp.fidxs()); - case TypeMem mem -> XInt.meet(xs,mem._escFs); - case Type tt -> tt == BOTTOM ? XInt.FULL : xs; - }; - } + @Override public String str() { return toString(); } - private static int[] escapeAlias( int[] xs, Type t ) { - return switch(t) { - case TypeMemPtr tmp -> escapeAlias(xs,tmp._obj); - case TypeStruct ts -> ts._open && !ts.isAry() - ? XInt.FULL // Open structs can have any fields - : XInt.meet(xs,ts.aliases()); - case TypeMem mem -> XInt.meet(xs,mem._escAs); - case Type tt -> tt == BOTTOM ? XInt.FULL : xs; - }; + @Override Type _upgradeType(java.util.HashMap<String,Type> types) { + return make(_alias,_t._upgradeType(types)); } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java index 175b1c63..4a28b919 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java @@ -1,9 +1,8 @@ package com.seaofnodes.simple.type; import com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.util.AryInt; import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.BAOS; +import com.seaofnodes.simple.util.Utils; import java.util.*; /** @@ -30,26 +29,22 @@ public class TypeMemPtr extends TypeNil { // Deep read-only access through this pointer; independent of slot finality. public boolean _ro; - public boolean _one; // Singleton pointer value; the pointer itself is constant. - public boolean _pub; // Escaped/public pointer. False means constructor-private. + public boolean _one; // Singleton instance private static final Ary<TypeMemPtr> FREE = new Ary<>(TypeMemPtr.class); - private TypeMemPtr(byte nil, TypeStruct obj, boolean one, boolean pub) { super(TMEMPTR,nil); init(nil,obj,one,pub); } - private TypeMemPtr init(byte nil, TypeStruct obj, boolean one, boolean pub) { _nil=nil; _obj=obj; _ro=obj==TypeStruct.BOT; _one=one; _pub=pub; return this; } + private TypeMemPtr(byte nil, TypeStruct obj, boolean one) { super(TMEMPTR,nil); init(nil,obj,one); } + private TypeMemPtr init(byte nil, TypeStruct obj, boolean one) { _nil=nil; _obj=obj; _ro=obj==TypeStruct.BOT; _one=one; return this; } // Return a filled-in TypeMemPtr; either from free list or alloc new. - static TypeMemPtr malloc(byte nil, TypeStruct obj, boolean one, boolean pub) { - return FREE.isEmpty() ? new TypeMemPtr(nil,obj,one,pub) : FREE.pop().init(nil,obj,one,pub); + private static TypeMemPtr malloc(byte nil, TypeStruct obj, boolean one) { + return FREE.isEmpty() ? new TypeMemPtr(nil,obj,one) : FREE.pop().init(nil,obj,one); } // All fields directly listed public static TypeMemPtr make(byte nil, TypeStruct obj, boolean one) { - return make(nil,obj,one,true); + return make(nil,obj,one,obj==TypeStruct.BOT); } - public static TypeMemPtr make(byte nil, TypeStruct obj, boolean one, boolean pub) { - return make(nil,obj,one, pub,obj==TypeStruct.BOT); - } - private static TypeMemPtr make(byte nil, TypeStruct obj, boolean one, boolean pub, boolean ro) { - TypeMemPtr tmp = malloc(nil, obj, one, pub).access(ro); + private static TypeMemPtr make(byte nil, TypeStruct obj, boolean one, boolean ro) { + TypeMemPtr tmp = malloc(nil, obj, one).access(ro); TypeMemPtr t2 = tmp.intern(); if( t2==tmp ) return tmp; return VISIT.isEmpty() ? t2.free(tmp) : t2.delayFree(tmp); @@ -58,8 +53,6 @@ public class TypeMemPtr extends TypeNil { TypeMemPtr tmp = (TypeMemPtr)t; tmp._nil = -99; tmp._obj = null; - tmp._one = false; - tmp._pub = true; tmp._dual = null; tmp._hash = 0; FREE.push(tmp); @@ -70,22 +63,17 @@ public class TypeMemPtr extends TypeNil { static TypeMemPtr make(byte nil, TypeStruct obj) { return make(nil, obj, false); } public static TypeMemPtr make (TypeStruct obj) { return make((byte)2, obj); } public static TypeMemPtr makeNullable(TypeStruct obj) { return make((byte)3, obj); } - public static TypeMemPtr makePrivate (TypeStruct obj) { return make((byte)2, obj, false, false); } - public TypeMemPtr makeFrom(TypeStruct obj) { return obj==_obj ? this : make(_nil, obj, _one, _pub,_ro); } + public TypeMemPtr makeFrom(TypeStruct obj) { return obj==_obj ? this : make(_nil, obj, _one,_ro); } public TypeMemPtr makeNullable() { return makeFrom((byte)3); } - /** Inferred mutable local: nullable, public and non-singleton. */ - public TypeMemPtr makeVar() { return (TypeMemPtr)makeStorage(); } - /** Public-memory shape: nullable and non-singleton, preserving the referent. */ - @Override TypeMemPtr _makeStorage() { return make(_nil,_obj._makeStorage(),false,true,_ro); } - @Override TypeMemPtr makeFrom(byte nil) { return nil==_nil ? this : make(nil, _obj, _one, _pub,_ro); } - @Override public Type nonZero() { return makeFrom((byte)(_nil <= 1 ? 1 : 2)); } + @Override TypeMemPtr makeFrom(byte nil) { return nil==_nil ? this : make(nil, _obj, _one,_ro); } + public TypeMemPtr makeHigh() { return make((byte)0,_obj.makeHigh(),false); } private TypeMemPtr access(boolean ro) { _ro=ro; return this; } public TypeMemPtr withAccess(boolean ro) { if( _ro==ro ) return this; - TypeMemPtr ptr = malloc(_nil, _obj, _one, _pub); + TypeMemPtr ptr = malloc(_nil, _obj, _one); ptr._ro = ro; TypeMemPtr res = ptr.intern(); if( res==ptr ) return ptr; @@ -115,73 +103,69 @@ public class TypeMemPtr extends TypeNil { public TypeNil xmeet(Type t) { TypeMemPtr that = (TypeMemPtr) t; assert !isFree() && !that.isFree(); - // Can't keep _one if mixing two unequal singletons; the result is not - // either singleton. Once either side is public, the meet is public. - boolean one = _one && that._one && (_obj==that._obj || !(_obj.isConAry() || that._obj.isConAry())); - return make(xmeet0(that), (TypeStruct)_obj.meet(that._obj), one, _pub | that._pub,_ro | that._ro); + return make(xmeet0(that), (TypeStruct)_obj.meet(that._obj), _one & that._one,_ro | that._ro); } @Override - TypeMemPtr xdual() { return malloc( dual0(), _obj.dual(), !_one, !_pub).access(!_ro); } + TypeMemPtr xdual() { return malloc( dual0(), _obj.dual(), _one).access(!_ro); } @Override TypeMemPtr rdual() { if( _dual!=null ) return dual(); assert !_terned; - TypeMemPtr d = malloc(dual0(), null, !_one, !_pub).access(!_ro); + TypeMemPtr d = malloc(dual0(), null, _one).access(!_ro); (_dual = d)._dual = this; // Cross link duals d._obj = _obj._terned ? _obj.dual() : _obj.rdual(); return d; } // RHS is NIL; do not deep-dual when crossing the center line - @Override Type meet0() { return _nil==3 ? this : make((byte)3,_obj,false,true,_ro); } + @Override Type meet0() { return _nil==3 ? this : make((byte)3,_obj,false,_ro); } - @Override boolean _isConstant() { return _one; } + @Override boolean _isConstant() { return _one && _obj._isConstant(); } @Override boolean _isFinal() { return _ro; } @Override TypeMemPtr _makeRO() { return withAccess(true); } - @Override TypeMemPtr _close( String name, HashMap<String, Type> TYPES ) { return malloc(_nil,_obj._close(name, TYPES ),_one,_pub).access(_ro); } + @Override boolean _isGLB(boolean mem) { return _ro && _nil==3 && !_one && _obj.isGLB2(); } + @Override TypeMemPtr _glb(boolean mem) { return make((byte)3,_obj.glb2(),false,true); } + @Override TypeMemPtr _close(HashMap<String,Type> types) { return makeFrom(_obj._close(types)); } + - @Override Type _upgradeType(HashMap<String,Type> TYPES) { - return makeFrom((TypeStruct)_obj._upgradeType(TYPES)); + // True if this "isa" t up to named structures + @Override public boolean shallowISA( Type t ) { + if( !(t instanceof TypeMemPtr that) ) return false; + if( this==that ) return true; + if( xmeet0(that)!=that._nil || (_ro && !that._ro) ) return false; + if( _obj==that._obj ) return true; + if( _obj._name.equals(that._obj._name) ) + return true; // Shallow, do not follow matching names, just assume ok + throw Utils.TODO(); // return _obj.shallowISA(that._obj); } + // Is forward-reference + @Override public boolean isFRef() { return _obj.isFRef(); } + @Override public int log_size() { return 3; } // (1<<3)==8-byte pointers - @Override int hash() { return (_ro ? 8192 : 0) ^ _obj.hashCode() ^ super.hash() ^ (_one ? 2048 : 0) ^ (_pub ? 4096 : 0); } + @Override int hash() { return (_ro ? 8192 : 0) ^ _obj.hashCode() ^ super.hash() ^ (_one ? 2048 : 0); } @Override boolean eq(Type t) { TypeMemPtr ptr = (TypeMemPtr)t; // Invariant - return _ro==ptr._ro && super.eq(ptr) && _one == ptr._one && _pub == ptr._pub && _obj == ptr._obj; + return _ro==ptr._ro && super.eq(ptr) && _one == ptr._one && _obj == ptr._obj; } @Override boolean cycle_eq(Type t) { if( t._type != TMEMPTR ) return false; TypeMemPtr ptr = (TypeMemPtr)t; // Invariant - return _ro==ptr._ro && super.eq(ptr) && _one == ptr._one && _pub == ptr._pub && _obj.cycle_eq(ptr._obj); + return _ro==ptr._ro && super.eq(ptr) && _one == ptr._one && _obj.cycle_eq(ptr._obj); } - @Override public int nkids() { return 1; } - @Override public Type at( int idx ) { return _obj; } - @Override public void set( int idx, Type t ) { _obj = (TypeStruct)t; } - - // Reserve tags for null/not, one/general, public/private and read-only access - @Override int TAGOFF() { return 16; } - @Override public void packed( BAOS baos, HashMap<String,Integer> strs ) { - assert _nil>=2; - baos.write(TAGOFFS[_type] - + (_nil==2 ? 0 : 1) - + (!_one ? 0 : 2) - + (!_pub ? 0 : 4) - + (!_ro ? 0 : 8) ); - } - static TypeMemPtr packed( int tag, BAOS bais ) { - return malloc((byte)((tag&1)+2),null,(tag&2)==2,(tag&4)==4).access((tag&8)==8); - } + @Override int nkids() { return 1; } + @Override Type at( int idx ) { return _obj; } + @Override void set( int idx, Type t ) { _obj = (TypeStruct)t; } @Override public String str() { if( this==BOT ) return "PtrBot"; if( this==TOP ) return "PtrTop"; if( this== NOTBOT) return "*void"; - return x()+"*"+(_obj==null?"---":_obj.str())+q(); + return x()+"*"+_obj.str()+q(); } @Override SB _print(SB sb, BitSet visit, boolean html ) { @@ -190,4 +174,8 @@ public class TypeMemPtr extends TypeNil { if( this== NOTBOT) return sb.p("*void"); return _obj.print(sb.p(x()).p("*"),visit,html).p(q()); } + + @Override Type _upgradeType(java.util.HashMap<String,Type> types) { + return makeFrom((TypeStruct)_obj._upgradeType(types)); + } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeNil.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeNil.java index 2c1c1d76..32505aa2 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeNil.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeNil.java @@ -51,6 +51,7 @@ public abstract class TypeNil extends TypeScalar { public boolean nullable() { return _nil==3; } @Override boolean _isConstant() { return false; } + @Override Type _glb(boolean mem) { return Type.NIL; } final String q() { return _nil == 1 || _nil == 2 ? "" : "?"; } final String x() { return isHigh() ? "~" : ""; } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypePtr.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypePtr.java index 38941ed1..75478119 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypePtr.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypePtr.java @@ -1,12 +1,9 @@ package com.seaofnodes.simple.type; -import com.seaofnodes.simple.util.AryInt; import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; -import com.seaofnodes.simple.util.BAOS; import java.util.ArrayList; import java.util.BitSet; -import java.util.HashMap; /** * Represents a Scalar; a single register-sized value. @@ -26,7 +23,7 @@ public class TypePtr extends TypeNil { public static void gather(ArrayList<Type> ts) { ts.add(PTR); ts.add(NPTR); } - TypeNil makeFrom(byte nil) { throw Utils.TODO("Should not reach here: plain pointer nil state is canonical"); } + TypeNil makeFrom(byte nil) { throw Utils.TODO(); } @Override public TypeNil xmeet(Type t) { TypePtr that = (TypePtr) t; @@ -50,16 +47,8 @@ public class TypePtr extends TypeNil { // 0->xscalar, 1->nscalar, 2->nscalar, 3->scalar @Override Type meetX() { return _nil==0 ? XNIL : (_nil==3 ? PTR : NPTR); } - @Override TypePtr _makeStorage() { return PTR; } - - - // Reserve 2 tags, PTR and NPTR - @Override int TAGOFF() { return 2; } - @Override public void packed( BAOS baos, HashMap<String,Integer> strs ) { - assert _nil>=2; - baos.write(TAGOFFS[_type] + (_nil==2 ? 0 : 1)); - } - static TypePtr packed( int tag ) { return tag==0 ? NPTR : PTR; } + boolean _isGLB(boolean mem) { return this==PTR; } + @Override TypePtr _glb(boolean mem) { return PTR; } @Override public String str() { return STRS[_nil]; } @Override public SB _print(SB sb, BitSet visit, boolean html) { return sb.p(str()); } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeRPC.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeRPC.java index 8a12f50a..a1389540 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeRPC.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeRPC.java @@ -1,26 +1,32 @@ package com.seaofnodes.simple.type; import com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.util.BAOS; -import java.util.*; +import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.util.Utils; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.HashSet; /** * Return Program Control or Return PC or RPC */ public class TypeRPC extends Type { - // A set of CallEndNode IDs (or StopNode); commonly just one. High-bit - // XInt sets represent complements/infinite sets. - public int[] _rpcs; + // A set of CallEndNode IDs (or StopNode); commonly just one. + // Basically a sparse bit set + HashSet<Integer> _rpcs; + + // If true, invert the meaning of the bits + boolean _any; private static final Ary<TypeRPC> FREE = new Ary<>(TypeRPC.class); - private TypeRPC init(int[] rpcs) { _rpcs = XInt.intern(rpcs); return this; } + private TypeRPC init(boolean any, HashSet<Integer> rpcs) { _any=any; _rpcs=rpcs; return this; } private TypeRPC() { super(TRPC); } - private static TypeRPC malloc(int[] rpcs) { - return (FREE.isEmpty() ? new TypeRPC() : FREE.pop()).init(rpcs); + private static TypeRPC malloc(boolean any, HashSet<Integer> rpcs) { + return (FREE.isEmpty() ? new TypeRPC() : FREE.pop()).init(any,rpcs); } - private static TypeRPC make(int[] rpcs) { - TypeRPC rpc = malloc(rpcs); + private static TypeRPC make(boolean any, HashSet<Integer> rpcs) { + TypeRPC rpc = (FREE.isEmpty() ? new TypeRPC() : FREE.pop()).init(any,rpcs); TypeRPC t2 = rpc.intern(); return t2==rpc ? rpc : t2.free(rpc); } @@ -35,72 +41,71 @@ public class TypeRPC extends Type { @Override boolean isFree() { return _rpcs==null; } public static TypeRPC constant(int cend) { - return make(XInt.make(cend)); - } - - public int rpc() { - return XInt.onlyBit(_rpcs); + HashSet<Integer> rpcs = new HashSet<>(); + rpcs.add(cend); + return make(false,rpcs); } - - public static final TypeRPC BOT = make(XInt.FULL); + public static final TypeRPC BOT = make(true,new HashSet<>()); private static final TypeRPC TEST2 = constant(2); private static final TypeRPC TEST3 = constant(2); - public static final TypeRPC EMPTY = make(XInt.EMPTY); public static void gather(ArrayList<Type> ts) { ts.add(BOT); ts.add(TEST2); ts.add(TEST3); } - @Override public TypeRPC makeZero() { return EMPTY; } - - @Override public String str() { - if( _rpcs == XInt.FULL ) return "$[ALL]"; - if( _rpcs == XInt.EMPTY ) return "$[]"; - return "$"+XInt.str(_rpcs); + if( _rpcs.isEmpty() ) + return _any ? "$[ALL]" : "$[]"; + if( _rpcs.size()==1 ) + for( Integer rpc : _rpcs ) + return _any ? "$[-"+rpc+"]" : "$["+rpc+"]"; + return "$["+(_any ? "-" : "")+_rpcs+"]"; } @Override public TypeRPC xmeet(Type other) { TypeRPC rpc = (TypeRPC)other; - return make(XInt.meet(_rpcs,rpc._rpcs)); + // If the two sets are equal, the _any must be unequal (invariant), + // so they cancel and all bits are set. + if( _rpcs.equals(rpc._rpcs) ) + return BOT; + // Classic union of bit sets (which might be infinite). + HashSet<Integer> lhs = _rpcs, rhs = rpc._rpcs; + // Smaller on left + if( lhs.size() > rhs.size() ) { lhs = rpc._rpcs; rhs = _rpcs; } + + HashSet<Integer> rpcs = new HashSet<>(); + boolean any = true; + // If both sets are infinite, intersect. + if( _any && rpc._any ) { + for( Integer i : lhs ) if( rhs.contains(i) ) rpcs.add(i); + + } else if( !_any && !rpc._any ) { + // if neither set is infinite, union. + rpcs.addAll( lhs ); + rpcs.addAll( rhs ); + any = false; + } else { + // If one is infinite, subtract the other from it. + HashSet<Integer> sub = _any ? rpc._rpcs : _rpcs; + HashSet<Integer> inf = _any ? _rpcs : rpc._rpcs; + for( Integer i : inf ) + if( inf.contains(i) && !sub.contains(i) ) + rpcs.add(i); + } + return make(any,rpcs); } - @Override TypeRPC xdual() { return malloc(XInt.dual(_rpcs)); } - @Override Type rdual() { - TypeRPC d = xdual(); - (_dual = d)._dual = this; - return d; - } + @Override Type xdual() { return malloc(!_any,_rpcs); } - @Override boolean _isConstant() { return XInt.isConstant(_rpcs); } - @Override TypeRPC _makeStorage() { return this; } - - // Reserve tags for ALL, singleton, generic - @Override int TAGOFF() { return 3; } - @Override public void packed( BAOS baos, HashMap<String,Integer> strs ) { - if( this==BOT ) { - baos.write( TAGOFFS[_type] + 0 ); - } else if( XInt.isConstant(_rpcs) ) { // Singleton - baos.write(TAGOFFS[_type] + 1); - baos.packed2(rpc()); - } else { // Generic - assert !XInt.isHigh(_rpcs); // Not serializing above-center RPC - baos.write(TAGOFFS[_type] + 2); - XInt.packed(baos,_rpcs); - } - } - static TypeRPC packed( int tag, BAOS bais ) { - if( tag==0 ) return BOT; - if( tag==1 ) return malloc(XInt.make(bais.packed2())); - return malloc(XInt.packed(bais)); - } + @Override boolean _isConstant() { return !_any && _rpcs.size()==1; } + @Override boolean _isGLB(boolean mem) { return true; } @Override - int hash() { return XInt.hash(_rpcs); } + int hash() { return _rpcs.hashCode() ^ (_any ? -1 : 0) ; } @Override public boolean eq( Type t ) { TypeRPC rpc = (TypeRPC)t; // Contract - return _rpcs==rpc._rpcs; + return _any==rpc._any && _rpcs.equals(rpc._rpcs); } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeScalar.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeScalar.java index eaba0a1c..1b41db13 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeScalar.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeScalar.java @@ -1,57 +1,31 @@ package com.seaofnodes.simple.type; -import com.seaofnodes.simple.util.BAOS; import com.seaofnodes.simple.util.SB; - import java.util.ArrayList; import java.util.BitSet; -import java.util.HashMap; /** - * The envelope for values which can live in an ordinary register or memory - * field: integers, floats, memory pointers, and function pointers. - * - * `BOT` means any scalar value and `TOP` means no scalar value. They sit - * strictly inside the global {@link Type#BOTTOM}/{@link Type#TOP} pair, which - * also includes non-values such as control and memory. + * Values that fit in a register or field: integers, floats and pointers. + * These bounds exclude control and memory. An unresolved load can produce a + * scalar without making the parser guess which kind of value it will hold. */ public class TypeScalar extends Type { protected TypeScalar(byte type) { super(type); } private TypeScalar() { this(TSCALAR); } - public static final TypeScalar BOT = new TypeScalar().intern(); public static final TypeScalar TOP = BOT.dual(); - public static void gather(ArrayList<Type> ts) { ts.add(BOT); } - /** Meet two different scalar families. */ final Type smeet(TypeScalar scalar) { - if( _type == TSCALAR ) return isHigh() ? scalar : this; - if( scalar._type == TSCALAR ) return scalar.isHigh() ? this : scalar; + if( _type==TSCALAR ) return isHigh() ? scalar : this; + if( scalar._type==TSCALAR ) return scalar.isHigh() ? this : scalar; return BOT; } - - @Override Type xmeet(Type t) { - TypeScalar scalar = (TypeScalar)t; - return isHigh() ? scalar : this; - } - + @Override Type xmeet(Type t) { return isHigh() ? t : this; } @Override TypeScalar xdual() { return new TypeScalar(); } - @Override public boolean isHigh() { return this == TOP; } - @Override Type _makeStorage() { return BOT; } - - // Reserve tags for both lattice endpoints. Unlike most concrete scalar - // families, generic scalar TOP can survive into a serialized graph. - @Override int TAGOFF() { return 2; } - @Override public void packed(BAOS baos, HashMap<String,Integer> strs) { - assert this == BOT || this == TOP; - baos.write(TAGOFFS[_type] + (this == BOT ? 0 : 1)); - } - static TypeScalar packed(int tag) { - assert tag == 0 || tag == 1; - return tag == 0 ? BOT : TOP; - } - + @Override public boolean isHigh() { return this==TOP; } + @Override boolean _isGLB(boolean mem) { return this==BOT; } + @Override Type _glb(boolean mem) { return BOT; } @Override public String str() { return isHigh() ? "~scalar" : "scalar"; } @Override SB _print(SB sb, BitSet visit, boolean html) { return sb.p(str()); } } 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 4c5ae6ea..8eec6dd4 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeStruct.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeStruct.java @@ -10,65 +10,42 @@ import java.util.*; public class TypeStruct extends Type { // A Struct has a name and a set of fields; the fields themselves have - // names, types and aliases. The name is essentially another field, - // immutable and type ignored (BOT). - - /** - * `_open` and `_fref` are independent lattice coordinates. - * - * `_open` says the structural tail is not known yet. On the low side an - * open struct has infinitely many implicit BOT fields; its dual has an - * implicit TOP tail. A real declaration is therefore open while its body - * is being parsed: - * - * ```text - * struct S { int x; /* more declared fields may follow *\/ } - * ``` - * - * `_fref` says no actual declaration has contributed to the type. The - * parser may still invent fields demanded by uses, leaving a useful open - * structural constraint: - * - * ```text - * S? p; p.x; // fref S { BOT x; ... } - * ``` - * - * Meeting that constraint with a real definition ANDs `_fref` off while - * retaining and checking the invented field. Closing recursive types must - * not by itself clear `_fref`; closure discovers shape, not a declaration. - */ + // names, types and aliases. The name has no semantic meaning, but is + // useful for debugging. + + // During parsing a mid-declaration struct is flagged as a "open". It is + // treated as having infinite fields with correct name and type BOTTOM. public String _name; // Struct name - public boolean _open; - public boolean _fref; + public boolean _open; // infinite fields are all true:BOTTOM, false:TOP public Field[] _fields; - private TypeStruct() { super(TSTRUCT); } - private static final Ary<TypeStruct> FREE = new Ary<>(TypeStruct.class); - private TypeStruct init(String name, boolean open, boolean fref, Field[] fields) { - // Check for const array element name with mutable field - //assert !name.startsWith("[~]") || fields.length!=2 || fields[1]._t==null || (fields[1]._final != fields[1]._t.isHigh()); + private TypeStruct(String name, boolean open, Field[] fields) { + super(TSTRUCT); _name = name; _open = open; - _fref = fref; _fields = fields; - return this; } + + private static final Ary<TypeStruct> FREE = new Ary<>(TypeStruct.class); // Return a filled-in TypeStruct; either from free list or alloc new. - private static TypeStruct malloc(String name, boolean open, boolean fref, Field[] fields) { - return (FREE.isEmpty() ? new TypeStruct() : FREE.pop()).init(name,open,fref,fields); + private static TypeStruct malloc(String name, boolean open, Field[] fields) { + if( FREE.isEmpty() ) return new TypeStruct(name,open,fields); + TypeStruct ts = FREE.pop(); + assert ts.isFree(); + ts._name = name; + ts._open = open; + ts._fields = fields; + return ts; } // Free ts; return this. @Override TypeStruct free(Type t) { TypeStruct ts = (TypeStruct)t; assert !ts.isFree() && !ts._terned; - ts._name = null; ts._fields = null; ts._offs = null; ts._dual = null; ts._hash = 0; - ts._aliases = null; - ts._fidxs = null; FREE.push(ts); return this; } @@ -77,196 +54,83 @@ public class TypeStruct extends Type { // All fields directly listed public static TypeStruct make(String name, boolean open, Field... fields) { - return make(name,open,false,fields); - } - public static TypeStruct make(String name, boolean open, boolean fref, Field... fields) { - TypeStruct ts = malloc(name, open, fref, fields); + TypeStruct ts = malloc(name, open, fields); TypeStruct t2 = ts.intern(); if( t2==ts ) return ts; return VISIT.isEmpty() ? t2.free(ts) : ts.delayFree(ts); } - - // Summary aliases, after interning. Skips private - // fields which need to be explicitly loaded. - private int[] _aliases; - public int[] aliases() { - if( _aliases != null ) return _aliases; - if( _fields.length==0 ) return (_aliases=XInt.EMPTY); - assert _terned; - int maxAlias = 0; - for( Field f : _fields ) - maxAlias = Math.max(maxAlias,f._alias); - int len = XInt.idx(maxAlias)+1; - if( (maxAlias&31)==31 ) len++; - int[] xs = XInt.free(len); - for( Field f : _fields ) - if( f._fname.charAt(0) != '_' && // Skip private fields. - !hiddenInit(f) && // Skip private allocation helper. - f._t != Type.TOP ) // Skip uninit fields - xs[XInt.idx(f._alias)] |= XInt.mask(f._alias); - return (_aliases=XInt.intern(xs)); - } - - // Summary fidxs, after interning. Skips private - // fields which need to be explicitly loaded. - private int[] _fidxs; - public int[] fidxs() { - if( _fidxs != null ) return _fidxs; - assert _terned; - int[] xs = XInt.EMPTY; - for( Field f : _fields ) - if( f._fname.charAt(0) != '_' && // Skip private fields. - !hiddenInit(f) && // Skip private allocation helper. - f._t instanceof TypeFunPtr tfp ) // Looking for TFP - xs = XInt.meet(xs,tfp._fidxs); - return (_fidxs = xs); - } - - // With a user constructor, the same-named function field is the hidden - // default <init> allocation helper, not part of the public class surface. - public boolean hiddenInit(Field fld) { - if( field("<ctor>") == null || !_name.startsWith("class:") ) return false; - int idx = _name.lastIndexOf('.'); - String name = _name.substring(idx<0 ? "class:".length() : idx+1); - return fld._fname.equals(name); - } - // New open struct with no fields public static TypeStruct open( String name ) { return make(name,true); } - public static TypeStruct forward( String name ) { return make(name,true,true); } - public TypeStruct defined() { return _fref ? make(_name,_open,false,_fields) : this; } - // Array, variant body + // Array public static TypeStruct makeAry(String name, TypeInteger len, int lenAlias, Type body, int bodyAlias, boolean efinal) { return make(name,false, - Field.make("#" , len, lenAlias, true ), - Field.make("[]",body,bodyAlias,efinal)); - } - // Array, known constant body - public static TypeStruct makeAry(String name, int lenAlias, int bodyAlias, TypeConAry con) { - Type len = TypeInteger.constant(con.len()); - return make(name,false, - Field.make("#" ,len , lenAlias,true), - Field.make("[]" ,con.elem(),bodyAlias,true), - Field.make("[con]",con ,bodyAlias,true)); + Field.make("#" ,len , lenAlias,true ,false), + Field.make("[]",body,bodyAlias,efinal,false)); } - - public TypeStruct makeInit() { + public TypeStruct makeHigh() { 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); - } - return make(_name,_open,_fref,fs); + for( int i=0; i<_fields.length; i++ ) + fs[i] = _fields[i].makeFrom(Type.TOP); + return make(_name,false,fs); } public TypeStruct add( Field f ) { - assert find(f._fname)==-1; // No double field names + assert _open && find(f._fname)==-1; // No double field names Field[] flds = Arrays.copyOf(_fields,_fields.length+1); flds[_fields.length] = f; - return make(_name,_open,_fref,flds); + return make(_name,true,flds); } public TypeStruct replace( Field f ) { + assert !_open; Field[] flds = Arrays.copyOf(_fields,_fields.length); flds[find(f._fname)] = f; - return make(_name,_open,_fref,flds); - } - public TypeStruct remove( int i ) { - Field[] flds = new Field[_fields.length-1]; - System.arraycopy(_fields, 0,flds,0,i); - System.arraycopy(_fields,i+1,flds,i,flds.length-i); - return make(_name,_open,_fref,flds); + return make(_name,false,flds); } - public TypeStruct addOrUpdate(Field fld) { - int idx = find(fld._fname); - int len = idx == -1 ? (idx=_fields.length)+1 : _fields.length; - Field[] flds = Arrays.copyOf(_fields,len); - flds[idx] = fld; - return make(_name,_open,_fref,flds); - } public final TypeStruct close() { - return (TypeStruct)recurOpen()._close(_name, null).recurClose(); + return close(null); } - @Override TypeStruct _close( String name, HashMap<String, Type> TYPES ) { - assert (name==null) != (TYPES==null); // One or the other - String namePlusFinal = isAry() ? (_name+(_fields[1]._final ? "~" : "!")).intern() : _name; - TypeStruct ts = (TypeStruct)VISIT.get(namePlusFinal); - if( ts!=null ) return ts; - TypeStruct base = name==null ? (TypeStruct)TYPES.get(_name) : this; - assert base != null; - boolean open = base._fref ? base._open : name != _name && name != null && _open; - ts = base.recurPre(namePlusFinal, _name, open, base._fref ); - Field[] flds = ts._fields; - // Now start the recursion - for( int i=0; i<flds.length; i++ ) - flds[i].setType(base._fields[i]._t._close(name, TYPES)); - - // Note: this whole complex shenanigans here is because there are - // actually two flavors of any array (non-/final contents) but I - // allow the parser to represent both with the same name string. - if( isAry() && _fields[1]._final && !flds[1]._final ) // This is a final array variant - flds[1] = flds[1].makeFrom(true); // Add final to the base TYPES non-final array - - return ts; + public final TypeStruct close(HashMap<String,Type> types) { + return (TypeStruct)recurOpen()._close(types).recurClose(); } - - // Needs full recursive treatment; e.g. - // struct LL { LL? next; int !q; } // Q is mutable field. - // Stored into a not-mutable, so need that version also: - // struct LL { LL? next; int q; } // Q is NOT mutable field. - // Now upgrade: - // it is not the default version from TYPES. - // it is fully cyclic - // - // Evil question: has no *open* internal fields, so can I assume it is fully correct and never needs upgrade? - // - // upgrades are for well-formed types with internal ptrs to early open - // versions of other types, as happens in all the CoRecur tests. - // During the co-recur tests, can I make non-default cycles with - // weaker versions of other early types? - // Seems plausible... - @Override Type _upgradeType(HashMap<String,Type> TYPES) { - TypeStruct base = (TypeStruct)TYPES.get(_name); - if( this == base ) return this; // Already the good form - TypeStruct ts = (TypeStruct)VISIT.get(_uid); + @Override TypeStruct _close(HashMap<String,Type> types) { + Object key = isAry() ? _uid : _name; + TypeStruct ts = (TypeStruct)VISIT.get(key); if( ts!=null ) return ts; - // Main work: replace open with closed - if( _open ) - return base; - // Build recursive version - ts = recurPre(_uid,_name,false,_fref); + if( _open && types!=null && types.get(_name) instanceof TypeMemPtr ptr && !ptr._obj._open ) + return ptr._obj._close(types); + // Closing A must not pretend that an undeclared B is an empty + // definition. The root is visited first; self references find it above. + // Keep other empty forward shapes open until their own declaration. + if( _open && _fields.length==0 && VISIT.size()>1 ) return this; + ts = recurPre(key,false); Field[] flds = ts._fields; + // Now start the recursion for( int i=0; i<flds.length; i++ ) - flds[i].setType(_fields[i]._t._upgradeType(TYPES)); + flds[i].setType(_fields[i]._t._close(types)); + return ts; } static final AryInt CEQUALS = new AryInt(); - public static final String TOPNAME = "$TOP", BOTNAME = "$BOT"; - public static final TypeStruct TOP = make("$TOP",false); - public static final TypeStruct BOT = TOP.dual(); + public static final TypeStruct BOT = open("$STRUCT"); + public static final TypeStruct TOP = BOT.dual(); public static final TypeStruct TEST= make("test",false,Field.TEST); private static final TypeStruct ARY = makeAry("[]i64",TypeInteger.U32,-1,TypeInteger.BOT,-2,false); private static final TypeStruct STR = makeAry("[]u8" ,TypeInteger.U32,-1,TypeInteger.U8 ,-4,false); - private static final TypeStruct ABC = makeAry("[]u8",-1,-4,TypeConAryB.ABC); - // Representative unresolved forms for the exhaustive lattice tests: - // bare forward ref, forward ref constrained by a field use, and definition. - private static final TypeStruct FREF0 = forward("%FREF"); - private static final TypeStruct FREF1 = FREF0.add(Field.make("x",Type.BOTTOM,-20,false)); - private static final TypeStruct FDEF1 = make("%FREF",false,Field.make("x",TypeInteger.BOT,-20,false)); + private static final TypeStruct ABC = makeAry("[]u8",TypeInteger.constant(3),-1,TypeConAryB.ABC,-4,true); // A pair of self-cyclic types - private static final TypeStruct SINT0 = make("%SINT",false); - private static final TypeStruct SFLT0 = make("%SFLT",false); - public static final TypeStruct SINT1 = SINT0.add(Field.make("a", TypeInteger.U32, -1, false)).add(Field.make("s2",TypeMemPtr.make((byte)2,SFLT0),-2, false)); - public static final TypeStruct SFLT1 = SFLT0.add(Field.make("b", TypeFloat .F32, -3, false)).add(Field.make("s1",TypeMemPtr.make((byte)2,SINT1),-4, false)); + private static final TypeStruct SINT0 = open("%SINT"); + private static final TypeStruct SFLT0 = open("%SFLT"); + private static final TypeStruct SINT1 = SINT0.add(Field.make("a", TypeInteger.U32, -1, false, false)).add(Field.make("s2",TypeMemPtr.make((byte)2,SFLT0),-2, false, false)); + private static final TypeStruct SFLT1 = SFLT0.add(Field.make("b", TypeFloat .F32, -3, false, false)).add(Field.make("s1",TypeMemPtr.make((byte)2,SINT1),-4, false, false)); + public static final TypeStruct SFLT2 = SFLT1.close(); - @Override public boolean isHigh() { return _name==TOPNAME; } public static void gather(ArrayList<Type> ts) { ts.add(BOT); @@ -274,15 +138,14 @@ public class TypeStruct extends Type { ts.add(ARY); ts.add(STR); ts.add(ABC); - ts.add(FREF0); - ts.add(FREF1); - ts.add(FDEF1); ts.add(SINT0); ts.add(SFLT0); ts.add(SINT1); ts.add(SFLT1); + ts.add(SFLT2); + ts.add(((TypeMemPtr)(SFLT2.field("s1")._t))._obj); // Break cyclic init: built a struct - Field fcalloc = Field.make("calloc",TypeFunPtr.CALLOC,-2,true); + Field fcalloc = Field.make("calloc",TypeFunPtr.CALLOC,-2,true,true); TypeStruct scalloc = make("calloc",false,fcalloc); ts.add(scalloc); @@ -291,7 +154,7 @@ public class TypeStruct extends Type { // Find field index by name public int find(String fname) { for( int i=0; i<_fields.length; i++ ) - if( _fields[i]._fname==fname ) + if( _fields[i]._fname.equals(fname) ) return i; return -1; } @@ -317,27 +180,28 @@ public class TypeStruct extends Type { if( this==BOT ) return BOT; if( that==BOT ) return BOT; + // Within the same compilation unit, struct names are unique. If the + // names differ, its different structs. Across many compilation units, + // structs with the same name but different field layouts can be + // interned... which begs the question: + // "What is the meet of structs from two different compilation units?" + // And the answer is: "don't ask". + if( !_name.equals(that._name) ) + return BOT; // It's a struct; that's about all we know + // if equal, no matters. // if short is BOT, chop ; recurPre on short. // if short is TOP, copy long; recurPre on long. TypeStruct min = _fields.length < that._fields.length ? this : that; - if( _fields.length != that._fields.length && (!min._open ^ min!=this) ) + if( _fields.length != that._fields.length && (min._open ^ min==this) ) return that.xmeet(this); - assert (_name==that._name) == (_name.equals(that._name)); // Strings are interned - String name = - that._name==TOPNAME ? _name : - _name==TOPNAME ? that._name : - that._name==_name ? _name : - BOTNAME; - if( name==BOTNAME ) return BOT; - // Check all other fields are sanely similar; same struct type but // different field contents (e.g. field "age" is either 'int' or '18'). 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 || f0._extern != f1._extern ) + if( !f0._fname.equals(f1._fname) || f0._alias != f1._alias || f0._one != f1._one ) return BOT; } @@ -351,7 +215,7 @@ public class TypeStruct extends Type { // Setup and install the type, prior to recursing, so we can find our // recursive self again. - ts = recurPre(pid,name,_open | that._open,_fref & that._fref); + ts = recurPre(pid,_open | that._open); // Recurse all common fields Field[] flds = ts._fields; @@ -372,10 +236,12 @@ public class TypeStruct extends Type { @Override TypeStruct xdual() { + if( _name=="$STRUCT" ) + return malloc("$STRUCT",false,new Field[0]); Field[] flds = new Field[_fields.length]; for( int i=0; i<_fields.length; i++ ) flds[i] = _fields[i].dual(); - return malloc(sdual(),!_open,!_fref,flds); + return malloc(_name,!_open,flds); } // Recursive dual @@ -383,23 +249,18 @@ public class TypeStruct extends Type { if( _dual!=null ) return dual(); assert !_terned; Field[] flds = new Field[_fields.length]; - TypeStruct d = malloc(sdual(),!_open,!_fref,flds); + TypeStruct d = malloc(_name,!_open,flds); (_dual = d)._dual = this; // Cross link duals for( int i=0; i<_fields.length; i++ ) flds[i] = _fields[i]._terned ? _fields[i].dual() : _fields[i].rdual(); return d; } - private String sdual() { - return - _name==TOPNAME ? BOTNAME : - _name==BOTNAME ? TOPNAME : - _name; - } + // Is forward-reference + @Override public boolean isFRef() { return _open; } @Override boolean _isConstant() { - assert !_open; // who is asking about constant open structs? if( _open ) return false; // Infinite BOT fields - assert !VISIT.containsKey(_uid); //expect constants to not include self-cycles. if( VISIT.containsKey(_uid) ) return true; // Cycles assume constant + if( VISIT.containsKey(_uid) ) return true; // Cycles assume constant VISIT.put(_uid,this); // Check all fields for being constant for( Field field : _fields ) @@ -410,76 +271,89 @@ public class TypeStruct extends Type { // All fields are final @Override boolean _isFinal() { - if( _open ) return false; // May have more non-final fields + if( _open ) return false; // May have more more non-final fields if( VISIT.containsKey(_uid) ) // Test: been here before? return true; // Cycles assume final - VISIT.put(_uid,this); // Set: don't do this again + VISIT.put(_uid,this); // Set: dont do this again for( Field fld : _fields ) if( !fld._isFinal() ) return false; return true; } - private TypeStruct recurPre(Object key, String name, boolean open, boolean fref) { + private TypeStruct recurPre(Object key, boolean open) { // Make a clone of original; suitable for hashing so can build // e.g. TMPs to clone as part of cycles - but will fail the 'eq' check // until the entire cycle is built. Field[] flds = new Field[_fields.length]; for( int i=0; i<flds.length; i++ ) flds[i] = _fields[i].malloc(); // Blank copy, but can be hashed - TypeStruct ts = malloc(name, open, fref, flds ); - ts._aliases = aliases(); + TypeStruct ts = malloc(_name, open, flds ); VISIT.put(key,ts); return ts; } - private interface FieldTransform { Type apply(Type t); } - - private TypeStruct recurTransform(Object key, String name, boolean open, boolean fref, boolean xfinal, FieldTransform transform) { - TypeStruct ts = (TypeStruct)VISIT.get(key); + // Make a read-only version + @Override TypeStruct _makeRO() { + // Check for already visited + TypeStruct ts = (TypeStruct)VISIT.get(_name); if( ts!=null ) return ts; // Already visited - ts = recurPre(key,name,open,fref); + ts = recurPre(_name,_open); // Make a new type with blank fields Field[] flds = ts._fields; - for( Field fld : flds ) - fld._final |= xfinal; + for( Field fld : flds ) fld._final = true; // Now start the recursion for( int i=0; i<flds.length; i++ ) - flds[i].setType(transform.apply(_fields[i]._t)); + flds[i].setType(_fields[i]._t._makeRO()); return ts; } - // Make a read-only version - @Override TypeStruct _makeRO() { - return recurTransform(_name,_name,_open,_fref,true,Type::_makeRO); + + // All fields are at GLB already + boolean isGLB2() { + if( VISIT.containsKey(_uid) ) return true; // Cycles assume GLB + VISIT.put(_uid,this); + for( Field fld : _fields ) + if( !fld.isGLB2() ) + return false; + return true; } - @Override TypeStruct _makeStorage() { - return recurTransform(_uid,_name,_open,false,false,Type::_makeStorage); + // Keeps the same struct, but lower-bounds all fields. + public TypeStruct glb2() { + TypeStruct ts = (TypeStruct)VISIT.get(_name); + if( ts!=null ) return ts; + ts = recurPre(_name,_open); + Field[] flds = ts._fields; + for( int i=0; i<flds.length; i++ ) + flds[i]._final = true ; + + // Now start the recursion + for( int i=0; i<flds.length; i++ ) + flds[i].setType(_fields[i]._t._glb(true)); + + return ts; } // log_size for a struct is not defined, unless its exactly some power of // 2. *Total size* is well-defined, and is available in the offsets. - @Override public int log_size() { throw Utils.TODO("Should not reach here: structs have no scalar log size"); } + @Override public int log_size() { throw Utils.TODO(); } @Override public int size() { return offset(_fields.length); } @Override public int alignment() { int align = 0; for( Field f : _fields ) - if( !f._extern ) align = Math.max(align, f._t.alignment()); + align = Math.max(align, f._t.alignment()); return align; } // If false, always false. // If true , maybe true, need to check recursive fields. private boolean static_eq( TypeStruct ts ) { - assert (_name==ts._name) == (_name.equals(ts._name)); // Strings are interned - return _name==ts._name && _open==ts._open && _fref==ts._fref && _fields.length==ts._fields.length; + return _name.equals(ts._name) && _open==ts._open && _fields.length==ts._fields.length; } @Override boolean eq(Type t) { - TypeStruct ts = (TypeStruct)t; // Invariant - // Recursive; so use cyclic equals if( !VISIT.isEmpty() ) { assert CEQUALS.isEmpty(); @@ -488,6 +362,7 @@ public class TypeStruct extends Type { return rez; } // Normal equals + TypeStruct ts = (TypeStruct)t; // Invariant if( !static_eq(ts) ) return false; if( _fields==ts._fields ) return true; for( int i = 0; i < _fields.length; i++ ) @@ -515,35 +390,15 @@ public class TypeStruct extends Type { @Override int hash() { - long hash = _name.hashCode() ^ (_open ? 4 : 0) ^ (_fref ? 8 : 0); + long hash = _name.hashCode() ^ (_open ? 4 : 0); for( Field f : _fields ) hash = Utils.rot(hash,13) ^ ((long)f._fname.hashCode() * f._alias); return Utils.fold(hash); } - @Override public int nkids() { return _fields.length; } - @Override public Field at( int idx ) { return _fields[idx]; } - @Override public void set( int idx, Type t ) { _fields[idx] = (Field)t; } - // Tags 0-4 - closed +#nflds (+name) - // Tags 5 - closed(+ nflds +name) - // Tags 6-10- open +#nflds (+name) - // Tags 11 - open (+ nflds +name) - @Override int TAGOFF() { return 24; } - @Override public void packed( BAOS baos, HashMap<String,Integer> strs ) { - baos.write(TAGOFFS[_type]+ (_fref ? 12 : 0) + (_open ? 6 : 0) + Math.min(5,_fields.length)); - if( _fields.length >= 5 ) - baos.packed2(_fields.length); - baos.packed2(strs.get(_name)); - // TODO: Write aliases summary - } - static TypeStruct packed( int tag, BAOS bais, String[] strs ) { - boolean fref = tag >= 12; - if( fref ) tag -= 12; - int ntag = tag >= 6 ? tag-6 : tag; - int nflds = ntag < 5 ? ntag : bais.packed2(); - String name = strs[bais.packed2()]; - return malloc(name,ntag!=tag,fref,new Field[nflds]); - } + @Override int nkids() { return _fields.length; } + @Override Type at( int idx ) { return _fields[idx]; } + @Override void set( int idx, Type t ) { _fields[idx] = (Field)t; } @Override SB _print(SB sb, BitSet visit, boolean html ) { @@ -553,17 +408,12 @@ public class TypeStruct extends Type { if( isAry() && field("[]")._t instanceof TypeConAry con ) return sb.p(con.str()); sb.p(_name); - if( isAry() ) - // Array names deliberately canonicalize mutable and immutable - // element variants. The verbose debugger print must retain the - // otherwise invisible lattice coordinate. - return sb.p(_fields[1]._final ? "[final]" : "[mutable]"); - if( html ) + if( html || isAry() ) return sb; sb.p(" {"); for( Field f : _fields ) (f._t ==null ? sb.p("---") : f._t.print(sb,visit,html)).p(f._final ? " " : " !").p(f._fname).p("; "); - if( _open && !_fref ) sb.p("... "); + if( _open ) sb.p("... "); return sb.p("}"); } @@ -574,10 +424,7 @@ public class TypeStruct extends Type { } - public boolean isAry() { return find("[]")!=-1; } - public boolean isConAry() { - return isAry() && _fields[1]._t instanceof TypeConAry; - } + public boolean isAry() { return _fields.length>=2 && _fields[_fields.length-1]._fname=="[]"; } public int aryBase() { assert isAry(); @@ -615,7 +462,7 @@ 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++ ) - if( !_fields[i]._extern ) + if( !_fields[i]._one ) 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. @@ -627,7 +474,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; } + if( _fields[i]._one ) continue; int log = _fields[i]._t.log_size(); _offs[idx++] = offs[log]; // Field offset offs[log] += 1<<log; // Next field offset at same alignment @@ -652,4 +499,16 @@ public class TypeStruct extends Type { return is; } + + @Override Type _upgradeType(HashMap<String,Type> types) { + Type def = types.get(_name); + if( def instanceof TypeMemPtr ptr && !ptr._obj._open && _open ) + return ptr._obj; + TypeStruct old = (TypeStruct)VISIT.get(_uid); + if( old!=null ) return old; + TypeStruct ts = recurPre(_uid,_open); + for( int i=0; i<_fields.length; i++ ) + ts._fields[i].setType(_fields[i]._t._upgradeType(types)); + return ts; + } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeTuple.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeTuple.java index 075698dc..27a0a9d9 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeTuple.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeTuple.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.type; -import com.seaofnodes.simple.util.*; +import com.seaofnodes.simple.util.Ary; +import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.util.Utils; import java.util.*; public class TypeTuple extends Type { @@ -16,6 +18,7 @@ public class TypeTuple extends Type { // All fields directly listed public static TypeTuple make(Type... types) { TypeTuple tt = malloc(types); + for( Type t : types ) if( !t._terned ) return tt; TypeTuple t2 = tt.intern(); return t2==tt ? tt : t2.free(tt); } @@ -29,32 +32,20 @@ public class TypeTuple extends Type { } @Override boolean isFree() { return _types==null; } - public TypeTuple makeFrom(int idx, Type tx ) { - if( _types[idx]==tx ) return this; - Type[] types = _types.clone(); - types[idx] = tx; - return make(types); - } - public TypeTuple meetFrom(int idx, Type t ) { return makeFrom(idx,_types[idx].meet(t)); } public static final TypeTuple BOT = malloc(new Type[0]).intern(); public static final TypeTuple TOP = BOT.dual(); - // The default state, a triple of CONTROL, Memory, and Value, with bottoms - // for all. This is the default StopNode type, when new functions are - // being created and we cannot tell what the future holds. This is the - // default ReturnNode type, returning what the worst a function can do. - public static final TypeTuple STATE = make(Type.CONTROL,TypeMem.BOT,Type.BOTTOM); - public static final TypeTuple STOP_HIGH = make(Type.CONTROL,TypeMem.START,Type.TOP); public static final TypeTuple TEST = make(TypeInteger.BOT,TypeMemPtr.TEST); - public static final TypeTuple DEAD_NEW = make(TypeMemPtr.TOP,TypeMem.TOP); + public static final TypeTuple START= make(Type.CONTROL,TypeMem.TOP,TypeInteger.BOT); + public static final TypeTuple RET = make(Type.CONTROL,TypeMem.BOT,Type.BOTTOM); public static final TypeTuple IF_BOTH = make(Type. CONTROL,Type. CONTROL); public static final TypeTuple IF_NEITHER = make(Type.XCONTROL,Type.XCONTROL); public static final TypeTuple IF_TRUE = make(Type. CONTROL,Type.XCONTROL); public static final TypeTuple IF_FALSE = make(Type.XCONTROL,Type. CONTROL); - public static void gather(ArrayList<Type> ts) { ts.add(BOT); ts.add(TEST); ts.add(STATE); ts.add(IF_TRUE); } + public static void gather(ArrayList<Type> ts) { ts.add(BOT); ts.add(TEST); ts.add(START); ts.add(IF_TRUE); } @Override Type xmeet(Type other) { @@ -68,23 +59,18 @@ public class TypeTuple extends Type { @Override TypeTuple xdual() { if( _types.length==0 ) return malloc(null); - boolean isCon=true; - for( Type t : _types ) - if( t._dual!=t ) - // Not a constant - return malloc(TypeFunPtr.xdual(_types)); - return this; + return malloc(TypeFunPtr.xdual(_types)); } + + @Override int nkids() { return _types==null ? 0 : _types.length; } + @Override Type at(int idx) { return _types[idx]; } + @Override void set(int idx, Type t) { _types[idx]=t; } @Override TypeTuple rdual() { if( _dual!=null ) return dual(); - assert !_terned; - assert _types.length>0; - Type[] ts = new Type[_types.length]; - TypeTuple d = malloc(ts); - (_dual = d)._dual = this; // Cross link duals - // Collection rdual + TypeTuple d = malloc(new Type[_types.length]); + (_dual=d)._dual=this; for( int i=0; i<_types.length; i++ ) - ts[i] = _types[i]._terned ? _types[i].dual() : _types[i].rdual(); + d._types[i] = _types[i]._terned ? _types[i].dual() : _types[i].rdual(); return d; } @@ -94,18 +80,11 @@ public class TypeTuple extends Type { return false; return true; } - @Override boolean _isFinal() { throw Utils.TODO("Should not reach here: tuples are not stored object values"); } - - @Override TypeMemPtr _makeRO() { throw Utils.TODO("Should not reach here: tuples cannot be made read-only"); } + @Override boolean _isFinal() { throw Utils.TODO(); } - @Override Type _upgradeType(HashMap<String,Type> TYPES) { - Type[] ts = new Type[_types.length]; - for( int i=0; i<ts.length; i++ ) - ts[i] = _types[i]._upgradeType(TYPES); - return make(ts); - } + @Override TypeMemPtr _makeRO() { throw Utils.TODO(); } - @Override public int log_size() { throw Utils.TODO("Should not reach here: tuples have no scalar storage size"); } + @Override public int log_size() { throw Utils.TODO(); } @Override public int alignment() { assert isConstant(); int align = 0; @@ -114,30 +93,7 @@ public class TypeTuple extends Type { return align; } - public Type ctl() { assert _types.length>=3; return _types[0]; } - public Type ret() { assert _types.length>=3; return _types[2]; } - public TypeMem mem() { assert _types.length>=3; - return _types[1] instanceof TypeMem tmem ? tmem : (_types[1].isHigh() ? TypeMem.START : TypeMem.BOT); - } - - @Override public int nkids() { return _types.length; } - @Override public Type at( int idx ) { return _types[idx]; } - @Override public void set( int idx, Type t ) { _types[idx] = t; } - - // Reserve tags for tuples with 2,3,generic - @Override int TAGOFF() { return 4; } - @Override public void packed( BAOS baos, HashMap<String,Integer> strs ) { - if( _types.length==2 ) baos.write(TAGOFFS[_type] + 0); - else if( _types.length==3 ) baos.write(TAGOFFS[_type] + 1); - else if( _types.length==4 ) baos.write(TAGOFFS[_type] + 2); - else baos.write(TAGOFFS[_type] + 3).packed2(_types.length); - } - static TypeTuple packed( int tag, BAOS bais ) { - if( tag==0 ) return malloc(new Type[2]); - if( tag==1 ) return malloc(new Type[3]); - if( tag==2 ) return malloc(new Type[4]); - return malloc(new Type[bais.packed2()]); - } + public Type ret() { assert _types.length==3; return _types[2]; } @Override SB _print(SB sb, BitSet visit, boolean html ) { if( this==TOP ) return sb.p("[TOP]"); @@ -169,4 +125,10 @@ public class TypeTuple extends Type { return true; } + @Override Type _upgradeType(java.util.HashMap<String,Type> types) { + if( _types==null ) return this; + Type[] ts = new Type[_types.length]; + for( int i=0; i<ts.length; i++ ) ts[i] = _types[i]._upgradeType(types); + return make(ts); + } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/XInt.java b/chapter25/src/main/java/com/seaofnodes/simple/type/XInt.java deleted file mode 100644 index 752c1419..00000000 --- a/chapter25/src/main/java/com/seaofnodes/simple/type/XInt.java +++ /dev/null @@ -1,268 +0,0 @@ -package com.seaofnodes.simple.type; - -import com.seaofnodes.simple.util.*; -import java.lang.Integer; -import java.util.Arrays; - -// Stupidly overkill interned immutable int[]'s -public abstract class XInt { - private static final Ary<Ary<int[]>> FREES = new Ary(Ary.class); - private static final Ary<Ary<int[]>> INTERNS = new Ary(Ary.class); - private static int PROBES, HITS, NEWS; - - public static final int[] EMPTY = intern(new int[]{0, 0}); - public static final int[] FULL = intern(new int[]{0,-1}); - public static final int[] SET1 = make(1); - public static final int[] SET3 = make(SET1, 2); - - static int idx(int x) { return (x>>5)+1; } - static int mask(int x) { return 1<<(x&31); } - - // No hash set, still mutable - public static boolean isMutable(int[] xs) { return xs[0]==0; } - // Return hash code; must be set hence frozen - public static int hash(int[] xs) { return xs[0]; } - - // High bit set (infinite 1's extension) - public static boolean isHigh(int[] xs) { return xs[xs.length-1] < 0; } - - // Check for a bit - public static boolean bit(int[] xs, int x) { - int idx = idx(x); - if( idx >= xs.length ) - return isHigh(xs); - return (xs[idx] & mask(x))!=0; - } - - // Number of bits set - public static int bitCount(int[] xs) { - assert !isHigh(xs); // Dont ask if infinite - int cnt=0; - for( int i=1; i<xs.length; i++ ) - cnt += Integer.bitCount(xs[i]); - return cnt; - } - - // Largest bit - public static int max(int[] xs) { - assert !isHigh(xs); - return Integer.numberOfTrailingZeros(xs[xs.length-1])+((xs.length-1-1)<<5); - } - - // Has 1 bit set - public static boolean isConstant(int[] xs) { return !isHigh(xs) && bitCount(xs)==1; } - // Return the 1 set bit or assert - public static int onlyBit(int[] xs) { - assert isConstant(xs); - for( int i=1; i<xs.length; i++ ) - if( xs[i] != 0 ) - return Integer.numberOfTrailingZeros(xs[i]) + ((i-1)<<5); - throw Utils.TODO("Should not reach here"); - } - - // Subset check: ys is a subset of xs - public static boolean subset(int[] xs, int[] ys) { - assert !isMutable(ys); - if( xs==ys ) return true; - if( xs==FULL ) return true; - if( ys==EMPTY ) return true; - int min = Math.min(xs.length,ys.length); - for( int i=1; i<min; i++ ) - if( (xs[i] | ys[i])!=xs[i] ) - return false; - if( xs.length == ys.length ) - return true; - if( ys.length < xs.length ) - return !isHigh(ys); // If ys is infinite, it'll hit one of the xs extra zeros - return isHigh(xs); - } - - - // Construct a set with 1 bit set - public static int[] make(int x ) { return make(EMPTY,x); } - - // Add a bit - public static int[] make(int[] xs, int x ) { - assert !isMutable(xs); - int idx = idx(x); - if( idx >= xs.length && isHigh(xs) ) - return xs; // Set in the default infinite - if( idx < xs.length && (mask(x) & xs[idx])!=0 ) - return xs; // Already set - int len = Math.max(xs.length,idx+1); - if( (x&31)==31 && idx+1==len ) - len++; - int[] ys = free(len); - System.arraycopy(xs,1,ys,1,xs.length-1); - ys[idx] |= mask(x); - return intern(ys); - } - - // Interning, and life-cycle management - // Intern the array - public static int[] intern(int[] xs) { - if( !isMutable(xs) ) return xs; // Already interned - PROBES++; - - // Trim trailing 0s or -1s, use the infinite extend bit - int len = xs.length; - while( len > 2 && - ((xs[len-1]== 0 && xs[len-2]>=0) || - (xs[len-1]== -1 && xs[len-2]< 0) ) ) - len--; - // Replace with trimmed - if( len < xs.length ) { - var ys = free(len); - System.arraycopy(xs,1,ys,1,len-1); - xs = _free(xs,ys); - } - - // Compute lame hash code - int hash = 0; - for( int i=1; i<len; i++ ) - hash += xs[i]; - if( hash == 0 ) hash = 0xCAFEBABE; - xs[0] = hash; // Set lame hashcode, and mark immutable - - // Get length-specific INTERN table - var xss = INTERNS.atX(len); - if( xss==null ) - INTERNS.setX( xs.length, xss = new Ary<>(int[].class)); - - // Probe for a hit - for( int[] ys : xss ) - if( Arrays.equals(xs,ys) ) // Hit! - // Free new array and return old array - { HITS++; return _free(xs,ys); } - - // Intern new array and return - return xss.push(xs); - } - - // Return Ary of free int[]s of fixed size - private static Ary<int[]> _xss(int len) { - Ary<int[]> xss = FREES.atX(len); - if( xss==null ) - FREES.setX(len, xss = new Ary<>(int[].class)); - return xss; - } - // Pull free array from free list - static int[] free(int len) { - int[] xs = _xss(len).pop(); - if( xs==null ) - { NEWS++; xs = new int[len]; } - assert isMutable(xs); - return xs; - } - // Return ys. Put xs on free list - static int[] _free(int[] xs, int[] ys) { - Arrays.fill(xs,0); // Mutable again - _xss(xs.length).push(xs); - return ys; - } - - // Meet: Set union - public static int[] meet(int[] xs, int[] ys) { - if( xs==ys ) return xs; - boolean isX=true, isY=true; - int min = Math.min(xs.length,ys.length); - int max = Math.max(xs.length,ys.length); - for( int i=1; i<max; i++ ) { - int x = i < xs.length ? xs[i] : isHigh(xs) ? -1 : 0; - int y = i < ys.length ? ys[i] : isHigh(ys) ? -1 : 0; - if( (x | y)!=x ) isX=false; - if( (x | y)!=y ) isY=false; - } - if( isX ) return xs; - if( isY ) return ys; - // Get a free one - int[] ms = free(max); - for( int i=1; i<min; i++ ) - ms[i] = xs[i] | ys[i]; - for( int i=min; i<ms.length; i++ ) { - int x = i < xs.length ? xs[i] : isHigh(xs) ? -1 : 0; - int y = i < ys.length ? ys[i] : isHigh(ys) ? -1 : 0; - ms[i] = x | y; - } - return intern(ms); - } - - // Dual: flip all the bits - public static int[] dual(int[] xs) { - int[] ds = free(xs.length); - for( int i=1; i<xs.length; i++ ) - ds[i] = ~xs[i]; - return intern(ds); - } - - - public static void packed(BAOS baos, int[] xs) { - baos.packed1(xs.length); - for( int i=1; i<xs.length; i++ ) - baos.packed4(xs[i]); - } - public static int[] packed(BAOS bais) { - int len = bais.packed1(); - int[] xs = free(len); - for( int i=1; i<len; i++ ) - xs[i] = bais.packed4(); - return intern(xs); - } - - // Next set bit, or -1 - // for( int bit = XInt.next(bits,0); bit >=0; bit = XInt.next(bits,bit) ) {...bit...} - public static int next(int[] xs, int x) { - assert !isHigh(xs); // No iterating infinite bitsets - x++; // Skip current - for( ; !bit(xs,x); x++ ) - if( idx(x)>=xs.length ) - return -1; - return x; - } - - // Remap all bits - public static int[] remap( int[] xs, AryInt map ) { - if( xs==FULL ) return xs; - assert !isHigh(xs); - int[] ys = free(xs.length); - for( int bit = next(xs,0); bit >=0; bit = next(xs,bit) ) { - int bat = map.at(bit); - ys[idx(bat)] |= mask(bat); - } - return intern(ys); - } - - - - public static String str(int[] xs) { return str(new SB(),xs).toString(); } - public static SB str(SB sb, int[] xs) { - sb.p("[ "); - // Printing ranges; - // 1,2,3 prints 1-3 - // 1,2,4 prints 1,2,4 - // - int max = (xs.length-1)<<5; - int low = 0; // No low range - for( int i=1; i<max; i++ ) { - if( bit(xs,i) ) { // Set bit, start or extend range - if( low==0 ) low=i; // No range, start one - } else { // Break in sequence, ending a range - range(sb,low,i-1); // Print prior range, and start new one - low=0; // Reset range - } - } - // Print any partial range - range(sb,low,max-1).unchar(); - if( isHigh(xs) ) - sb.p(",..."); - return sb.p("]"); - } - - private static SB range( SB sb, int low, int mid ) { - if( low==0 ) ; - else if( low==mid ) sb.p(low).p(','); - else if( low==mid-1 ) sb.p(low).p(',').p(mid).p(','); - else sb.p(low).p('-').p(mid).p(','); - return sb; - } -} diff --git a/chapter25/src/main/java/com/seaofnodes/simple/util/BAOS.java b/chapter25/src/main/java/com/seaofnodes/simple/util/BAOS.java deleted file mode 100644 index 61ce7387..00000000 --- a/chapter25/src/main/java/com/seaofnodes/simple/util/BAOS.java +++ /dev/null @@ -1,162 +0,0 @@ -package com.seaofnodes.simple.util; - -import java.util.HashMap; -import java.util.Arrays; - -// "ByteArrayOutputStream", and Input Stream and not synchronized and has -// matching pack/unpack strategies. -public class BAOS { - - private byte[] _buf; - private int _len; - - public BAOS() { _buf = new byte[1]; } - public BAOS(byte[] buf) { _buf = buf; } - public BAOS(byte[] buf, int x) { _buf = buf; _len = x; } - - public byte[] toByteArray() { - return Arrays.copyOf(_buf,_len); - } - - public byte[] buf() { return _buf; } - public int size() { return _len; } - public void set( byte[] buf, int len ) { _buf=buf; _len=len; } - public void clear() { _len=0; } - - // ----------------------------------------------------------------------- - // Basic read, write (expand) - - private void grow(int len) { - while( _len+len >= _buf.length ) - _buf = Arrays.copyOf(_buf,_buf.length*2); - } - - // Write a byte, grow as needed; silent chop - public BAOS write(int b) { - grow(1); - _buf[_len++] = (byte)b; // Silent chop - return this; - } - - // Read an UNSIGNED bytes at INDEX, AIOOBE if off end - public int read1(int idx) { return _buf[idx] & 0xFF; } - - // Read an UNSIGNED byte, AIOOBE if off end - public int read() { return read1(_len++); } - - // write 2 bytes - public BAOS write2( int x ) { - grow(2); - _buf[_len++] = (byte)(x>> 0); - _buf[_len++] = (byte)(x>> 8); - return this; - } - - // Read a u16 in 2 bytes - public int read2(int idx) { return read1(idx) | (read1(idx+1)<<8); } - public int read2() { int rez = read2(_len); _len+=2; return rez; } - - // write 4 bytes - public BAOS write4( int x ) { - grow(4); - _buf[_len++] = (byte)(x>> 0); - _buf[_len++] = (byte)(x>> 8); - _buf[_len++] = (byte)(x>>16); - _buf[_len++] = (byte)(x>>24); - return this; - } - - // Read a i32 in 4 bytes - public int read4(int idx) { return read2(idx) | (read2(idx+2)<<16); } - public int read4() { int rez = read4(_len); _len+=4; return rez; } - - // write 8 bytes - public BAOS write8( long x ) { - write4((int) x ); - write4((int)(x>>32)); - return this; - } - - // Read a i64 in 8 bytes - public long read8(int idx) { return ((long)read4(idx) & 0xFFFFFFFFL) | ((long)read4(idx+4)<<32); } - public long read8() { long rez = read8(_len); _len+=8; return rez; } - - // Write byte array - public void write( byte[] bs ) { - grow(bs.length); - System.arraycopy(bs,0,_buf,_len,bs.length); - _len += bs.length; - } - - // Read/fill byte array - public byte[] read( byte[] bs ) { - System.arraycopy(_buf,_len,bs,0,bs.length); - _len += bs.length; - return bs; - } - - // ----------------------------------------------------------------------- - // Write a packed integer to the BAOS. Track a histogram to make more - // intelligent choices in the future. - private static final HashMap<Long,Integer> PACKED_HISTOGRAM = new HashMap<>(); - private BAOS histo(long x) { - Integer cnt = PACKED_HISTOGRAM.get(x); - PACKED_HISTOGRAM.put(x,cnt==null ? 1 : cnt+1); - return this; - } - - // Write a u8 (checked) - public void packed1(int x) { - assert 0 <= x && x <= 255; - histo(x).write(x); - } - - public int packed1() { return read(); } - - // Write a u16 (checked) - public void packed2(int x) { - assert 0 <= x && x <= 65535; - (( x <= 254 ) ? write(x) : write(0xFF).write2(x)).histo(x); - } - - // Read a packed u16 - public int packed2() { - int x = read(); - return x==0xFF ? read2() : x; - } - - // Write a packed i32 - public void packed4(int x) { - ((-127 <= x && x <= 127 ) ? write(x) : write(-128).write4(x)).histo(x); - } - - // Read a packed i32 - public int packed4() { - int x = _buf[_len++]; // Signed byte read - return x== -128 ? read4() : x; - } - - // Write a packed i64 - public void packed8(long x) { - ((-127 <= x && x <= 127 ) ? write((int)x) : write(-128).write8(x)).histo(x); - } - - // Read a packed i64 - public long packed8() { - int x = _buf[_len++]; // Signed byte read - return x== -128 ? read8() : x; - } - - public void packedS(String s) { - packed4(s.length()); - for( int i=0; i<s.length(); i++ ) - packed4(s.charAt(i)); - } - - public String packedS() { - int len = packed4(); - String s = new String(_buf,_len,len).intern(); - _len += len; - return s; - } -} diff --git a/chapter25/src/main/java/com/seaofnodes/simple/util/IntHashMap.java b/chapter25/src/main/java/com/seaofnodes/simple/util/IntHashMap.java deleted file mode 100644 index b356ec31..00000000 --- a/chapter25/src/main/java/com/seaofnodes/simple/util/IntHashMap.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.seaofnodes.simple.util; - -import java.util.Arrays; - -// Int-Int Hash Map -public class IntHashMap { - private int[] _es; - private int _size; - private final int _sentinel; - - // Pick a sentinel value; not allowed for keys or values. Sentinel is - // returned on a miss. Writing sentinel counts as a remove. - public IntHashMap(int sentinel) { - _sentinel = sentinel; - _es = new int[4]; - Arrays.fill(_es,sentinel); - } - - public int size() { return _size; } - public boolean isEmpty() { return _size==0; } - public void clear() { - _size = 0; - Arrays.fill(_es,_sentinel); - } - - private int cap() { return _es.length>>1; } - - public int put( int key, int val ) { - int slot = probe(key); - int old = _es[(slot<<1)+1]; - if( old == _sentinel && val != _sentinel ) _size++; - if( old != _sentinel && val == _sentinel ) _size--; - // Check cap, resize at 3/4 full - if( _size > ((cap()>>1) + (cap()>>2)) ) { - grow(); - slot = probe(key); - } - - _es[ slot<<1 ] = key; - _es[(slot<<1)+1] = val; - return old; - } - - public int get( int key ) { - int slot = probe(key); - return _es[(slot<<1)+1]; - } - - private int probe( int key ) { - int mask = cap()-1; - int slot = key & mask; - while( _es[slot<<1] != _sentinel && _es[slot<<1] != key ) - slot = (slot + (key|1)) & mask; - return slot; - } - - // Double underlying array size - private void grow() { - int[] old = _es; - _es = new int[_es.length<<1]; - Arrays.fill(_es,_sentinel); - for( int i=0; i<old.length; i+=2 ) - if( old[i] != _sentinel ) { - int slot = probe(old[i]); - _es[ slot<<1 ] = old[i ]; - _es[(slot<<1)+1] = old[i+1]; - } - } - - // Just all the keys - public int[] keySet() { - int[] keys = new int[_size]; - int j=0; - for( int i=0; i<_es.length; i+=2 ) - if( _es[i] != _sentinel ) - keys[j++] = _es[i]; - return keys; - } - - @Override public String toString() { - SB sb = new SB().p("[ "); - for( int i=0; i<_es.length; i+=2 ) - if( _es[i] != _sentinel ) - sb.p('(').p(_es[i]).p(',').p(_es[i+1]).p(')').p(','); - return sb.unchar().p(']').toString(); - } -} diff --git a/chapter25/src/main/java/com/seaofnodes/simple/util/SB.java b/chapter25/src/main/java/com/seaofnodes/simple/util/SB.java index cc59f2a6..d7afd6eb 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/util/SB.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/util/SB.java @@ -42,7 +42,7 @@ public final class SB { // 4 bytes, 8 hex digits, 32 bits, Big Endian public SB hex4(int s) { return hex2(s>>16).hex2(s); } // 8 bytes, 16 hex digits, 64 bits, Big Endian - public SB hex8(long s) { return hex4((int)(s>>32)).hex4((int)s); } + public SB hex8(long s) { return hex4((int)(s>>32)).hex4((int)s); } // Fixed width field public SB fix( int sz, String s ) { diff --git a/chapter25/src/main/smp/sys.smp b/chapter25/src/main/smp/sys.smp index 7705a187..6a6d3ca3 100644 --- a/chapter25/src/main/smp/sys.smp +++ b/chapter25/src/main/smp/sys.smp @@ -1,11 +1,199 @@ +// -*- mode: java; -*- -// top-level default standard library import +// top-level default import +struct sys { + // libc bindings + /** https://www.man7.org/linux/man-pages/man2/<libc call>.2.html */ + struct libc { + i32 errno = "C"; + // fd buf len -> len + { i32 i64 u32 -> u32 } write = "C"; + // fd buf len -> len + { i32 i64 u32 -> u32 } read = "C"; + // status + { i32 -> i32 } _exit = "C"; + // addr len prot flags fd off -> void* + { i64 i64 i32 i32 i32 i32 -> i64 } mmap = "C"; + // mmap flags + i32 PROT_EXEC = 4; + i32 PROT_READ = 1; + i32 PROT_WRITE= 2; + i32 PROT_NONE = 0; + i32 MAP_PRIVATE = 2; + i32 MAP_ANON = 32; + }; + struct ary { + val copy = { u8[] dst, int doff, u8[~] src, int soff, int len -> + for( int i=0; i<len; i++ ) + dst[i+doff] = src[i+soff]; + return dst; + }; + }; -val adt = adt; // Abstract Data Types -val ary = ary; // Array utilities -val aryi64 = aryi64; // Variable sized array of i64 -val aryu8 = aryu8; // Variable sized array of u8 -val char = char; // Character utilities -val io = io; // IO library -val libc = libc; // libc FFI bindings -val scan = scan; // A simple string parser + // Ary/Vector of u8s - a resizable array of bytes. + struct aryu8 { + u32 !len = 0; + u8[] !buf; + // Grow as needed + val _grow = { int len2 -> + if( len+len2 < buf# ) return self; + int newlen = buf# * 2; + while( len+len2 >= newlen ) newlen *= 2; + u8[] buf2 = new u8[newlen]; + sys.ary.copy(buf2,0,buf,0,buf#); + buf = buf2; + self; + }; + + // Add a character to aryu8 + val add = { u8 c -> + _grow(1).buf[len++] = c; + self; + }; + + // Add a plain array to the aryu8 + val addAry = { u8[~] ary -> + _grow(ary#); + sys.ary.copy(buf,len,ary,0,ary#); + len += ary#; + self; + }; + + // Remove x chars from the tail. + val undo = { int x -> + len = len >= x ? len-x : 0; + self; + }; + + // Print ASCII number into aryu8 + val num = { i64 x -> + if( !x ) { add('0'); return self; } + if( x < 0 ) { add('-'); x = -x; } + i64 ten = 1, log = 0; + while( ten <= x ) { ten *= 10; log++; } + for( int i=0; i<log; i++ ) { + ten /= 10; + i64 q = x/ten; + u8 digit = q+'0'; + add(digit); + x -= q*ten; + } + self; + }; + + // Write to fd, returns the number of bytes written (len). + val write = { i32 fd -> i64 ptr = buf; sys.libc.write(fd,ptr,len); }; + }; + + + // Scanner; a simple string parser. + struct Scan { + int !x; + u8[~] buf; + + // Skip whitespace + val skip = { -> + while( buf[x] <= ' ' ) + x++; + return self; + }; + + // True if find required character, and skip it; else False. + val match = { u8 c -> + skip(); + if( buf[x] != c ) return false; + x++; + return true; + }; + + // Parse a simple number + val number = { -> + skip(); + bool neg = match('-'); + i64 n = 0; + while( '0' <= buf[x] & buf[x] <= '9' ) + n = n*10 + (buf[x++] - '0'); + return neg ? -n : n; + }; + }; + + // Ary/Vector of i64s + struct aryi64 { + u32 !len = 0; + i64[] !buf; + // Grow as needed + val _grow = { int len2 -> + if( len+len2 < buf# ) return self; + int newlen = buf# * 2; + while( len+len2 >= newlen ) newlen *= 2; + i64[] buf2 = new i64[newlen]; + for( int i=0; i<buf#; i++ ) + buf2[i] = buf[i]; + buf = buf2; + self; + }; + + // Add a i64 to aryu8 + val add = { i64 c -> + _grow(1).buf[len++] = c; + self; + }; + + // Write to aryu8 + val str = { -> + // TODO: clunky default constructor syntax + var str = new sys.aryu8 { buf = new u8[1]; }.add('['); + for( int i=0; i<len; i++ ) + str.num(buf[i]).add(',').add(' '); + str.undo(2).add(']'); + }; + }; + + // Parse a comma-separated list of numbers, wrapped with square brackets, + // with optional whitespace e.g. "[ -17, 0,99,12456789 ] ". + // parseAryI64 & aryi64.str are bijections modulo whitespace. + // Returns null if the parse failed. + val parseAryI64 = { u8[~] str -> + !sys.Scan !s = new sys.Scan{ buf = str; }; + if( !s.match('[') ) return null; + // Array of parsed results + var nums = new sys.aryi64{ buf=new i64[1]; }; + // Parse a list of numbers + if( !s.match(']') ) { // Handle the empty list + // Parse "123, " until a something else + while( s.x < s.buf# ) { + nums.add(s.number()); + if( !s.match(',') ) + break; + }; + if( !s.match(']') ) return null; // Close the list + } + return nums; + }; + + struct io { + // Print string to stdout + val p = { u8[~] str -> + i64 ptr = str; // cast array base to i64 + return sys.libc.write(1,ptr,str#); + }; + + // Return stdin as a u8[] + val stdin = { -> + var buf = new u8[8*1024]; + i64 ptr = buf; + int len = 0; + while( true ) { + int rez = sys.libc.read(0,ptr,buf#); + if( rez < 0 ) sys.libc._exit(sys.libc.errno); + len += rez; + if( rez < buf# ) + break; + sys.libc._exit(-2); // Need to implement + } + // Compress + var dst = new u8[len]; + return sys.ary.copy(dst,0,buf,0,len); + }; + }; +}; diff --git a/chapter25/src/main/smp/sys/adt.smp b/chapter25/src/main/smp/sys/adt.smp deleted file mode 100644 index a4fe40bf..00000000 --- a/chapter25/src/main/smp/sys/adt.smp +++ /dev/null @@ -1,3 +0,0 @@ - -// Abstract data types. -val bitset = bitset; diff --git a/chapter25/src/main/smp/sys/adt/bitset.smp b/chapter25/src/main/smp/sys/adt/bitset.smp deleted file mode 100644 index d02cd2fe..00000000 --- a/chapter25/src/main/smp/sys/adt/bitset.smp +++ /dev/null @@ -1,57 +0,0 @@ - -// A set of non-negative integers, represented one bit per value. -struct bitset { - i64[] !_bits; - - new bitset = { -> - _bits = new i64[1]; - }; - - // Return 0 for missing, non-zero for true - val get = { int bit -> - if( (bit >> 6) >= _bits# ) return false; - i64 mask = 1 << (bit & 63); - return (_bits[bit >> 6] & mask); - }; - - val _grow = { int bit -> - int x = bit >> 6; - int len = _bits#; - if( x < len ) return self; - while( x >= len ) len *= 2; - var bits = new i64[len]; - for( int i=0; i<_bits#; i++ ) - bits[i] = _bits[i]; - _bits = bits; - return self; - }; - - // Set - val set = { int bit -> - i64 mask = 1 << (bit & 63); - _grow(bit)._bits[bit >> 6] |= mask; - return self; - }; - - // Test and set - val tset = { int bit -> - int idx = bit >> 6; - i64 mask = 1 << (bit & 63); - if( _grow(bit)._bits[idx] & mask ) - return true; // Already set - _bits[idx] |= mask; - return false; // Was false (and now set) - }; - - val clr = { int bit -> - if( (bit >> 6) >= _bits# ) return self; - i64 mask = 1 << (bit & 63); - _bits[bit >> 6] &= (mask ^ -1); - return self; - }; - - val clear = { -> - for( int i=0; i<_bits#; i++ ) _bits[i] = 0; - return self; - }; -}; diff --git a/chapter25/src/main/smp/sys/ary.smp b/chapter25/src/main/smp/sys/ary.smp deleted file mode 100644 index 8312e94c..00000000 --- a/chapter25/src/main/smp/sys/ary.smp +++ /dev/null @@ -1,24 +0,0 @@ - -// Array utilities - -// Copy byte array contents. Copies the shorter of the buffers and len -val copy = { u8[] dst, int doff, u8[~] src, int soff, int len -> - if( len > dst# - doff ) len = dst# - doff; - if( len > src# - soff ) len = src# - soff; - for( int i=0; i<len; i++ ) - dst[i+doff] = src[i+soff]; - return dst; -}; - -// Compare lexigraphically array contents. Compares up to shorter array or -// len. Returns <0 or >0 for first non-matching byte. Returns <0 or >0 for -// the larger array after that, or 0 if both arrays are fully equal. -val cmp = { u8[~] src1, u32 off1, u8[~] src2, u32 off2, u32 len -> - u32 len1 = len > src1# - off1 ? src1# - off1 : len; - u32 len2 = len > src2# - off2 ? src2# - off2 : len; - u32 max = len1 > len2 ? len2 : len1; - for( int i=0; i<max; i++ ) - if( src1[off1+i] != src2[off2+i] ) - return src1[off1+i] - src2[off2+i]; // Bytes determine compare - return len1 - len2; // Length determines compare -}; diff --git a/chapter25/src/main/smp/sys/aryi64.smp b/chapter25/src/main/smp/sys/aryi64.smp deleted file mode 100644 index 98b72605..00000000 --- a/chapter25/src/main/smp/sys/aryi64.smp +++ /dev/null @@ -1,82 +0,0 @@ - -// Ary/Vector of i64s -struct aryi64 { - u32 !len = 0; - i64[] !buf; - - new aryi64 = { i64[] b -> - buf = b; - }; - - // Grow as needed - val _grow = { int len2 -> - if( len+len2 < buf# ) return self; - int newlen = buf# * 2; - while( len+len2 >= newlen ) newlen *= 2; - i64[] !buf2 = new i64[newlen]; - for( int i=0; i<buf#; i++ ) - buf2[i] = buf[i]; - buf = buf2; - self; - }; - - // Add a i64 to aryu8 - val add = { i64 c -> - _grow(1).buf[len++] = c; - self; - }; - - val get = { int index -> return buf[index]; }; - - // Write the bracketed array-list form to aryu8. - val str = { -> - // TODO: clunky default constructor syntax - var str = new aryu8().add('['); - for( int i=0; i<len; i++ ) - str.num(buf[i]).add(',').add(' '); - if( len ) str.undo(2); - return str.add(']'); - }; - - val swap = { int i, int j -> - i64 tmp = buf[i]; - buf[i] = buf[j]; - buf[j] = tmp; - self; - }; -}; - - - -// Parse a comma-separated list of numbers, wrapped with square brackets, -// with optional whitespace e.g. "[ -17, 0,99,12456789 ] ". -// parseAryI64 & aryi64.str are bijections modulo whitespace. -// Returns null if the parse failed, without giving a clue as to why. -val parseAryI64 = { u8[~] str -> - !scan !s = new scan(str); - if( !s.match('[') ) return null; - // Array of parsed results - var nums = new aryi64(new i64[1]); - - // Parse a list of numbers - if( s.match(']') ) // Handle the empty list - return nums; - - // Parse number commas "123, " until something else - while( s.x < s.buf# ) { - nums.add(s.number()); - if( s.bad ) return null; - if( !s.match(',') ) - break; - }; - // Close the list - if( !s.match(']') ) return null; - s.skip(); - return s.x == s.buf# ? nums : null; -}; - -val copy = { i64[] dst, int doff, i64[~] src, int soff, int len -> - for( int i=0; i<len; i++ ) - dst[i+doff] = src[i+soff]; - return dst; -}; diff --git a/chapter25/src/main/smp/sys/aryu8.smp b/chapter25/src/main/smp/sys/aryu8.smp deleted file mode 100644 index 21cf920b..00000000 --- a/chapter25/src/main/smp/sys/aryu8.smp +++ /dev/null @@ -1,87 +0,0 @@ - -// Ary/Vector of u8s - a resizable array of bytes. -struct aryu8 { - u32 !len = 0; - u8[] !buf; - - new aryu8 = { -> buf = new u8[1]; }; - - // Grow as needed - val _grow = { int len2 -> - if( len+len2 < buf# ) return self; - int newlen = buf# * 2; - while( len+len2 >= newlen ) newlen *= 2; - u8[] !buf2 = new u8[newlen]; - ary.copy(buf2,0,buf,0,buf#); - buf = buf2; - self; - }; - - val clear = { -> len = 0; self; }; - - // Add a character to aryu8 - val add = { u8 c -> - _grow(1).buf[len++] = c; - self; - }; - - // Add all of a plain array to the aryu8 - val addAry = { u8[~] xs -> - _grow(xs#); - ary.copy(buf,len,xs,0,xs#); - len += xs#; - self; - }; - - // Remove x bytes from the tail. - val undo = { int x -> - len = len >= x ? len-x : 0; - self; - }; - - // Print ASCII number into aryu8 - val num = { i64 x -> - if( !x ) { add('0'); return self; } - if( x < 0 ) { add('-'); x = -x; } - i64 ten = 1, log = 0; - while( ten <= x ) { ten *= 10; log++; } - for( int i=0; i<log; i++ ) { - ten /= 10; - i64 q = x/ten; - u8 digit = q+'0'; - add(digit); - x -= q*ten; - } - self; - }; - - // Write to fd, returns the number of bytes written (len). - val write = { i32 fd -> - i64 ptr = buf; // Cast buffer pointer to i64 for native C call - libc.write(fd,ptr,len); - }; -}; - - -// Parse one integer, ignore trailing data; returns 0 with an empty string. -// If you want a more robust parsing, use the `scan` class -val atoi = { u8[~] str -> - int x = 0; - while( x < str# && str[x] <= ' ' ) x++; - if( x == str# ) return 0; - bool neg = false; - if( str[x]=='-' ) { neg=true; x++; } - i64 n = 0; - while( x < str# && '0' <= str[x] <= '9' ) - n = n*10 + str[x++]-'0'; - return neg ? -n : n; -}; - -// Zero-terminate string for libc strings. No error checking, just copies and -// appends a zero. -{ u8[~] -> u8[] } str0 = { u8[~] str -> - u8[] !strz = new u8[str#+1]; - ary.copy(strz,0,str,0,str#); - strz[str#] = '\0'; - strz; -}; diff --git a/chapter25/src/main/smp/sys/char.smp b/chapter25/src/main/smp/sys/char.smp deleted file mode 100644 index 22ca4213..00000000 --- a/chapter25/src/main/smp/sys/char.smp +++ /dev/null @@ -1,8 +0,0 @@ - -val toupper = { u8 c -> - return c >= 'a' && c <= 'z' ? c - ('a' - 'A') : c; -}; - -val tolower = { u8 c -> - return c >= 'A' && c <= 'Z' ? c + ('a' - 'A') : c; -}; diff --git a/chapter25/src/main/smp/sys/io.smp b/chapter25/src/main/smp/sys/io.smp deleted file mode 100644 index d6263ffe..00000000 --- a/chapter25/src/main/smp/sys/io.smp +++ /dev/null @@ -1,64 +0,0 @@ - -// Simple I/O Library - -// Write byte array to stdout -val p = { u8[~] str -> write(1,str); }; -val p_noInline = { u8[~] str -> write(1,str); }; - -// Return fd contents as a u8[]. -val read = { i32 fd -> - var buf = new u8[8*1024]; - i64 ptr = buf; - int len = 0; - while( true ) { - i32 rez = libc.read(fd,ptr,buf#); - if( rez > buf# ) libc._exit(libc.errno()); - len += rez; - if( rez < buf# ) - break; - libc._exit(-2); // Need to implement for more than 8K input - } - var out = new u8[len]; - ary.copy(out,0,buf,0,len); -}; - -// Write all of str to fd. -val write = { i32 fd, u8[~] str -> - i64 ptr = str; // Convert array pointer to a i64 for the native call - libc.write(fd,ptr,str#); -}; - -// Write all of str to fd. -val writeOff = { i32 fd, u8[~] str, u32 off, u32 len -> - i64 ptr = str; // Convert array pointer to a i64 for the native call - ptr += off; // Skip offset - // Cap at available length - if( len > str# - off ) len = str# - off; - // Do the write - libc.write(fd,ptr,len); -}; - -// Return stdin as a u8[]. -val stdin = { -> read(0); }; - -// Create file for writing. Return fd, or -1 and errno has the error code -val create = { u8[~] str -> - i64 ptr = sys.aryu8.str0(str); - i32 perms = 6*64 + 6*8 + 6; // 0666 - return libc.creat(ptr, perms); -}; - -// Open file for reading. Return fd, or -1 and errno has the error code -val open = { u8[~] str -> - i64 ptr = sys.aryu8.str0(str); - return libc.open(ptr, sys.libc.O_RDONLY, 0); -}; - -// Close an open fd. Return 0, or -1 and errno has the error code -val close = { i32 fd -> return libc.close(fd); }; - -// Remove a file. Return 0, or -1 and errno has the error code -val remove = { u8[~] str -> - i64 ptr = sys.aryu8.str0(str); - return libc.unlink(ptr); -}; diff --git a/chapter25/src/main/smp/sys/libc.smp b/chapter25/src/main/smp/sys/libc.smp deleted file mode 100644 index 17ee65cb..00000000 --- a/chapter25/src/main/smp/sys/libc.smp +++ /dev/null @@ -1,44 +0,0 @@ - -// libc bindings: -// https://www.man7.org/linux/man-pages/man2/<libc call>.2.html - -// The buffers are int-versions of zero-terminated strings. -// int buf = aryu8.str0("some_file"); -// buf mode perm-> fd -{ i64 i32 i32 -> i32 } open = "C"; -// buf perm -> fd -{ i64 i32 -> i32 } creat = "C"; - -// fd buf len -> len -{ i32 i64 u32 -> i32 } write = "C"; -// fd buf len -> len -{ i32 i64 u32 -> i32 } read = "C"; -// fd -> err -{ i32 -> i32 } close = "C"; -// path -> err -{ i64 -> i32 } unlink = "C"; - -i32 O_RDONLY = 0; -i32 O_WRONLY = 1; -i32 O_RDWR = 2; -i32 O_CREAT = 512; // 02000 on x86_64-pc-cygwin -i32 O_TRUNC = 1024; // 04000 on x86_64-pc-cygwin - -i32 STDIN = 0; -i32 STDOUT = 1; -i32 STDERR = 2; - -// status -{ i32 -> i32 } _exit = "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 -i32 PROT_EXEC = 4; -i32 PROT_READ = 1; -i32 PROT_WRITE= 2; -i32 PROT_NONE = 0; -i32 MAP_PRIVATE = 2; -i32 MAP_ANON = 32; diff --git a/chapter25/src/main/smp/sys/scan.smp b/chapter25/src/main/smp/sys/scan.smp deleted file mode 100644 index 44bedaaa..00000000 --- a/chapter25/src/main/smp/sys/scan.smp +++ /dev/null @@ -1,39 +0,0 @@ - -// Scanner; a simple string parser. -struct scan { - int !x; // Cursor - bool !bad; // True after a malformed token - u8[~] buf; // Buffer to parse - - new scan = { u8[~] b -> buf = b; }; - - // Skip whitespace - val skip = { -> - while( x < buf# && buf[x] <= ' ' ) - x++; - return self; - }; - - // True if find required character, and skip it; else False. - val match = { u8 c -> - skip(); - if( x >= buf# ) return false; - if( buf[x] != c ) return false; - x++; - return true; - }; - - // Parse a simple number - val number = { -> - skip(); - bool neg = match('-'); - if( x >= buf# || buf[x] < '0' || '9' < buf[x] ) { - bad = true; - return 0; - } - i64 n = 0; - while( x < buf# && '0' <= buf[x] & buf[x] <= '9' ) - n = n*10 + (buf[x++] - '0'); - return neg ? -n : n; - }; -}; |
