diff options
| author | Cliff Click <cliffc@acm.org> | 2026-08-10 11:11:18 -0700 |
|---|---|---|
| committer | Cliff Click <cliffc@acm.org> | 2026-08-10 15:21:17 -0700 |
| commit | 28209703c72fb7e9955961dbdb84e24a0f871acd (patch) | |
| tree | 8e4df2c1a5e6811696b454bbc4d7cd1cc30e42e7 /src/main/java | |
| parent | e57bdd315a7918a0d130e1c95648ca4a1b4f08ed (diff) | |
| download | Simple-28209703c72fb7e9955961dbdb84e24a0f871acd.tar.gz Simple-28209703c72fb7e9955961dbdb84e24a0f871acd.zip | |
Chapter 25: Moduleslinear-chapter25
Diffstat (limited to 'src/main/java')
164 files changed, 10422 insertions, 3897 deletions
diff --git a/src/main/java/com/seaofnodes/simple/IterPeeps.java b/src/main/java/com/seaofnodes/simple/IterPeeps.java index 18ff470a..9fac7a99 100644 --- a/src/main/java/com/seaofnodes/simple/IterPeeps.java +++ b/src/main/java/com/seaofnodes/simple/IterPeeps.java @@ -1,6 +1,7 @@ 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; @@ -39,7 +40,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. Its a useful + * check is linear at each peephole step... so quadratic overall. It's 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 @@ -80,6 +81,10 @@ 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 ) { @@ -117,22 +122,35 @@ public class IterPeeps { Node changed = code._stop.walk( n -> { Node m = n; Type nval = n.compute(); - 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; - } - System.err.println("BREAK HERE FOR BUG"); + + // Types must be forwards, even if on the worklist. + boolean checkType = !n.iskeep() || + n._nid <= 8 || + (n instanceof ProjNode && n.in(0) instanceof StartNode); + + boolean monotonic = dir + ? nval.isa(n._type) // Pesi: new value lifts over old + : n._type.isa(nval); // Opto: new value falls over old + assert !checkType || monotonic : "Non-monotonic peep: "+n+"#"+n._nid+" old="+n._type+" new="+nval+" inputs="+inputTypes(n)+" peep="+m; + if( list.on(n) ) + return null; + + assert n.nOuts() > 0 : "Unused live node: "+n; + m = n.peepholeOpt(); + assert m==null : "Peep fired and not on worklist, "+n.getClass().getSimpleName()+"#"+n._nid+" -> "+m; 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(); + } + /** * Classic WorkList, with a fast add/remove, dup removal, random pull. * The Node's nid is used to check membership in the worklist. @@ -180,12 +198,18 @@ 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); } boolean isEmpty() { return _len==0; } + Node[] asAry() { return Arrays.copyOf(_es,_len); } /** * Removes a random Node from the WorkList; null if WorkList is empty diff --git a/src/main/java/com/seaofnodes/simple/Parser.java b/src/main/java/com/seaofnodes/simple/Parser.java index f80e8f2c..4c2dfc84 100644 --- a/src/main/java/com/seaofnodes/simple/Parser.java +++ b/src/main/java/com/seaofnodes/simple/Parser.java @@ -1,12 +1,14 @@ package com.seaofnodes.simple; -import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.print.GraphVisualizer; import com.seaofnodes.simple.type.*; import com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.util.Utils; import com.seaofnodes.simple.node.ScopeNode.Kind; + +import static com.seaofnodes.simple.util.Utils.TODO; + import java.util.*; /** @@ -18,16 +20,32 @@ 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 @@ -79,34 +97,16 @@ public class Parser { */ public final Stack<ScopeNode> _xScopes = new Stack<>(); - ScopeNode _continueScope; - ScopeNode _breakScope; // Merge all the while-breaks here - FunNode _fun; // Current function being parsed + ScopeNode _breakScope; // Merge all the while-breaks here + ScopeNode _continueScope; // Merge all the while-continues here + ScopeNode _returnScope; // Merge all the function exits here + TypeStruct _ctorOpenStruct; // Open struct being initialized by an allocation constructor block. // Mapping from a type name to a Type. The string name matches // `type.str()` call. - 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<>() {{ + public static final HashMap<String, Type> TYPES = new HashMap<>(); + public static final HashMap<String, Type> INIT_TYPES = + new HashMap<>() {{ put("bool",TypeInteger.U1 ); put("byte",TypeInteger.U8 ); put("f32" ,TypeFloat .F32); @@ -124,170 +124,363 @@ 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(); } - public void parse() { + // 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(false,null,bulk); + merge.alias(alias,precise); + return peep(merge); + } + + + public Ary<FRefNode> parse( CompUnit ref ) { + assert _scope == null && _breakScope == null && _continueScope == null && _returnScope == null; + _lexer = new Lexer(ref._src); + // Starting Scope has control, memory, initial arguments + _scope = new ScopeNode(); _scope.define(ScopeNode.CTRL, Type.CONTROL , false, null, _lexer); _scope.define(ScopeNode.MEM0, TypeMem.BOT , false, null, _lexer); - _scope.define(ScopeNode.ARG0, TypeInteger.BOT, false, null, _lexer); - - ctrl(XCTRL); - _scope.mem(new MemMergeNode(false)); + // Track active scopes for Graph display + _xScopes.push(_scope); + ctrl(_code.XCTRL); + mem(con(TypeMem.BOT)); - // Parse the sys import - _lexer = new Lexer(sys.SYS); - while( !_lexer.isEOF() ) { - parseStatement(); - _lexer.skipWhiteSpace(); - } + // File-level struct declaration + _ref = ref; + _nestedType = ref._cname; + String typeName = addClzPrefix(ref._cname); - // Reset lexer for program text - _lexer = new Lexer(_code._src); - _xScopes.push(_scope); + ReturnNode clzret = parseStruct( true, typeName ); - // 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); + if( !_lexer.isEOF() ) throw _errorSyntax("unexpected"); - } 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"; + // At the top scope and every new var should be a FRef, and these + // should immediately resolve to a file-based name which is a Simple + // class type. + Ary<FRefNode> frefs = new Ary<>(FRefNode.class); + for( int i=2; i<_scope._vars._len; i++ ) { + FRefNode fref = (FRefNode)_scope._inputs.at(i); + CompUnit cu = ParseAll.findCompUnitOrThrow(_code,_ref,fref._name); + fref._con = TypeMemPtr.make((byte)2,TypeStruct.make(addClzPrefix(cu._cname),true),true); + frefs.push(fref); } - if( !_lexer.isEOF() ) throw _errorSyntax("unexpected"); + // The class being published + ref._clz = (TypeStruct)TYPES.get(typeName); // Clean up and reset _xScopes.pop(); _scope.kill(); - for( StructNode init : INITS.values() ) - init.unkeep().kill(); - INITS.clear(); - stop.peephole(); + _scope = null; + + return frefs; } + + /** * Parses a function body, assuming the header is parsed. */ - private ReturnNode parseFunctionBody( TypeFunPtr sig, Lexer loc, 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(); + private ReturnNode parseFunctionBody( TypeFunPtr sig, Lexer loc, 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,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) { + boolean isInit = FunNode.isInit(funName); + boolean isCtor = isInit || isExplicitConstructorName(funName); + String typeName = isCtor ? ((TypeMemPtr)sig._sig[0])._obj._name : null; // Stack parser state on the local Java stack, and unstack it later - FunNode oldfun = _fun; - ScopeNode breakScope = _breakScope; _breakScope = null; + _scope.push(new Kind.Func(funName)); + ScopeNode breakScope = _breakScope; _breakScope = null; ScopeNode continueScope = _continueScope; _continueScope = null; + ScopeNode returnScope = _returnScope; _returnScope = null; - FunNode fun = _fun = (FunNode)peep(new FunNode(loc(),sig,_nestedType,null,_code._start)); + int oldUID = _code.UID(); // Used to approximate function size + FunNode fun = (FunNode)peep(new FunNode(loc(),sig, funName, _ref, null, _ref._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,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 PhiNode(ScopeNode.MEM0,TypeMem.BOT,r,null).init(); - PhiNode rrez = new PhiNode(ScopeNode.ARG0,Type.BOTTOM,r,null).init(); - ReturnNode ret = new ReturnNode(r, rmem, rrez, rpc, fun).init(); - fun.setRet(ret); - assert ret.inProgress(); - _code._stop.addDef(ret); - + Node rpc = new ParmNode("$rpc",0,TypeRPC.BOT,fun,ConstantNode.seed(TypeRPC.BOT).peephole()).peephole(); // 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.push(new Kind.Func()); - ctrl(fun); // Scope control from function + _scope.ctrl(fun); // Scope control from function // Private mem alias tracking per function - MemMergeNode mem = new MemMergeNode(true); - mem.addDef(null); // Alias#0 - mem.addDef(new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,con(TypeMem.BOT)).peephole()); // All aliases - _scope.mem(mem); + Node defaultMem = new ProjNode(_ref._start,1,ScopeNode.MEM0).peephole(); + Node privMem = new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,defaultMem).peephole(); + mem(privMem); // All args, "as-if" called externally for( int i=0; i<ids.length; i++ ) { Type t = sig.arg(i); - _scope.define(ids[i], t, false, new ParmNode(ids[i],i+2,t,fun,con(t)).peephole(), loc); + // Args take a default input if called from Start/external-world + _scope.define(ids[i], t, i==0 && ids[0]=="self", new ParmNode(ids[i],i+2,t,fun,ConstantNode.seed(t).peephole()).peephole(), loc); } - // Parse the body - Node last=ZERO; - while (!peek('}') && !_lexer.isEOF()) - last = parseStatement(); + // Parse the function body. + Node last=_code.ZERO; // Last statement as the default result - // 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().merge(), last); - else { - fun.setDef(1,XCTRL); // Kill default main - _code.addAll(fun._outputs); + 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() ) { + // <clinit> only returns an integer, which becomes an OS exit code + // if the <clinit> is run directly + //if( !(last._type instanceof TypeInteger) ) + // last = _code.ZERO; + assert last!=null; + } else if( isCtor ) { + last = fun.parm(3); } - - // 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; + 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; - - // 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()); + _breakScope = breakScope; + _scope.pop(); return ret; } + 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 = old == null + ? Field.make(v._name,v.type(),_code.alias(_ref._cname),v._final) + : Field.make(v._name,v.type(), old._alias, old._final); + 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() ) ); + } + } + + // 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); + } + + // 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 ) continue; // Forward refs not declared here + // NewNode's TOP field is the zero-code poison for a required + // field. The parser Var retains the typed nullable state for + // early-read diagnostics; only a user constructor writes memory. + if( upgrade && v._uninit ) continue; + Node val = _returnScope.in(vidx); + if( !upgrade && v._uninit && val._type instanceof TypeNil tn && tn.nullable() ) + throw error("'"+tself._name+"' is not fully initialized, field '"+ + fld._fname+"' is only partially set in the constructor",v._loc); + // 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); + } + + 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() { + if( !(_scope.klast() instanceof Kind.Func func) ) + return false; + return isExplicitConstructorName(func._name); + } + + private boolean isExplicitConstructorName(String funName) { + TypeStruct self = _scope.constructorSelf(); + return self != null && funName == (self._name+"."+initFieldName(self._name)).intern(); + } + + 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._t instanceof TypeFunPtr ? fld : fld.makeFrom(fld._t.glb(true)); + } + return TypeStruct.make(self._name,self._open,fs); + } + + /** * Parses a block * @@ -300,9 +493,11 @@ public class Parser { private Node parseBlock(Kind kind) { // Enter a new scope _scope.push(kind); - Node last = ZERO; - while (!peek('}') && !_lexer.isEOF()) + Node last = _code.ZERO; + while (!peek('}') && !_lexer.isEOF()) { + _code.add(last); last = parseStatement(); + } // Exit scope last.keep(); _scope.pop(); @@ -325,8 +520,9 @@ 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("#showGraph")) return require(showGraph(),";"); - else if (matchx(";") ) return null; // Empty statement + else if (matchx(";") ) return _code.ZERO; // Empty statement // Break ambiguity around leading function types and starting a block else if (peek('{') && !isTypeFun() ) { match("{"); @@ -336,6 +532,31 @@ 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 * @@ -386,7 +607,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(),ctrl()).peephole()); // Note we set back edge to null here + ctrl(new LoopNode(loc(),null,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). @@ -476,12 +697,12 @@ public class Parser { _xScopes.push( exit ); } _scope = exit; - return ZERO; + return _code.ZERO; } private ScopeNode jumpTo(ScopeNode toScope) { ScopeNode cur = _scope.dup(); - ctrl(XCTRL); // Kill current scope + ctrl(_code.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() ) @@ -499,7 +720,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 ZERO; } + private Node parseContinue() { checkLoopActive(); _continueScope = require(jumpTo( _continueScope ),";"); return _code.ZERO; } private Node parseBreak () { checkLoopActive(); // At the time of the break, and loop-exit conditions are only valid if @@ -508,7 +729,7 @@ public class Parser { _breakScope.removeGuards(_breakScope.ctrl()); _breakScope = require(jumpTo(_breakScope ),";"); _breakScope.addGuards(_breakScope.ctrl(), null, false); - return ZERO; + return _code.ZERO; } // Look for an unbalanced `)`, skipping balanced @@ -518,7 +739,7 @@ public class Parser { // Next X char handles skipping complex comments switch( _lexer.nextXChar() ) { case Character.MAX_VALUE: - throw Utils.TODO(); + throw TODO(); case ')': if( --paren<0 ) { posT( pos() - 1 ); @@ -568,7 +789,7 @@ public class Parser { case ":" -> parseAsgn(); // pred ? asgn; case "&&" -> parseLogical(); // pred && expr case "||" -> pred; // pred || expr_is_ignored - default -> throw Utils.TODO(); + default -> throw TODO(); }; lhs.keep(); _scope.removeGuards(ifT); @@ -593,7 +814,7 @@ public class Parser { case ":" -> (doRHS=match(fside)) ? parseAsgn() : con(lhs._type.makeZero()); case "&&" -> rhs = pred; case "||" -> rhs = parseLogical(); - default -> throw Utils.TODO(); + default -> throw TODO(); }; rhs.keep(); _scope.removeGuards(ifF); @@ -616,10 +837,10 @@ public class Parser { // Merge results RegionNode r = ctrl(tScope.mergeScopes(fScope,loc())); - Node ret = peep(new PhiNode("",lhs._type.meet(rhs._type).glb(false),r,lhs.unkeep(),rhs.unkeep())); + 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; + if( !fside.equals("else") && (err=ret.err()) !=null ) throw err; r.peephole(); return ret; } @@ -635,11 +856,56 @@ public class Parser { */ private Node parseReturn() { var expr = require(parseAsgn(), ";"); + return addReturn(expr); + } + private Node addReturn(Node expr) { + expr.keep(); // Need default memory, since it can be lazy, need to force // a non-lazy Phi - _fun.addReturn(ctrl(), _scope.mem().merge(), expr); - ctrl(XCTRL); // Kill control - return expr; + 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); + } + } + + // 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", expr._type.glb(false), 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(); } /** @@ -647,7 +913,7 @@ public class Parser { * @return {@code null} */ Node showGraph() { - System.out.println(new GraphVisualizer().generateDotOutput(_code._stop,_scope,_xScopes)); + System.out.println(new GraphVisualizer().generateDotOutput(_ref,_scope,_xScopes)); return null; } @@ -656,7 +922,6 @@ public class Parser { 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('=','=') ) @@ -665,62 +930,72 @@ 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 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` - Node self = _scope.in(_scope.lookup("self")); // Insert self reference - 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 // outside the constructor need to check the final bit. 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.inAllocation() && def._idx >= _scope._kinds.last(-1)._lexSize) ) + !(_scope.inConstructor() && def._idx >= _scope.klast()._lexSize) && + !inExplicitConstructor() ) throw error("Cannot reassign final '"+name+"'"); // Parse assignment expression Node expr = parseAsgn(); // Lift expression, based on type - Node lift = liftExpr(expr.keep(), def.type(), def._final, true); + Type decl = def.type(); // Declared type + Node lift = convertExpr(expr.keep(), decl).keep(); // 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(); } - // Make finals deep; widen ints to floats; narrow wide int types. + // Widen ints to floats; narrow wide int types. // Early error if types do not match variable. - private Node liftExpr( Node expr, Type t, boolean xfinal, boolean isLoad ) { - if( expr._type instanceof TypeMemPtr tmp && TYPES.get(tmp._obj._name).isFRef() ) - throw error("Must define forward ref "+tmp._obj._name); - // Final is deep on ptrs - if( xfinal && t instanceof TypeMemPtr tmp ) { - t = tmp.makeRO(); - expr = peep(new ReadOnlyNode(expr)); - } + private Node convertExpr( Node expr, Type t ) { + // 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() ) - expr = peep(new AddNode(peep(new CastNode(t,ctrl(),expr)),off(tmp._obj,"[]"))); - // Auto-widen int to float - expr = widenInt( expr, t ); - // Auto-narrow wide ints to narrow ints. For loads, emit code to force - // the loaded value to match the declared sign/zero bits. For stores, - // just force the type, acting "as if" the store silently truncates. - Type et = expr._type; - if( isLoad ) { expr = zsMask(expr,t); et = expr._type; } - else if( et instanceof TypeInteger && t instanceof TypeInteger ) et=t; - - // Type is sane - if( et!=Type.BOTTOM && !et.shallowISA(t) ) - expr = peep(new CastNode(t.isConstant() ? t : t.widen(),null,expr)); - return expr; + return peep(new AddNode(peep(new PtrToIntNode(expr)),off(tmp._obj,"[]"))); + // Null check has to eventually pass + if( t == Type.NIL && expr._type instanceof TypeMemPtr tmp && tmp.notNull() ) + return peep(new CheckCastNode(t,null,expr)); + // Widen ints to floats, narrow wide types + return peep(new ConvertNode(t,expr)); } private Node widenInt( Node expr, Type t ) { @@ -761,11 +1036,17 @@ public class Parser { // Optional initializing expression follows boolean xfinal = false; boolean fld_final = false; // Field is final, but not deeply final + boolean uninit = false; // Required non-null constructor field Node expr; if( match("=") ) { - expr = isExternDecl() - ? externDecl(name,t) - : parseAsgn(); + if( isExternDecl() ) { + 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(); + } // TOP means val and val is always final xfinal = (t==Type.TOP) || expr instanceof ExternNode || @@ -773,8 +1054,6 @@ public class Parser { (t!=Type.BOTTOM && // no Bang AND !hasBang && - // Locals are not-final by default - !_scope.inFunction() && // not-null (expecting null to be set to not-null) expr._type != Type.NIL && // Pointers are final by default; int/flt are not-final by default. @@ -787,13 +1066,23 @@ public class Parser { t = expr._type; if( !xfinal ) t = t.glb(false); // Widen if not final } + + // Final is deep on ptrs + if( xfinal && t instanceof TypeMemPtr tmp ) { + t = tmp.makeRO(); + expr = peep(new ReadOnlyNode(expr)); + } + // expr is a constant 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 + FunNode fun = _code.link(tfp); + if( fun != null ) + fun.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. @@ -801,34 +1090,33 @@ public class Parser { throw errorSyntax("=expression"); // Initial value for uninitialized struct fields. expr = switch( t ) { - // 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 -> ZERO; + // 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 -> _code.ZERO; case TypeFloat tf -> con(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); } }; - // 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() && !hasBang ) - fld_final = true; + // Every non-null pointer without a default is constructor-required. + // Without `!` it is also shallow-final; mutability and definite + // initialization are independent properties. + if( t instanceof TypeNil tn && !tn.nullable() ) { + uninit = true; + if( !hasBang ) fld_final = true; + } } // Lift expression, based on type - Node lift = liftExpr(expr, t, xfinal, true); - // Rebuild the deep RO 't', not returned from liftExpr - if( xfinal && t instanceof TypeMemPtr tmp ) - t = tmp.makeRO(); - - // Lift type to the declaration. This will report as an error later if - // we cannot lift the type. - if( !lift._type.isa(t) ) - lift = peep(new CastNode(t.widen(),null,lift)); + // 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); + // 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; return lift; } @@ -840,59 +1128,132 @@ public class Parser { * * @return zero */ - private String _nestedType; - private Node parseStruct() { - // "struct" already parsed, so expect the struct name next. - String old = _nestedType; + private Node parseStruct( ) { + // keyword "struct" already parsed, so expect the struct name next. 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 && _scope.in( v._idx )._type != Type.TOP ) ); - TYPES.put( typeName, TypeMemPtr.make( ts ) ); - } - } - } - // Count struct declarations - int varlen = _scope._vars._len; - ts = ts.close(); - TYPES.put(typeName,TypeMemPtr.make(ts)); + // create type class:NESTED.typeName; no fields. - // 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 + 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 - // 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()); + ReturnNode ret = parseStruct( false, fullName ); - // 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 ); + _nestedType = old; + // 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>"); + ts = ctorFld==null + ? TypeStruct.make(clzName,false,fld) + : TypeStruct.make(clzName,false,fld,ctorFld); + 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("}"); - require(";"); - _scope.pop(); - return ZERO; + return require(_code.ZERO,";"); + } + + // 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 ) { + // 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()); + } + + // 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); + // 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); } @@ -902,48 +1263,146 @@ 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(); // Only type with a leading `{` is a function pointer... if( peek('{') ) return typeFunPtr(); // Otherwise you get a type name + int old1 = pos(); String tname = _lexer.matchId(); if( tname==null ) return null; // Convert the type name to a type. - Type t0 = TYPES.get(tname); + Type t0 = TYPES.get(tname), t1 = t0; // No new types as keywords - if( t0 == null && KEYWORDS.contains(tname) ) - return posT(old1); - if( t0 == Type.BOTTOM || t0 == Type.TOP ) return t0; // var/val type inference + 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); - // 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 // Nest arrays and '?' as needed Type t2 = t1; while( true ) { if( match("?") ) { - if( !(t2 instanceof TypeMemPtr tmp) ) + if( t2 instanceof TypeMemPtr tmp ) { + if( tmp.nullable() ) throw error("Type "+t2+" already allows null"); + t2 = tmp.makeNullable(); + } else throw error("Type "+t0+" cannot be null"); - if( tmp.nullable() ) throw error("Type "+t2+" already allows null"); - t2 = tmp.makeNullable(); } else if( match("[~]") ) { - t2 = typeAry(t2,true); + t2 = TypeMemPtr.make(typeAry(t2,true)); } else if( match("[]") ) { - t2 = typeAry(t2,false); + t2 = TypeMemPtr.make(typeAry(t2,false)); } else break; } @@ -955,28 +1414,63 @@ public class Parser { int old2 = pos(); match("!"); String id = _lexer.matchId(); - if( !(peek(',') || peek(';') || match("->")) || id==null ) + if( !(peek(',') || 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 - TYPES.put(tname,t1); + forwardRefType(tname,(TypeStruct)((TypeMemPtr)t1)._obj); // Return the (array, final) type return t2; } - // Make an array type of t - private TypeMemPtr typeAry( Type t, boolean efinal ) { + 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 ) { 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) )); - Field elem = ta._obj.field("[]"); - if( elem._final == efinal ) - return ta; - return TypeMemPtr.make( ta._obj.replace(elem.makeFrom(efinal))); + 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); + } + if( !efinal ) return ta; + // Already have the aliases, just efinal is wrong + return TypeStruct.makeAry(tname,TypeInteger.U32,ta._fields[0]._alias,t,ta._fields[1]._alias,true ); + } + + 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); } // A function type is `{ type... -> type }` or `{ type }`. @@ -985,16 +1479,19 @@ public class Parser { match("{"); // Skip already-peeked '{' Type t0 = type(); // Either return or first arg if( t0==null ) return posT(old); // Not a function - if( match("}") ) // No-arg function { -> type } - return TypeFunPtr.make(match("?"),false,TypeFunPtr.TEMPTY,t0); 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); 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("?"),false,ts.asAry(),ret); + return TypeFunPtr.make(match("?"),ts.asAry(),ret); } Type t1 = type(); if( t1==null ) return posT(old); // Not a function @@ -1070,7 +1567,7 @@ public class Parser { else if( !match("!=") ) break; lhs.keep(); Node rhs = parseComparison(); - lhs = peep(new BoolNode.EQ(lhs.unkeep(),rhs).widen()); + lhs = peep(new BoolNode.EQ(lhs.unkeep(),rhs)); if( !eq ) lhs = peep(new NotNode(lhs)); } return lhs; @@ -1087,46 +1584,64 @@ public class Parser { */ private Node parseComparison() { Node lhs = parseShift(); - int dir = 0; - - while( true ) { - int dir0; - if( false ) ; - else if( match("<=" ) ) dir0 = -1; - else if( match(">=" ) ) dir0 = -2; - else if( match("<" ) ) dir0 = 1; - else if( match(">" ) ) dir0 = 2; - else break; - lhs.keep(); - // Check and record direction - if( dir==0 ) { - dir = Math.abs(dir0); - Node rhs = parseShift(); - lhs = makeCompBool(dir0,lhs.unkeep(),rhs); // Convert to a bool - } else if( dir == Math.abs(dir0) ) { - // e0 < lhs < ??? - Node ifNode = new IfNode(ctrl(), lhs).peephole(); - Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole(); - Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole(); - // False side does nothing - ScopeNode fScope = _scope.dup(); - fScope.ctrl(ifF); - // True side executes next part of range - ctrl(ifT); - _scope.addGuards(ifT,lhs,false); - Node rhs = parseShift(); - _scope.removeGuards(ifT); // TODO: I think should remain true as long as possible - Node next = makeCompBool(dir0, lhs, rhs).keep(); - // Merge result - RegionNode r = ctrl(_scope.mergeScopes(fScope, loc())); - assert next._type.meet(lhs._type).isa( TypeInteger.BOOL ); - lhs = peep(new PhiNode("",TypeInteger.BOOL,r,next.unkeep(),lhs.unkeep())); - r.peephole(); - } else { + int dir = parseCompDir(); + // No compare + if( dir==0 ) return lhs; + + // Compare + Node rhs = parseShift().keep(); + Node cmp = makeCompBool(dir,lhs,rhs); // Convert to a bool + + // Stacked compares? + int dir0 = parseCompDir(); + if( dir0 == 0 ) { + rhs.unkeep(); + if( rhs != cmp ) rhs.isKill(); + return cmp; + } + + // rhs is keeped() and becomes lhs + // cmp is NOT keeped() and is the last test + Node ifNode = new IfNode(ctrl(), cmp).peephole(); + Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole(); + Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole(); + // False side does nothing but capture memory & side-effects + ScopeNode fail = _scope.dup(); + fail.ctrl(ifF); + + // Loop over stacked compares. Each loop moves the old RHS to become + // the new LHS, so e.g. (a < b < c < d) compares (LHS:a < RHS:b) then + // compares (LHS:b < RHS:c) then (LHS:c < RHS:d), etc. + while( dir0 != 0 ) { + if( Math.abs(dir) != Math.abs(dir0) ) throw error("Mixing relational directions in a chained relational test"); - } - } - return lhs; + // True side parses next arm of test + ctrl(ifT); + lhs = rhs; // lhs is keeped() and is old RHS + rhs = parseShift().keep(); + cmp = makeCompBool(dir,lhs.unkeep(),rhs); // Convert to a bool + ifNode = new IfNode(ctrl(), cmp).peephole(); + ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole(); + ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole(); + // Merge result into the fail case + ctrl(ifF); + fail.mergeScopes(_scope.dup(), loc()).peephole(); + // Check next stacked compare + dir0 = parseCompDir(); + } + rhs.unkill(); + ctrl(ifT); + RegionNode r = fail.mergeScopes(_scope, loc()).init(); + _scope = fail; + return new PhiNode("", r,_code.ZERO,con(1)).peephole(); + } + + private int parseCompDir() { + if( match("<=") ) return -1; // Negative for "equals-or-less" + if( match(">=") ) return -2; // 1 for <, 2 for > + if( match("<" ) ) return 1; // Positive for just "less" + if( match(">" ) ) return 2; + return 0; } private Node makeCompBool( int dir0, Node lhs, Node rhs ) { @@ -1134,10 +1649,9 @@ public class Parser { 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.widen()); + return peep(lhs); } - /** * Parse an additive expression * @@ -1158,7 +1672,7 @@ public class Parser { ParseException err; if( (err=lhs.err()) != null ) throw err; - lhs = peep(lhs.widen()); + lhs = peep(lhs); } return lhs; } @@ -1179,7 +1693,7 @@ public class Parser { else if( match("-") ) lhs = new SubNode(lhs,null); else break; lhs.setDef(2,parseMultiplication()); - lhs = peep(lhs.widen()); + lhs = peep(lhs); } return lhs; } @@ -1200,7 +1714,7 @@ public class Parser { else if( match("/") ) lhs = new DivNode(lhs,null); else break; lhs.setDef(2,parseUnary()); - lhs = peep(lhs.widen()); + lhs = peep(lhs); } return lhs; } @@ -1217,16 +1731,14 @@ public class Parser { // Pre-dec/pre-inc int old = pos(); if( match("--") || match("++") ) { - int delta = _lexer.peek(-1)=='+' ? 1 : -1; // Pre vs post + int delta = _lexer.peek(-1)=='+' ? 1 : -1; // Inc vs Dec String name = _lexer.matchId(); if( name!=null ) { Var n = _scope.lookup(name); if( n != null && !(n.type() instanceof TypeMemPtr) ) { if( n._final ) throw error("Cannot reassign final '"+n._name+"'"); - Node expr = n.type() instanceof TypeFloat - ? peep(new AddFNode(_scope.in(n),con(TypeFloat.constant(delta)))) - : zsMask(peep(new AddNode(_scope.in(n),con(delta))),n.type()); + Node expr = zsMask(peep(new AddNode(_scope.in(n),con(delta))),n.type()); _scope.update(n,expr); return expr; } @@ -1234,7 +1746,7 @@ public class Parser { // Reset, try again pos(old); } - if (match("-")) return peep(new MinusNode(parseUnary()).widen()); + if (match("-")) return peep(new MinusNode(parseUnary())); if (match("!")) return peep(new NotNode(parseUnary())); // Else be a primary expression return parsePrimary(); @@ -1246,7 +1758,7 @@ public class Parser { * <pre> * primaryExpr : integerLiteral | "string" | 'char' | true | false | null | * new Type | '(' expression ')' | Id['++','--'] | - * [static.]* Id + * type['.' Id]* * </pre> * @return a primary {@link Node}, never {@code null} */ @@ -1254,95 +1766,113 @@ public class Parser { if( _lexer.isNumber(_lexer.peek()) ) return parseLiteral(); if( _lexer.peek('"') ) return newString(parseString()); if( matchx("true" ) ) return con(1); - if( matchx("false") ) return ZERO; - if( matchx("null" ) ) return NIL; + if( matchx("false") ) return _code.ZERO; + if( matchx("null" ) ) return con(Type.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('.') ) - return parsePostfix(con(t)); - //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 ) { - // If missing, assume a forward reference - _scope.define(id, FRefNode.FREF_TYPE, true, XCTRL, loc()); - var = _scope.lookup(id); - } + // 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 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) ) { - Node self = _scope.in(_scope.lookup("self")); // Insert self reference - return parsePostfixName(self,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,cu!=null,loc()); } // Load local value Node rvalue = _scope.in(var); - if( rvalue._type == Type.BOTTOM ) - if( rvalue instanceof FRefNode ) - return parsePostfix(rvalue); - else throw error("Cannot read uninitialized field '"+id+"'"); + 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); + + // 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 ); + //return parsePostfixName(_scope.in(2),id); + } + } // Check for assign-update, x += e0; char ch = _lexer.matchOperAssign(); - if( ch==0 ) // Normal primary, check for postfix updates - return parsePostfix(rvalue); + 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(selfx)); + } // Assign-update direct into Scope Node op = opAssign(ch,rvalue, var.type() ); _scope.update(var,op); 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. @@ -1365,11 +1895,11 @@ public class Parser { case '-' -> new SubNode(lhs,rhs); case '*' -> new MulNode(lhs,rhs); case '/' -> new DivNode(lhs,rhs); - default -> throw Utils.TODO(); + default -> throw TODO(); }; // Convert to float ops, or narrow int types; error if not declared type. // Also, if postfix LHS is still keep() - return liftExpr(peep(op.widen()),t,false,true); + return convertExpr(peep(op),t.glb(false)); } @@ -1378,124 +1908,163 @@ public class Parser { */ private Node alloc() { Type t = type(); - if( t==null ) throw error("Expected a type"); + if( t==null ) { + String tname = _lexer.matchId(); + if( tname==null || KEYWORDS.contains(tname) ) + throw error("Expected a type"); + t = forwardRefType(tname); + } // Parse ary[ length_expr ] if( match("[") ) { if( !t.makeZero().isa(t) ) - 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); + throw error("Cannot allocate a non-nullable, since arrays are always zero/null filled"); + Node len = parseAsgn().keep(); require("]"); - 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( init.at(i)._type == Type.TOP || init.at(i)._type == Type.BOTTOM ) - 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; + 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(); // CNC TODO- BAD THREADING SELF-MEM + Node size = off(ts, " len"); + + // Build a NewNode; takes in ctrl and size. + // Produces a ptr and a private mem. + 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(); + + // Find a "class:XXX" struct, with field "XXX" function ptr as the <init> + TypeStruct clz = (TypeStruct)TYPES.get(addClzPrefix(ts._name)); + if( clz==null ) + throw error("Unknown struct type '" + ts._name + "'"); + TypeFunPtr init = (TypeFunPtr)clz.field(initFieldName(ts._name))._t; + + // Call construct <init>($ctrl,$mem,NewNode.self,NewNode.#selfMem) and + // encourage inlining + Node initSelf = self._type.isa(init.arg(0)) ? self : peep(new CheckCastNode(init.arg(0),ctrl(),self)); + Ary<Node> args = new Ary<>(Node.class){{add(ctrl()); add(mem()); add(initSelf); add(smem); add(ConstantNode.seed(init)); }}; + Node selfMem = functionCall( args ); + // The returned value is a merge of private *Memory* and NOT some Scalar + + // Optional user constructor; same as the default constructor; private + // memory going in and out. + Field ctor = clz.field("<ctor>"); + if( ctor != null ) + selfMem = constructorCall((TypeFunPtr)ctor._t,self,selfMem,ctorArgs); + else if( !ctorArgs.isEmpty() ) + throw error("Constructor arguments for '" + ts._name + "' but no constructor is defined"); + if( match("{") ) + throw error("Inline constructor blocks are no longer supported; define a constructor in '" + ts._name + "'"); + + // Might be TOP if parsing in dead/unreachable code + if( selfMem._type == Type.TOP ) { + _code.add(selfMem); + return self.unkeep(); + } + + // Escape all new aliases. EscapeNode inputs are the self pointer, the + // merged private memory, then all the named public aliases. The + // output is all the newly merged public aliases - but not actually + // bulk memory. + selfMem.keep(); + for( Field fld : ts._fields ) { + Field fld2 = fld._final ? fld : (Field)fld.glb(true); + Node prior = mem(); + Node esc = peep(new EscapeNode(fld2,self,selfMem,prior)); + mem(mergeAlias(prior,fld._alias,esc)); + } + + if( selfMem.unkeep().isUnused() ) selfMem.kill(); + else _code.add(selfMem); + 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 + "'"); - int len = fs.length; - Node[] ns = new Node[2+len]; - ns[0] = ctrl(); // Control in slot 0 - // Total allocated length in bytes - ns[1] = size; - // Memory aliases for every field - for( int i = 0; i < len; i++ ) - if( !fs[i]._one ) - ns[2+i] = memAlias(fs[i]._alias); - Node nnn = new NewNode(TypeMemPtr.make(obj), ns).peephole().keep(); - for( int i = 0; i < len; i++ ) - if( !fs[i]._one ) - memAlias(fs[i]._alias, new ProjNode(nnn,i+2,memName(fs[i]._alias)).peephole()); - Node ptr = new ProjNode(nnn.unkeep(),1,obj._name).peephole().keep(); - - // Initial nonzero values for every field - for( int i = 0; i < len; i++ ) { - Node val = init.get( i + idx ); - if( !fs[i]._one && val._type != val._type.makeZero() ) { - Node mem = memAlias(fs[i]._alias); - Node st = new StoreNode(loc(),fs[i]._fname,fs[i]._alias,fs[i]._t,mem,ptr,off(obj,fs[i]._fname),val,true).peephole(); - memAlias(fs[i]._alias,st); - } + 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; + } + + // Constructors are called with two extra arguments: self and selfMem. + private Node constructorCall(TypeFunPtr ctor, Node self, Node selfMem, Ary<Node> ctorArgs) { + Ary<Node> args = new Ary<>(Node.class); + Node ctorSelf = self._type.isa(ctor.arg(0)) + ? self + : peep(new CheckCastNode(ctor.arg(0),ctrl(),self)); + args.add(ctrl()); + args.add(mem()); + args.add(ctorSelf); + args.add(selfMem); + for( Node arg : ctorArgs ) + args.add(arg.unkeep()); + args.add(ConstantNode.seed(ctor)); - return ptr.unkeep(); + CallNode call = (CallNode)new CallNode(loc(), args.asAry()).peephole(); + CallEndNode cend = (CallEndNode)new CallEndNode(call,TypeRPC.constant(_code.rpc(_ref._cname))).peephole(); + call.peephole(); + cend = (CallEndNode)cend.keep().peephole(); + ctrl(new CProjNode(cend,0,ScopeNode.CTRL).peephole()); + mem (new ProjNode(cend,1,ScopeNode.MEM0).peephole()); + return new ProjNode(cend.unkeep(),2,"#selfMem").peephole(); } - 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 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(); } + private Node newString(String s) { - 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(); + 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()); // 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", TypeInteger.constant(s.length()), lenAlias, con, elemAlias, true),true); + TypeMemPtr str = TypeMemPtr.make((byte)2,TypeStruct.makeAry("[]u8", slen, lenAlias, body, elemAlias, true),true); assert str.isConstant(); return con(str); } - // We set up memory aliases by inserting special vars in the scope these - // variables are prefixed by $ so they cannot be referenced in Simple code. - // Using vars has the benefit that all the existing machinery of scoping - // and phis work as expected - private Node memAlias(int alias ) { return _scope.mem(alias ); } - private void memAlias(int alias, Node st) { _scope.mem(alias, st); } - public static String memName(int alias) { return ("$"+alias).intern(); } - /** * Parse postfix expression; this can be a field expression, an array * lookup or a postfix operator like '#' @@ -1508,17 +2077,34 @@ 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 + self.isKill(); // Not a method call, no need for self + return parsePostfix(expr); + } private Node parsePostfix(Node expr) { String name; if( match(".") ) name = requireId(); else if( match("#") ) name = "#"; else if( match("[") ) name = "[]"; - else if( match("(") ) return parsePostfix(require(functionCall(expr,null),")")); + // 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 return expr; // No postfix return parsePostfixName(expr,name); } + private Node defaultSelf() { + return _scope.in(_scope._kinds.at(_scope.enclosingFuncOrDecl())._lexSize); + } + /** * Parse postfix expression; this can be a field expression, an array * lookup or a postfix operator like '#' @@ -1529,97 +2115,96 @@ public class Parser { * expr ('[' expr ']')* [ [op]= expr ] // Array reference * </pre> */ + private Node parsePostfixName(Node expr, String name) { + // Keep expr across possible updates + expr.keep(); - 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) ) - throw error( "Expected "+(name=="#" || name=="[]" ? "array" : "reference")+" but found " + expr._type.str() ); - - // Find the field from the Type. Lookup in the base object field names. - TypeMemPtr ptr2 = (TypeMemPtr)TYPES.get(ptr._obj._name); - ptr = (TypeMemPtr)ptr.join(ptr2);// Upgrade to latest TYPES - TypeStruct base = ptr._obj; - int fidx = base.find(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(); + // 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 ) + fld = fieldOrOpen(ts,name,false); + // 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.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(); + 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(); // Disambiguate "obj.fld==x" boolean test from "obj.fld=x" field assignment if( matchOpx('=','=') ) { + // Field assignment Node val = parseAsgn().keep(); - Node lift = liftExpr( val, tf, f._final, false ); - - Node st = new StoreNode(loc(), name, f._alias, tf, memAlias(f._alias), expr, off.unkeep(), lift, false); - // 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() ) st.setDef(0,ctrl()); - memAlias(f._alias, st.peephole()); + // 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(); + 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(); + return val.unkeep(); // "obj.a = expr" returns the expression while updating memory } - // Keep expr for possible store update - expr.keep(); + // 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 = f._one && INITS.containsKey( base._name ) - ? INITS.get(base._name).in(base.find(name)) - : new LoadNode(loc(),name, f._alias, tf, memAlias(f._alias), 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); + Node load = peep(new LoadNode(loc(),name, alias, ctrl(), mem, expr, off)); // Check for assign-update, "ptr.fld += expr" or "ary[idx]++" char ch = _lexer.matchOperAssign(); if( ch!=0 ) { - Node op = opAssign(ch,load, tf ); - Node st = new StoreNode(loc(), name, f._alias, tf, memAlias(f._alias), expr.unkeep(), off.unkeep(), op, false); - // 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() ) st.setDef(0,ctrl()); - memAlias(f._alias, peep(st)); + 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 ); + mem.keep(); + Node st = new StoreNode(loc(), name, alias, decl.glb(true), ctrl(), mem, expr.unkeep(), off.unkeep(), op, false).peephole().keep(); + storeMem(st,alias,mem); + st.unkeep(); + mem.unkeep(); + load = postfix(ch) ? load.unkeep() : op; // And use the original loaded value as the result return load; - //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!="[]" && - !(load instanceof ExternNode) && - ((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(); } - - return parsePostfix(load); + off.unkill(); + // Might be a method call, so pass expr as 'self' + return parsePostfixMethod(load,expr.unkeep()); } @@ -1653,15 +2238,9 @@ public class Parser { private Node func() { Ary<Type> ts = new Ary<>(Type.class); Ary<String> ids = new Ary<>(String.class); - - // 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"); - } + Type self = implicitSelfType(); + ts.push(self); + ids.push(self == TypePtr.PTR ? "#self" : "self"); // Parse other arguments _lexer.skipWhiteSpace(); @@ -1676,9 +2255,42 @@ public class Parser { } require("->"); // Make a concrete function type, with a fidx - TypeFunPtr tfp = _code.makeFun(TypeFunPtr.make(false,false,ts.asAry(),Type.BOTTOM)); + TypeFunPtr tfp = TypeFunPtr.make1((byte)2,true,ts.asAry(),Type.BOTTOM,_code.fidx(_ref._cname)); ReturnNode ret = parseFunctionBody(tfp,loc,ids.asAry()); - return con(ret._fun.sig()); + 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); + 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; + 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,ids.asAry()); + _scope.restore(preParse); + require("}"); + return new FunPtrNode(tfp,_code._start,ret).peephole(); } /** @@ -1690,15 +2302,13 @@ public class Parser { private Node functionCall(Node fcn, Node self) { if( fcn._type == Type.NIL ) throw error("Calling a null function pointer"); - if( !(fcn instanceof FRefNode) && !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); + args.push(self.keep()); while( !peek(')') ) { Node arg = parseAsgn(); if( arg==null ) break; @@ -1707,11 +2317,15 @@ public class Parser { } // Control & memory after parsing args args.set(0,ctrl().keep()); - args.set(1,_scope.mem().merge().keep()); + args.set(1,mem().keep()); args.push(fcn); // Function pointer // Unkeep them all for( Node arg : args ) arg.unkeep(); + return functionCall(args); + } + + private Node functionCall( Ary<Node> args ) { // Dead into the call? Skip all the node gen if( ctrl()._type == Type.XCONTROL ) { for( Node arg : args ) @@ -1723,17 +2337,16 @@ public class Parser { // Into the call CallNode call = (CallNode)new CallNode(loc(), args.asAry()).peephole(); // Post-call setup - CallEndNode cend = (CallEndNode)new CallEndNode(call).peephole(); + CallEndNode cend = (CallEndNode)new CallEndNode(call,TypeRPC.constant(_code.rpc(_ref._cname))).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 - MemMergeNode mem = new MemMergeNode(true); - mem.addDef(null); // Alias#0 - mem.addDef(new ProjNode(cend,1,ScopeNode.MEM0).peephole()); - _scope.mem(mem); + Node mem = new ProjNode(cend,1,ScopeNode.MEM0).peephole(); + mem(mem); // Call result - return new ProjNode(cend,2,"#2").peephole(); + return new ProjNode(cend.unkeep(),2,"#2").peephole(); } // Just after parsing "type foo = " can parse `"C"` @@ -1745,10 +2358,14 @@ public class Parser { return false; } // External linked constant - 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(); + 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()); } /** @@ -1756,13 +2373,20 @@ public class Parser { * * <pre> * integerLiteral: [1-9][0-9]* | [0] - * floatLiteral: [digits].[digits]?[e [digits]]? + * floatLiteral: [digits].[digits]?[e [digits]]?[f]? * </pre> */ private ConstantNode parseLiteral() { return con(_lexer.parseNumber()); } - 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()); } + ConstantNode con( long con ) { return _code.con(con); } + ConstantNode con( Type t ) { return _code.con(t ); } + // 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 Node peep( Node n ) { // Peephole, then improve with lexically scoped guards return _scope.upcastGuard(n.peephole()); @@ -1772,11 +2396,27 @@ public class Parser { private String parseString() { if( !peek('"') ) return null; _lexer.inc(); - int start = pos(); - while( !_lexer.isEOF() && _lexer.nextChar()!= '"' ) ; - if( _lexer.isEOF() ) - throw error("Unclosed string"); - return new String(_lexer._input,start,pos()-start-1); + StringBuilder sb = new StringBuilder(); + while( !_lexer.isEOF() ) { + char c = _lexer.nextChar(); + if( c=='"' ) return sb.toString(); + if( c!='\\' ) { + sb.append(c); + continue; + } + if( _lexer.isEOF() ) + throw error("Unclosed string"); + char esc = _lexer.nextChar(); + sb.append(switch( esc ) { + case 'n' -> '\n'; + case 't' -> '\t'; + case 'r' -> '\r'; + case '\\' -> '\\'; + case '"' -> '"'; + default -> throw error("Unknown string escape \\\\"+esc); + }); + } + throw error("Unclosed string"); } // Already parsed "'" @@ -1784,6 +2424,14 @@ public class Parser { return require(con(TypeInteger.constant(_lexer.nextChar())),"'"); } + 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 @@ -1856,6 +2504,11 @@ public class Parser { // 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 && _input == that._input && _position < that._position; + } + /** * Record the source text for lexing */ @@ -2002,7 +2655,7 @@ public class Parser { // Used for errors String getAnyNextToken() { - if (isEOF()) return ""; + if (isEOF()) return "EOF"; if (isIdStart(peek())) return parseId(); if (isNumber(peek())) return parseNumberString(); if (isPunctuation(peek())) return parsePunctuation(); @@ -2016,17 +2669,26 @@ 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,-len))); + return TypeFloat.constant(Double.parseDouble(new String(_input,old,n))); } private String parseNumberString() { int old = _position; int len = Math.abs(isLongOrDouble()); + if( peek()=='f' ) { + nextChar(); + len++; + } return new String(_input,old,len); } @@ -2056,7 +2718,7 @@ public class Parser { private String parseId() { int start = _position; while (isIdLetter(nextChar())) ; - return new String(_input, start, --_position - start); + return new String(_input, start, --_position - start).intern(); } // @@ -2085,9 +2747,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+": " + _lexer.getAnyNextToken()); + return error("Syntax error, "+msg+" but found `" + _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/src/main/java/com/seaofnodes/simple/Var.java b/src/main/java/com/seaofnodes/simple/Var.java index cf549cd6..1906ce15 100644 --- a/src/main/java/com/seaofnodes/simple/Var.java +++ b/src/main/java/com/seaofnodes/simple/Var.java @@ -1,6 +1,7 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.Utils; /** @@ -15,6 +16,9 @@ public class Var { private Type _type; // Declared type 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 Parser.Lexer _loc; // Source location public Var(int idx, String name, Type type, boolean xfinal, Parser.Lexer loc ) { @@ -28,12 +32,8 @@ public class Var { _fref = fref; _loc = loc; } - 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)); - } + public Type type() { return _type; } + public Type _type() { return _type; } // ScopeNode printer needs to have no side effects // Forward reference variables (not types) must be BOTTOM and // distinct from inferred variables @@ -48,6 +48,6 @@ public class Var { } @Override public String toString() { - return _type.toString()+(_final ? " ": " !")+_name; + return (_fref ? "FREF " : "")+(_uninit ? "UNINIT " : "")+_type.toString()+(_final ? " ": " !")+_name; } } diff --git a/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java b/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java index e9b5eb2e..911e69f6 100644 --- a/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java +++ b/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java @@ -14,9 +14,14 @@ 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) { - for( Node bb : alloc._code._cfg ) + FunNode fun = null; + for( Node bb : alloc._code._cfg ) { + if( bb instanceof FunNode fun2 ) + fun = fun2; for( Node n : bb.outs() ) { if( n instanceof PhiNode phi && !(phi._type instanceof TypeMem) ) { + if( phi instanceof MachNode mach && mach.outregmap()==null ) + continue; // All 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); @@ -25,8 +30,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); - if( phi instanceof MachNode ) defLRG(alloc,n); + if( lrg==null ) lrg = alloc.newLRG(n,fun); + if( phi instanceof MachNode ) defLRG(alloc,n,fun); // Pass 2: everybody uses the same LRG lrg=alloc.union(lrg,phi); for( int i=phi instanceof ParmNode ? 2 : 1; i<n.nIns(); i++ ) @@ -40,13 +45,13 @@ abstract public class BuildLRG { int uidx = n.out(0)._inputs.find(n); RegMask mask1 = n.in (1) instanceof MachNode machx ? machx.outregmap() : alloc.lrg(n.in(1))._mask; RegMask mask2 = n.in (2) instanceof MachNode machx ? machx.outregmap() : alloc.lrg(n.in(2))._mask; - RegMask masko = n.out(0) instanceof MachNode machx ? machx.regmap(uidx): alloc.lrg(n )._mask; + RegMask masko = n.out(0) instanceof MachNode machx ? machx.regmap(uidx): mach.outregmap(); if( !mask1.overlap(masko) && mask2.overlap(masko) ) n.swap12(); } // Define live range - defLRG(alloc,n); + defLRG(alloc,n,fun); // Now, look in the opposite direction. How are incoming // LRGs affected by this node: For all uses, make live lrgs @@ -66,8 +71,9 @@ abstract public class BuildLRG { if( n instanceof MultiNode && !(n instanceof CFGNode) ) for( Node proj : n.outs() ) if( proj instanceof MachNode ) - defLRG(alloc,proj); + defLRG(alloc,proj,fun); } + } // Collect live ranges alloc.unify(); @@ -75,12 +81,12 @@ abstract public class BuildLRG { return alloc.success(); } - private static void defLRG( RegAlloc alloc, Node n ) { + private static void defLRG( RegAlloc alloc, Node n, FunNode fun ) { MachNode mach = (MachNode)n; RegMask def_mask = mach.outregmap(); if( def_mask == null ) return; LRG lrg = mach.twoAddress() == 0 - ? alloc.newLRG(n) // Define a new LRG for N + ? alloc.newLRG(n,fun) // 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/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java b/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java index 3715d015..85d8143b 100644 --- a/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java +++ b/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java @@ -5,78 +5,276 @@ 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.Ary; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; +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.*; + +// Compile Driver for a Compilation Unit @SuppressWarnings("unchecked") public class CodeGen { - 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". + + // 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 static final String PORTS = "com.seaofnodes.simple.node.cpus"; + + // Compile phases public enum Phase { Parse, // Parse ASCII text into Sea-of-Nodes IR + Iter, // Pessimistic peepholes after parse Opto, // Run ideal optimizations TypeCheck, // Last check for bad programs LoopTree, // Build a loop tree; break infinite loops + Serialize, // Serialize public IR for future compiles to link + 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; + + /** True when tests deliberately randomize Iter worklist order. */ + public static boolean iterSeedOverridden() { return System.getProperty("simple.iter.seed") != null; } + + // --------------------------- + // 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 in this compile; one per source/object file + 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; + throw Utils.TODO(); + } + + // True when fidx is published through a public field of a public class. + // TypeStruct.fidxs() already excludes private (underscore) fields. + public boolean publicFIDX(int fidx) { + // 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; + // Constructors and methods live on their class objects, including + // nested classes which do not have their own CompUnit. + for( Type t : Parser.TYPES.values() ) + if( t instanceof TypeStruct clz && + Parser.startsClzPrefix(clz._name) && + Parser.isPublicClassName(clz._name) && + resolvedClass(clz) && + publicClassFIDX(clz,fidx) ) + return true; + return false; + } + + private static boolean publicClassFIDX(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=1; 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; } + // --------------------------- - // Compilation source code - public final String _src; - // Compile-time known initial argument type - public final TypeInteger _arg; + + // Very common nodes, cached here + public final ConstantNode ZERO; + public final XCtrlNode XCTRL; + + public Node constant( Type t ) { + assert _phase==null || _phase.ordinal() <= Phase.Opto.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)); } + // --------------------------- - 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 ); } + // Test setup; no module nor file with specific argument + public CodeGen( String src ) { this(src, 126L ); } + + // 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, arg); + } + // Test setup; no module nor file; can alter seed & argument; can re-run same CodeGen + public CodeGen( String src, long workListSeed ) { + this(null,null,null,null, src, workListSeed, TypeInteger.BOT); + } - public CodeGen( String src, TypeInteger arg, long workListSeed, boolean reset ) { + // Generic CodeGen, including full module setup + public CodeGen( String modDir, String buildDir, Ary<String> externPaths, + String srcName, String src, long workListSeed, 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( reset ) Type.reset(); - _main = makeFun(TypeFunPtr.MAIN); - _phase = null; - _callingConv = null; - _start = new StartNode(arg); - _stop = new StopNode(src); + 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; - _arg = arg; - _iter = new IterPeeps(workListSeed); - P = new Parser(this,arg); + // 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); } - // All passes up to Phase, except ELF - public CodeGen driver( Phase phase ) { return driver(phase,null,null); } + // Run requested phases. + + // 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==null ) parse(); - int p1 = phase.ordinal(); - if( _phase.ordinal() < p1 && _phase.ordinal() < Phase.Opto .ordinal() ) opto(); - if( _phase.ordinal() < p1 && _phase.ordinal() < Phase.TypeCheck .ordinal() ) typeCheck(); - if( _phase.ordinal() < p1 && _phase.ordinal() < Phase.LoopTree .ordinal() ) loopTree(); - if( _phase.ordinal() < p1 && _phase.ordinal() < Phase.Select .ordinal() && cpu != null ) instSelect(cpu,callingConv); - if( _phase.ordinal() < p1 && _phase.ordinal() < Phase.Schedule .ordinal() ) GCM(); - if( _phase.ordinal() < p1 && _phase.ordinal() < Phase.LocalSched.ordinal() ) localSched(); - if( _phase.ordinal() < p1 && _phase.ordinal() < Phase.RegAlloc .ordinal() ) regAlloc(); - if( _phase.ordinal() < p1 && _phase.ordinal() < Phase.Encoding .ordinal() ) encode(); + 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); } + // Generic driver + public CodeGen driver( Phase phase, String cpu, String callingConv, boolean inMemory, boolean emitEntrySymbol, int dump ) { + int p1 = phase.ordinal(), p2 = _phase==null ? -1 : _phase.ordinal(); + if( p2 < p1 && p2 < Phase.Parse .ordinal() ) { parse(); p2 = dump(dump); } + if( p2 < p1 && p2 < Phase.Iter .ordinal() ) { iter(); p2 = dump(dump); } + if( p2 < p1 && p2 < Phase.Opto .ordinal() ) { opto(); p2 = dump(dump); } + if( p2 < p1 && p2 < Phase.TypeCheck .ordinal() ) { typeCheck(); p2 = dump(dump); } + if( p2 < p1 && p2 < Phase.LoopTree .ordinal() ) { loopTree(); p2 = dump(dump); } + if( p2 < p1 && p1 >= Phase.Encoding .ordinal() ) { unlinkImports(); p2 = dump(dump); } + if( p2 < p1 && p1 >= Phase.Encoding .ordinal() ) { serialize(); p2 = dump(dump); } // Include ideal graph in object file + if( p2 < p1 && p2 < Phase.Unlink .ordinal() ) { unlink(); p2 = dump(dump); } + if( p2 < p1 && p2 < Phase.Select .ordinal() && cpu != null ) { instSelect(cpu,callingConv); p2 = dump(dump); } + if( p2 < p1 && p2 < Phase.Schedule .ordinal() ) { GCM(); p2 = dump(dump); } + if( p2 < p1 && p2 < Phase.LocalSched.ordinal() ) { localSched();p2 = dump(dump); } + if( p2 < p1 && p2 < Phase.RegAlloc .ordinal() ) { regAlloc(); p2 = dump(dump); } + if( p2 < p1 && p2 < Phase.Encoding .ordinal() ) { encode(); p2 = dump(dump); } + if( p2 < p1 && p2 < Phase.Export .ordinal() ) { exportELF(inMemory,emitEntrySymbol); p2 = dump(dump); } return this; } - // 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); + public String entryClinitName() { + return Parser.addClzPrefix(_srcName==null ? "Test" : _srcName)+".<clinit>"; } + // Verbose printing during compilation + private int dump(int dump) { + int p2 = _phase.ordinal(); + if( (dump & (1<<p2)) == 0 ) return p2; + if( (dump & (1<<29)) != 0 ) { + String fn = ""+p2+"-"+_phase+".dot"; + try { + Files.writeString(Path.of(fn), + new GraphVisualizer().generateDotOutput(compunit(), null, null)); + } catch(IOException e) { throw Utils.TODO("Cannot write DOT file"); } + return p2; + } + + if( (dump & (1<<30)) != 0 ) + System.err.println("After "+_phase+":"); + System.err.println(IRPrinter.prettyPrint(this)); + return p2; + } + + + // Record times by phase + public final long[] _times = new long[Phase.LastPhase.ordinal()]; // --------------------------- /** @@ -89,9 +287,39 @@ public class CodeGen { return _uid++; } - // Next available memory alias number - private int _alias = 2; // 0 is for control, 1 for memory - public int getALIAS() { return _alias++; } + + // 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); } + + // 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); } // idepths are cached and valid until *inserting* CFG edges (deleting is @@ -116,30 +344,21 @@ 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 = new HashMap<>(); + public final HashMap<Node,Node> _gvn; - // 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 lookupFun(int fidx) { return _linker.atX(fidx); } + public FunNode lookupFun(TypeFunPtr tfp) { return lookupFun(tfp.fidx()); } // Return the FunNode from a fidx public FunNode link( int fidx ) { FunNode fun =_linker.atX(fidx); @@ -158,20 +377,48 @@ 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). - final Ary<FunNode> _linker = new Ary<>(FunNode.class); + 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; + } + + + // --------------------------- // Parser object public final Parser P; // Parse ASCII text into Sea-of-Nodes IR - public int _tParse; public CodeGen parse() { assert _phase == null; _phase = Phase.Parse; long t0 = System.currentTimeMillis(); - P.parse(); - _tParse = (int)(System.currentTimeMillis() - t0); + Parser.TYPES.clear(); + Parser.TYPES.putAll(Parser.INIT_TYPES); + if( _src != null ) { + // No source file, just the source itself. + ParseAll.parseSource(this,_srcName==null ? "Test" : _srcName,_src); + } else { + // Path from module root to source file + ParseAll.parsePath(this,_srcName); + } + + _times[Phase.Parse.ordinal()] = System.currentTimeMillis() - t0; JSViewer.show(); return this; } @@ -188,24 +435,64 @@ 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() { + assert _phase == Phase.Parse; + _phase = Phase.Iter; + long t0 = System.currentTimeMillis(); + + // 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: non-singleton + // memory must restart at FULL so newly discovered private escapes can + // only be removed, never added. Seed the complete graph once. + _start.walk(n -> { + n._type = iterEscapeReset(n._type); + add(n); + return null; + }); + _iter.iterate(this); + assert Opto.fixedPointCheck(this); + + _times[Phase.Iter.ordinal()] = System.currentTimeMillis() - t0; + return this; + } + + 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; + } + + // Run ideal optimizations - public int _tOpto; public CodeGen opto() { - assert _phase == Phase.Parse; + if( _phase == Phase.Parse ) + iter(); + assert _phase == Phase.Iter; _phase = Phase.Opto; long t0 = System.currentTimeMillis(); Opto.opto(this); - _tOpto = (int)(System.currentTimeMillis() - t0); + _times[Phase.Opto.ordinal()] = System.currentTimeMillis() - t0; return this; } 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 int _tTypeCheck; public CodeGen typeCheck() { // Demand phase Opto for cleaning up dead control flow at least, // required for the following GCM. @@ -213,33 +500,123 @@ public class CodeGen { _phase = Phase.TypeCheck; long t0 = System.currentTimeMillis(); - Parser.ParseException err = _stop.walk( Node::err ); - _tTypeCheck = (int)(System.currentTimeMillis() - t0); - if( err != null ) - throw err; + final Ary<Node> errs = new Ary<>(Node.class); + _stop.walk( n -> { + if( n.err() != null ) + errs.add(n); + return null; + }); + String unresolved = unresolvedTypeName(); + _times[Phase.TypeCheck.ordinal()] = System.currentTimeMillis() - t0; + 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; + throw min.err(); + } + if( unresolved != null ) + throw Parser.error("Unknown struct type '"+unresolved+"'",null); return this; } + private static boolean noBottomInputs(Node n) { + for( Node def : n._inputs ) + if( def!=null && !(def instanceof FldOffNode) && def._type==Type.BOTTOM ) + return false; + return true; + } + + // Forward-ref structs are open while parsing. They must all be resolved + // to closed structs before leaving type checking. + private String unresolvedTypeName() { + String[] unresolved = new String[1]; + _stop.walk(n -> { + TypeStruct ts = requiredStruct(n); + if( unresolved[0]==null && ts!=null && ts._fref ) + unresolved[0] = ts._name; + return null; + }); + return unresolved[0]; + } + + public static TypeStruct requiredStruct(Node n) { + 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; + } + // --------------------------- // Build the loop tree; break never-exit loops - public int _tLoopTree; public CodeGen loopTree() { - assert _phase.ordinal() <= Phase.TypeCheck.ordinal(); + assert _phase.ordinal() <= Phase.Serialize.ordinal(); _phase = Phase.LoopTree; long t0 = System.currentTimeMillis(); // Build the loop tree, fix never-exit loops - _start.buildLoopTree(_start,_stop); - _tLoopTree = (int)(System.currentTimeMillis() - t0); + _start.buildLoopTree( _linker, _stop); + // Invariant theory: No more type or code changes after Opto. The + // LoopTree stuff is used to do layouts now, but not core opts. TODO: + // at some future date we'll do loop opts (e.g. peeling, unrolling) + // that will totally impact code shape, types and core opts - but this + // will be done in tandem with Opto changes. + //_iter.iterate(this); + _times[Phase.LoopTree.ordinal()] = System.currentTimeMillis() - t0; return this; } // --------------------------- + public BAOS _serial; + public void serialize() { + assert _phase.ordinal() <= Phase.Serialize.ordinal(); + _phase = Phase.Serialize; + long t0 = System.currentTimeMillis(); + // Does not change compiler phase; just records IR + Serialize.serialize(this); + _times[Phase.Serialize.ordinal()] = System.currentTimeMillis() - t0; + } + + // Unlink imported function bodies before serialization. Calls to them + // remain as normal calls through their function pointer, and codegen later + // turns them into external relocations. + private void unlinkImports() { + assert _phase.ordinal() <= Phase.Serialize.ordinal(); + for( FunNode fun : _linker ) { + if( fun==null || fun.isDead() || owns(fun) ) + continue; + for( int i=1; i<fun.nIns(); i++ ) + if( fun.in(i) instanceof CallNode call ) + call.unlink(fun,i--); + } + //_iter.iterate(this); + } + + // --------------------------- // Code generation CPU target 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 @@ -252,10 +629,9 @@ public class CodeGen { public RegMask _rpcMask; // Convert to target hardware nodes - public int _tInsSel; public CodeGen instSelect( String cpu, String callingConv ) { return instSelect(cpu,callingConv,PORTS); } public CodeGen instSelect( String cpu, String callingConv, String base ) { - assert _phase.ordinal() == Phase.LoopTree.ordinal(); + assert _phase.ordinal() <= Phase.Unlink.ordinal(); _phase = Phase.Select; _callingConv = callingConv; @@ -267,12 +643,17 @@ public class CodeGen { try { _mach = ((Class<Machine>) Class.forName( clzFile )).getDeclaredConstructor(new Class[]{CodeGen.class}).newInstance(this); } catch( Exception e ) { throw new RuntimeException(e); } + _cCallingConv = _mach.cCallingConv(callingConv); + // Build global copies of common register masks. long callerSave = _mach.callerSave(); + long cCallerSave= _mach.callerSave(_cCallingConv); long neverSave = _mach. neverSave(); int maxReg = Math.min(64,_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 _callerSave = new RegMask(callerSave); + _cCallerSave= new RegMask(cCallerSave); // Build a Return RegMask array. All returns have the following inputs: // 0 - ctrl @@ -292,12 +673,20 @@ public class CodeGen { _uid = 1; // All new machine nodes reset numbering var map = new IdentityHashMap<Node,Node>(); _instSelect( _stop, map ); - _stop = ( StopNode)map.get(_stop ); + _stop = (StopNode)map.get(_stop ); StartNode start = (StartNode)map.get(_start); _start = start==null ? new StartNode(_start) : start; _instOuts(_stop,visit()); _visit.clear(); - _tInsSel = (int)(System.currentTimeMillis() - t0); + + // Replace the CompUnit stop (and list of functions) + // with hardware-specific ones + for( CompUnit cu : _compunits.values() ) { + cu._start= (StartCUNode)map.get(cu._start); + cu._stop = (StopCUNode )map.get(cu._stop ); + } + + _times[Phase.Select.ordinal()] = System.currentTimeMillis() - t0; return this; } @@ -319,11 +708,20 @@ public class CodeGen { // Produce a machine node from n; map it to flag as done so stops cycles. map.put(n, x=_mach.instSelect(n) ); + // Carry loop-tree and pre-order info across the ideal->mach transition + if( n instanceof CFGNode ncfg && x instanceof CFGNode xcfg ) { + xcfg._ltree = ncfg._ltree; + xcfg._pre = ncfg._pre; + } // 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) ); - if( x instanceof MachNode mach ) + // Post selection action + if( x instanceof MachNode mach ) { + if( n instanceof ReturnNode ret ) + ((ReturnNode)mach)._fun = (FunNode)map.get(ret._fun); mach.postSelect(this); // Post selection action + } return x; } @@ -335,14 +733,90 @@ public class CodeGen { 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); } } + // --------------------------- + public void unlink() { + assert _phase.ordinal() <= Phase.Select.ordinal(); + _phase = Phase.Unlink; + long t0 = System.currentTimeMillis(); + + // 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 : _linker ) { + // Already linked to start, not going dead + if( fun==null || fun.isDead() || fun._type.isHigh() ) + continue; + + // Imported functions are not emitted by this object. Unlink any + // concrete calls so they become normal external relocations. + if( !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 = _stop._inputs.find(stop); + if( idx != -1 ) + _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.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() ); + } + } + + _times[Phase.Unlink.ordinal()] = System.currentTimeMillis() - t0; + } // --------------------------- // Control Flow Graph in Reverse Post Order. - public int _tGCM; public Ary<CFGNode> _cfg = new Ary<>(CFGNode.class); // Global schedule (code motion) nodes @@ -353,28 +827,26 @@ public class CodeGen { long t0 = System.currentTimeMillis(); GlobalCodeMotion.buildCFG(this); - _tGCM = (int)(System.currentTimeMillis() - t0); + _times[Phase.Schedule.ordinal()] = System.currentTimeMillis() - t0; if( show ) - System.out.println(new GraphVisualizer().generateDotOutput(_stop,null,null)); + System.out.println(new GraphVisualizer().generateDotOutput(compunit(),null,null)); return this; } // --------------------------- // Local (basic block) scheduler phase, a classic list scheduler - public int _tLocal; public CodeGen localSched() { assert _phase == Phase.Schedule; _phase = Phase.LocalSched; long t0 = System.currentTimeMillis(); ListScheduler.sched(this); - _tLocal = (int)(System.currentTimeMillis() - t0); + _times[Phase.LocalSched.ordinal()] = System.currentTimeMillis() - t0; return this; } // --------------------------- // Register Allocation - public int _tRegAlloc; public RegAlloc _regAlloc; public CodeGen regAlloc() { assert _phase == Phase.LocalSched; @@ -382,15 +854,14 @@ public class CodeGen { long t0 = System.currentTimeMillis(); _regAlloc = new RegAlloc(this); _regAlloc.regAlloc(); - _tRegAlloc = (int)(System.currentTimeMillis() - t0); + _times[Phase.RegAlloc.ordinal()] = System.currentTimeMillis() - t0; return this; } - // Human readable register name - public String reg(Node n) { return reg(n,null); } - public String reg(Node n, FunNode fun) { + // Human-readable register name + public String reg(Node n) { if( _phase.ordinal() >= Phase.RegAlloc.ordinal() ) { - String s = _regAlloc.reg(n,fun); + String s = _regAlloc.reg(n); if( s!=null ) return s; } return "N"+ n._nid; @@ -399,50 +870,171 @@ public class CodeGen { // --------------------------- // Encoding - public int _tEncode; - public Encoding _encoding; // Encoding object - public void preEncode() { } // overridden by alternative ports + public Encoding _encoding; public CodeGen encode() { assert _phase == Phase.RegAlloc; _phase = Phase.Encoding; long t0 = System.currentTimeMillis(); - _encoding = new Encoding(this); - preEncode(); - _encoding.encode(); - _tEncode = (int)(System.currentTimeMillis() - t0); + + _encoding = new Encoding(this).encode(); + + _times[Phase.Encoding.ordinal()] = System.currentTimeMillis() - t0; return this; } // --------------------------- // Exporting to external formats - public CodeGen exportELF(String fname) throws IOException { + ElfWriter _elf; + public CodeGen exportELF( boolean inMemory, boolean emitEntrySymbol ) { assert _phase == Phase.Encoding; _phase = Phase.Export; - if( fname == null ) new LinkMem(this).link(); // In memory patching - else new ElfFile(this).export(fname); // External ELF file + long t0 = System.currentTimeMillis(); + if( _encoding!=null ) { + if( inMemory ) + new LinkMem(this).link(_encoding); // In memory patching + else + _elf = new ElfWriter(this).export(emitEntrySymbol); + } + _times[Phase.Export.ordinal()] = System.currentTimeMillis() - t0; return this; } + + // --------------------------- + + // 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); + for( String str : elf._strs ) + if( str.startsWith(Parser.CLZ) ) + _externSymbols.putIfAbsent(str,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; + } + // --------------------------- 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 _stop.ret().ctrl(); } - public Node expr() { return _stop.ret().expr(); } - public String print() { return _stop.print(); } + public Node ctrl() { return compunit()._stop.ret().ctrl(); } + public Node expr() { return compunit()._stop.ret().expr(); } + public String print() { return compunit()._stop.print(); } // Debugging helper @Override public String toString() { - return _phase.ordinal() > Phase.Schedule.ordinal() - ? IRPrinter._prettyPrint( this ) - : _stop.p(9999); + if( _phase!=null && _phase.ordinal() > Phase.Schedule.ordinal() ) + return IRPrinter.prettyPrint( this ); + if( _stop == null ) + return "No StopNode"; + return _stop.p(4999); } // Debugging helper - public Node f(int idx) { return _stop.find(idx); } + 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; + } String printCFG() { @@ -461,28 +1053,4 @@ 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/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java b/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java new file mode 100644 index 00000000..2681066e --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java @@ -0,0 +1,138 @@ +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 + //public Encoding _encoding; // Encoding for output + + public TypeStruct _clz; // The one TypeStruct being published + //BAOS _serial; // Serialized IR for this ELF file + Ary<CompUnit> _deps; // CompUnits that this CompUnit depends on + + //// List of symbols exported by this compilation unit, and their Node + //// definitions. + //public HashMap<String,Node> _exported; + // Per-Compilation-Unit Start/StopNodes, keeping alive all exported Nodes. + // Null means no code loaded (yet). + public StartCUNode _start; + 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/src/main/java/com/seaofnodes/simple/codegen/ElfFile.java b/src/main/java/com/seaofnodes/simple/codegen/ElfFile.java deleted file mode 100644 index 8f9e2981..00000000 --- a/src/main/java/com/seaofnodes/simple/codegen/ElfFile.java +++ /dev/null @@ -1,381 +0,0 @@ -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]; - - 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/src/main/java/com/seaofnodes/simple/codegen/ElfReader.java b/src/main/java/com/seaofnodes/simple/codegen/ElfReader.java new file mode 100644 index 00000000..534bb10f --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/codegen/ElfReader.java @@ -0,0 +1,234 @@ +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; + // 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/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java b/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java new file mode 100644 index 00000000..508cdcca --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java @@ -0,0 +1,387 @@ +package com.seaofnodes.simple.codegen; + +import com.seaofnodes.simple.node.*; +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 ) { + super(".rela.text", SHT_RELA, SHF_INFO_LINK, 24/*entsize*/); + _link = 2; + _info = info; + } + + void writeReloOffPlus(int offset, int symidx, byte relo) { + assert symidx > 0; + write8(offset); + write8(((long)symidx << 32L) | relo); + write8(-4); + } + } + + + // ------------------------------------------------------------------------ + // 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_GLOBAL ? _globals : _contents; + 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_GLOBAL ? gidx : -99; + } + + 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) { + 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 + symbol(fun._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; // Top-level declared a main, take the existing one + 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("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_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); } + } + + + // ------------------------------------------------------------------------ + + public ElfWriter export(boolean emitEntrySymbol) { + // 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*/+7/*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; + DataSection text = new DataSection(".text" , Section.SHT_PROGBITS, enc._bits , 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); + // 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); + + for( Node n : enc._externals.keySet()) { + String extern = enc._externals.get(n); + int symidx = symbols.symbol(extern, 0, SYM_BIND_GLOBAL, SYM_TYPE_NOTYPE); + int offset = enc.opStart(n) + enc.opLen(n) - 4; + relocations.writeReloOffPlus(offset, symidx, (byte)4/*PLT32*/); + } + + // Write relocations for the constant pool + for( Encoding.Relo relo : enc._bigCons.values() ) { + boolean ro = !(relo._t instanceof TypeStruct ts) || ts.isConstant(); + DataSection data = ro ? rodata : rwdata; + int symidx = symbols.symbol("CPOOL$"+symbols.gidx(), data._index, SYM_BIND_GLOBAL, SYM_TYPE_FUNC, relo._target, relo._t.size()); + relocations.writeReloOffPlus(relo._opStart+relo._off, symidx, relo._elf ); + } + + // 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/src/main/java/com/seaofnodes/simple/codegen/Encoding.java b/src/main/java/com/seaofnodes/simple/codegen/Encoding.java index e3684445..4c7475fd 100644 --- a/src/main/java/com/seaofnodes/simple/codegen/Encoding.java +++ b/src/main/java/com/seaofnodes/simple/codegen/Encoding.java @@ -1,12 +1,10 @@ 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.Ary; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; -import java.io.ByteArrayOutputStream; +import com.seaofnodes.simple.util.*; import java.util.*; @@ -24,11 +22,14 @@ public class Encoding { // 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 - // check relative offsets; no relocation info is required. + // chunk 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. @@ -36,17 +37,10 @@ public class Encoding { // - RIP-relative to external chunks have a zero offset; the matching // relocation info will be used to patch the correct value. - 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; + 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<>(); // Big Constant relocation info. public static class Relo { @@ -54,15 +48,16 @@ public class Encoding { public final Type _t; // Constant type 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; } } - public final HashMap<Node,Relo> _bigCons = new HashMap<>(); - Encoding( CodeGen code ) { _code = code; } + Encoding( CodeGen code ) { + _code = code; + } // Shortcut to the defining register public short reg(Node n) { @@ -103,14 +98,6 @@ public class Encoding { } // 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); @@ -123,48 +110,88 @@ public class Encoding { 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 ) { - _internals.put(call,_code.link(call.tfp())); + FunNode fun = _code.link(call.tfp()); + if( fun != null && !_code.owns(fun) ) + _externals.put(call,fun._name); + else + _internals.put(call,fun); return this; } - public Encoding relo( ConstantNode con ) { - TypeFunPtr tfp = (TypeFunPtr)con._con; - _internals.put(con,_code.link(tfp)); + // Code address as a constant + public Encoding relo( FunPtrNode fptr ) { + TypeFunPtr tfp = (TypeFunPtr)fptr._type; + FunNode fun = _code.link(tfp); + if( fun != null && !_code.owns(fun) ) + _externals.put(fptr,fun._name); + else + _internals.put(fptr,fun); return this; } + // Local jump public void jump( CFGNode jmp, CFGNode dst ) { _internals.put(jmp,dst.uctrlSkipEmpty()); } - - final HashMap<Node,String> _externals = new HashMap<>(); + // External references only located by Strings a link-time + public final HashMap<Node,String> _externals = new HashMap<>(); public Encoding external( Node n, String extern ) { _externals.put(n,extern); return this; } // Store t as a 32/64 bit constant in the code space; generate RIP-relative - // addressing to load it + // addressing to load it. Type is stored in either the .rodata or .data. public void largeConstant( Node relo, Type t, int off, int elf ) { - assert t.isConstant(); + // TODO: Any-old struct in the cpool + assert t.isConstant() || (t instanceof TypeStruct ts && Parser.startsClzPrefix(ts._name)); assert (byte)off == off; assert (byte)elf == elf; _bigCons.put(relo,new Relo(relo,t,(byte)off,(byte)elf)); } // -------------------------------------------------- - void encode() { + Encoding 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. @@ -174,8 +201,15 @@ public class Encoding { // encodings and compact the code, changing all the offsets. compactShortForm(); + // Write the constant pool + writeConstantPool(_cpool,true ); + // Write the static memory + writeConstantPool(_sdata,false); + // Patch RIP-relative and local encodings now. patchLocalRelocations(); + + return this; } // -------------------------------------------------- @@ -183,29 +217,48 @@ public class Encoding { // 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<>(); - Ary<CFGNode> rpo = new Ary<>(CFGNode.class); - rpos.put(_code._start.loop(),rpo); + _cfg = new Ary<>(CFGNode.class); + rpos.put(_code._start.loop(),_cfg); BitSet visit = _code.visit(); - 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; + + // 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; + } } - // 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); + } + // 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; + } + // Reverse in-place - for( int i=0; i< rpo.size()>>1; i++ ) - rpo.swap(i,rpo.size()-1-i); + for( int i=0; i< _cfg.size()>>1; i++ ) + _cfg.swap(i,_cfg.size()-1-i); visit.clear(); - _code._cfg = rpo; // Save the new ordering } @@ -279,7 +332,7 @@ public class Encoding { 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, the `fld < bld` must be false, or else both directions + // bld` is true, then `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 @@ -324,24 +377,26 @@ public class Encoding { // -------------------------------------------------- // Write encoding bits in order into a big byte array. // Record opcode start and length. - public FunNode _fun; // Currently encoding function + + // Current function is used by the spill-op encodings to query the stack + // frame layout and get the spill offsets. private void writeEncodings() { - _opStart= new int [_code.UID()]; - _opLen = new byte[_code.UID()]; - for( CFGNode bb : _code._cfg ) { + for( CFGNode bb : _cfg ) { if( !(bb instanceof MachNode mach0) ) - _opStart[bb._nid] = _bits.size(); - else if( bb instanceof FunNode fun ) { - _fun = fun; // Currently encoding function - _opStart[bb._nid] = _bits.size(); + opStart(bb, _bits.size()); + else if( bb instanceof FunNode ) { + opStart(bb, _bits.size()); mach0.encoding( this ); - _opLen[bb._nid] = (byte) (_bits.size() - _opStart[bb._nid]); + opLen(bb, (byte) (_bits.size() - opStart(bb))); } for( Node n : bb._outputs ) { - if( n instanceof MachNode mach && !(n instanceof FunNode) ) { - _opStart[n._nid] = _bits.size(); + 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()); mach.encoding( this ); - _opLen[n._nid] = (byte) (_bits.size() - _opStart[n._nid]); + opLen(n, (byte) (_bits.size() - opStart(n))); } } } @@ -351,10 +406,10 @@ public class Encoding { // Short-form RIP-relative support: replace short encodings with long // encodings and expand the code, changing all the offsets. private void compactShortForm() { - int len = _code._cfg._len; + int len = _cfg._len; int[] oldStarts = new int[len]; for( int i=0; i<len; i++ ) - oldStarts[i] = _opStart[_code._cfg.at(i)._nid]; + oldStarts[i] = opStart(_cfg.at(i)); // TODO: Rewrite this algo to use the small "_jmps" list of just the // jumps instead of walking all blocks. @@ -368,24 +423,27 @@ public class Encoding { while( slide != 0) { // While no fails slide = 0; for( int i=0; i<len; i++ ) { - CFGNode bb = _code._cfg.at(i); - _opStart[bb._nid] += slide; + CFGNode bb = _cfg.at(i); + opStartAdd(bb, slide); // Slide down all other (non-CFG) ops in the block for( Node n : bb._outputs ) - if( n instanceof MachNode && !(n instanceof CFGNode) ) - _opStart[n._nid] += slide; + 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( 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._nid] - _opStart[bb._nid]; + int delta = opStart(target) - opStart(bb); byte opLen = riprel.encSize(delta); // Recorded size is smaller than the current size? - if( _opLen[bb._nid] < opLen ) { + if( opLen(bb) < opLen ) { // Start sliding the code down; record slide amount and new size - slide += opLen - _opLen[bb._nid]; - _opLen[bb._nid] = opLen; + slide += opLen - opLen(bb); + opLen(bb, opLen); } } } @@ -396,21 +454,21 @@ public class Encoding { // short-jumps span function headers, the padding will not make any // short jumps fail. for( int i=0; i<len; i++ ) { - CFGNode bb = _code._cfg.at(i); + CFGNode bb = _cfg.at(i); // Functions pad to align 16 if( bb instanceof FunNode ) { - int newStart = _opStart[bb._nid]+slide; + int newStart = opStart(bb)+slide; slide += (newStart+15 & -16)-newStart; } - _opStart[bb._nid] += slide; + opStartAdd(bb, slide); for( Node n : bb._outputs ) - if( n instanceof MachNode && !(n instanceof CFGNode) ) - _opStart[n._nid] += slide; + if( n instanceof MachNode && !(n instanceof CFGNode) && + !(n instanceof FunPtrNode && bb instanceof ReturnNode) ) + opStartAdd(n, slide); } - _padFunHeads = true; // Copy/slide the bits to make space for all the longer branches - int grow = _opStart[_code._cfg.at(len-1)._nid] - oldStarts[len-1]; + int grow = opStart(_cfg.at(len-1)) - 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]; @@ -418,7 +476,7 @@ public class Encoding { int start = oldStarts[i]; if( start==0 && i>1 ) continue; int oldStart = oldStarts[i]; - int newStart = _opStart[_code._cfg.at(i)._nid]; + int newStart = opStart(_cfg.at(i)); System.arraycopy(_bits.buf(),oldStart,bits,newStart,end-start); end = start; } @@ -433,22 +491,28 @@ public class Encoding { void patchLocalRelocations() { // Walk the local code-address relocations for( Node src : _internals.keySet() ) { - int start = _opStart[src._nid]; + int start = opStart(src); Node dst = _internals.get(src); - int target = _opStart[dst._nid]; - ((RIPRelSize)src).patch(this, start, _opLen[src._nid], target - start); + // 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); } } // -------------------------------------------------- // Write the constant pool into the BAOS and optionally patch locally - void writeConstantPool( BAOS bits, boolean patch ) { + void writeConstantPool( BAOS bits, boolean ro ) { padN(16,bits); // radix sort the big constants by alignment Ary<Relo>[] raligns = new Ary[5]; for( Node op : _bigCons.keySet() ) { Relo relo = _bigCons.get(op); + // non-constant structs in the r/w data, everything else in r/o data + if( (relo._t instanceof TypeStruct ts && !ts.isConstant()) == ro ) + continue; int align = relo._t.alignment(); Ary<Relo> relos = raligns[align]==null ? (raligns[align]=new Ary<>(Relo.class)) : raligns[align]; relos.add(relo); @@ -459,7 +523,7 @@ public class Encoding { HashMap<Type,Integer> targets = new HashMap<>(); // By alignment - for( int align = 4; align >= 0; align-- ) { + for( int align = raligns.length-1; align >= 0; align-- ) { Ary<Relo> relos = raligns[align]; if( relos == null ) continue; for( Relo relo : relos ) { @@ -469,30 +533,37 @@ public class Encoding { 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) + case TypeTuple tt -> throw Utils.TODO("no tuples here, use structs instead"); + case TypeStruct ts -> addStruct(bits,ts); + // Simple primitive (e.g. larger int, float, function ptr) default -> addN(align,relo._t,bits); } } - // Record target address and opcode start + // Record target address and opcode start. + // Target is relative to the cpool/sdata 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); + relo._opStart= opStart(relo._op); } } + } - // 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); + // 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); } // Structs use internal field layout - private void cpool( BAOS bits, TypeStruct ts ) { + private void addStruct( BAOS bits, TypeStruct ts ) { // Field order by offset int[] layout = ts.layout(); int off=0; // offset in the struct @@ -507,7 +578,7 @@ public class Encoding { off += ((TypeConAry)f._t).len(); } else { int log = f._t.log_size(); - addN(log,f._t,bits); + addN(log,f._t.isConstant() ? f._t : f._t.makeZero(),bits); off += 1<<log; } } @@ -522,14 +593,14 @@ public class Encoding { void patchGlobalRelocations() { for( Node src : _externals.keySet() ) { - int start = _opStart[src._nid]; + int start = opStart(src); String dst = _externals.get(src); int target = switch( dst ) { case "calloc" -> SENTINEL_CALLOC; case "write" -> SENTINEL_WRITE ; default -> throw Utils.TODO(); }; - ((RIPRelSize)src).patch(this, start, _opLen[src._nid], target - start); + ((RIPRelSize)src).patch(this, start, opLen(src), target - start); } } } diff --git a/src/main/java/com/seaofnodes/simple/codegen/GlobalBits.java b/src/main/java/com/seaofnodes/simple/codegen/GlobalBits.java new file mode 100644 index 00000000..b0060024 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/codegen/GlobalBits.java @@ -0,0 +1,171 @@ +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); + } + + // 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/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java b/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java index d9c7a6ad..5d8ed03c 100644 --- a/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java +++ b/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java @@ -24,10 +24,10 @@ public abstract class GlobalCodeMotion { schedEarly(code); // Break up shared global constants by functions - breakUpGlobalConstants(code._start); + breakUpGlobalConstants(code._start ); code._visit.clear(); - schedLate (code); + schedLate(code); } // Post-Order of CFG @@ -45,45 +45,91 @@ public abstract class GlobalCodeMotion { // Break up shared global constants by functions private static void breakUpGlobalConstants( Node start ) { + // For all global constants for( int i=0; i< start.nOuts(); i++ ) { Node con = start.out(i); - if( con instanceof MachNode mach && mach.isClone() ) { - breakUpGlobalConstants(con); - // While constant has users in different functions - while( true ) { - // Find a function user, and another function - FunNode fun = null; - boolean done=true; - for( Node use : con.outs() ) { - FunNode fun2 = use instanceof ReturnNode ret ? ret.fun() : use.cfg0().fun(); - if( fun==null || fun==fun2 ) fun=fun2; - else { done=false; break; } - } - // Single function user, so this constant is not shared - if( done ) { - if( con.in(0)==start ) i--; - con.setDef(0,fun); - break; - } - // Move function users to a private constant - Node con2 = mach.copy(); // Private constant clone - con2._inputs.set(0,null); // Preserve edge invariants from clone - con2.setDef(0,fun); - // Move function users to this private constant - for( int j=0; j<con._outputs._len; j++ ) { - Node use = con.out(j); - FunNode fun2 = use.cfg0().fun(); - if( fun2==fun ) { - use.setDef(use._inputs.find(con),con2); - j--; - } - } + // Instruction selection can expand one shared TypeNode constant + // into a constant-valued machine expression. Split either form + // before scheduling, since separate functions have no CFG LCA. + if( !(con instanceof CFGNode) && + ((con._type.isHighOrConst() && con.isConst()) || + con instanceof MachNode mach && mach.isClone() && con._type.isConstant()) ) { + breakUpGlobalConstantSingle( con ); + i--; // Removed a global constant, re-run same index + } + } + } + + + private static void breakUpGlobalConstantSingle( Node con ) { + // While constant has users in different functions + while( true ) { + // Find a function user, and another function + FunNode fun = null; + boolean done=true; + for( Node use : con.outs() ) { + if( use == null ) continue; + FunNode fun2 = useFun(use); + if( fun2==null || !CodeGen.CODE.owns(fun2) ) continue; + if( fun==null || fun==fun2 ) fun=fun2; + else { done=false; break; } + } + // Single function user, so this constant is not shared + if( done ) { + con.setDef(0,fun); + return; + } + // Move function users to a private constant + Node con2 = con.copy(); // Private constant clone + // Node copies made through Node(Node) carry unregistered inputs, + // while some machine copies construct fresh registered edges. + // Detach correctly in either case before pinning to the function. + Node old = con2.in(0); + if( old != null && old._outputs.find(con2) != -1 ) + con2.setDef(0,null); + else + con2._inputs.set(0,null); + con2.setDef(0,fun); + // Move function users to this private constant + for( int j=0; j<con._outputs._len; j++ ) { + Node use = con.out(j); + if( use == null ) continue; + FunNode fun2 = useFun(use); + if( fun2==null || !CodeGen.CODE.owns(fun2) ) continue; + if( fun2==fun ) { + use.setDef(use._inputs.find(con),con2); + j--; } } } } + private static FunNode useFun(Node use) { + if( use instanceof ReturnNode ret ) + return ret.fun(); + if( use instanceof ParmNode parm ) + return parm.fun(); + CFGNode cfg = use.cfg0(); + if( cfg==null ) return null; + FunNode fun = cfg.fun(); + // Use itself is not in a function... which makes it a 2-part + // constant such as happens on some chips where large constants have + // to be built up in parts. PtrToInt followed by the array-body Add is + // another zero-code/global-value chain, so walk through any number of + // single-use pieces until reaching the function-owned use. + if( fun==null ) { + do { + assert use.nOuts()==1; + use = use.out(0); + cfg = use.cfg0(); + fun = cfg==null ? null : cfg.fun(); + } while( fun==null ); + } + return fun; + } + + // ------------------------------------------------------------------------ // Visit all nodes in CFG Reverse Post-Order, essentially defs before uses // (except at loops). Since defs are visited first - and hoisted as early @@ -252,6 +298,8 @@ 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 && n.cfg0().fun()==parm.fun() ) + return late[use._nid]; if( !(use instanceof PhiNode phi) ) return late[use._nid]; CFGNode found=null; @@ -286,6 +334,12 @@ public abstract class GlobalCodeMotion { lca = anti_dep( load, late[mem._nid], mem.cfg0(), lca, st ); } break; // Loads do not cause anti-deps on other loads + case EscapeNode esc: + if( load._alias == esc.fld()._alias ) { + assert late[mem._nid] != null; + lca = anti_dep( load, late[mem._nid], mem.cfg0(), lca, mem ); + } + break; case CallNode call: assert late[call._nid]!=null; lca = anti_dep(load,late[call._nid],call.cfg0(),lca,call); diff --git a/src/main/java/com/seaofnodes/simple/codegen/IFG.java b/src/main/java/com/seaofnodes/simple/codegen/IFG.java index 6dd6fbf1..1d88597c 100644 --- a/src/main/java/com/seaofnodes/simple/codegen/IFG.java +++ b/src/main/java/com/seaofnodes/simple/codegen/IFG.java @@ -68,9 +68,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 ); @@ -231,7 +231,9 @@ abstract public class IFG { selfConflict(alloc,n,lrg,TMP.get(lrg)); } private static void selfConflict(RegAlloc alloc, Node n, LRG lrg, Node prior) { - if( prior!=null && prior != n ) { + // Casts never produce a *new* value, just lift our understanding of the value. + // They do not force self-conflicts. + if( prior!=null && prior != n && !(n instanceof CheckCastNode) ) { lrg.selfConflict(prior); lrg.selfConflict(n); alloc.fail(lrg); // 2 unrelated values live at once same live range; self-conflict @@ -402,6 +404,7 @@ 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() ) @@ -409,6 +412,10 @@ 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(); } @@ -438,7 +445,7 @@ abstract public class IFG { private static int pickRiskyScore( LRG lrg ) { // Pick single-def clonables that are not right next to their single-use. // Failing to color these will clone them closer to their uses. - if( !lrg._multiDef && lrg._machDef.isClone() ) { + if( !lrg._multiDef && lrg._machDef.isClone() && lrg._machUse != null ) { Node def = ((Node)lrg._machDef); Node use = ((Node)lrg._machUse); CFGNode cfg = def.cfg0(); @@ -458,7 +465,7 @@ abstract public class IFG { // TODO: cost/benefit model. Perhaps counting loop-depth (freq) of def/use for cost // and "area" for benefit - return 1000; + return 1000 + (lrg._multiUse ? -100 : 0); } private static short biasColor( RegAlloc alloc, LRG lrg, short reg, RegMask mask ) { diff --git a/src/main/java/com/seaofnodes/simple/codegen/LRG.java b/src/main/java/com/seaofnodes/simple/codegen/LRG.java index 009ddcdb..3a4596fe 100644 --- a/src/main/java/com/seaofnodes/simple/codegen/LRG.java +++ b/src/main/java/com/seaofnodes/simple/codegen/LRG.java @@ -56,6 +56,10 @@ 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; } void addNeighbor(LRG lrg) { @@ -78,7 +82,7 @@ public class LRG { // More registers than neighbors boolean lowDegree() { return nadj() < _mask.size(); } - LRG( short lrg ) { _lrg = lrg; _reg = -1; } + LRG( short lrg, FunNode fun ) { _lrg = lrg; _reg = -1; _fun = fun; } boolean leader() { return _leader == null; } @@ -114,6 +118,7 @@ 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/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java b/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java index 2415203d..e0b59395 100644 --- a/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java +++ b/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java @@ -1,6 +1,8 @@ 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 @@ -8,15 +10,23 @@ public class LinkMem { final CodeGen _code; LinkMem( CodeGen code ) { _code = code; } - public CodeGen link() { - Encoding enc = _code._encoding; - + public CodeGen link(Encoding enc) { // Patch external calls internally enc.patchGlobalRelocations(); - // Write any large constants into a constant pool; they - // are accessed by RIP-relative addressing. - enc.writeConstantPool(enc._bits,true); + // 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); + boolean ro = !(relo._t instanceof TypeStruct ts) || ts.isConstant(); + int target = relo._target+(ro ? cpool : sdata); + ((RIPRelSize)relo._op).patch(enc, relo._opStart, enc.opLen(relo._op), target - relo._opStart); + } return _code; } diff --git a/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java b/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java index ddf5a679..b6b3a22c 100644 --- a/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java +++ b/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 CallEndNode) ? n.in(0) : n); } + static XSched get(Node n) { return n==null ? null : XS.get(n instanceof ProjNode && !(n.in(0) instanceof CFGNode) ? n.in(0) : n); } private XSched init(CFGNode bb, Node n) { _n = n; diff --git a/src/main/java/com/seaofnodes/simple/codegen/Machine.java b/src/main/java/com/seaofnodes/simple/codegen/Machine.java index 2766414f..91d07d57 100644 --- a/src/main/java/com/seaofnodes/simple/codegen/Machine.java +++ b/src/main/java/com/seaofnodes/simple/codegen/Machine.java @@ -6,28 +6,42 @@ import com.seaofnodes.simple.type.TypeFunPtr; 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 - public abstract SplitNode split( String kind, byte round, LRG lrg); + + // 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 ); + // 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 ); @@ -93,5 +107,9 @@ 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; } } diff --git a/src/main/java/com/seaofnodes/simple/codegen/Module.java b/src/main/java/com/seaofnodes/simple/codegen/Module.java new file mode 100644 index 00000000..a5d85dbc --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/codegen/Module.java @@ -0,0 +1,98 @@ +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/src/main/java/com/seaofnodes/simple/codegen/Opto.java b/src/main/java/com/seaofnodes/simple/codegen/Opto.java index f8ce1e54..b88067c4 100644 --- a/src/main/java/com/seaofnodes/simple/codegen/Opto.java +++ b/src/main/java/com/seaofnodes/simple/codegen/Opto.java @@ -1,10 +1,12 @@ 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 com.seaofnodes.simple.util.Utils; + import java.util.Arrays; +import java.util.BitSet; // Optimistic Algorithm abstract public class Opto { @@ -24,34 +26,10 @@ abstract public class Opto { // get a fairly precise set of Call Graph edges between CallNodes and // FunNodes. public static void opto(CodeGen code) { - - // Pessimistic peephole optimization on a worklist - this completes - // whatever peepholes can run after parsing. - code._iter.iterate(code); - // Reset Types before running the worklist algorithm Ary<Type> oldTypes = new Ary<>(Type.class); // Used for asserts only resetToTop(code,oldTypes); - - // 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); - + unlinkStart(code); // OPTIMISTIC INTERPROCEDURAL SCCP sccp(code, oldTypes); @@ -60,6 +38,11 @@ abstract public class Opto { // peepholes can make more progress. moveChangesToWorklist(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); + // Progress with improved types. E.g. since we have a full Call Graph // here, we might do better inlining. code._iter.iterate(code); @@ -73,15 +56,23 @@ abstract public class Opto { // Propagate field size constants code._iter.iterate(code); - // Throw away the Call Graph - // 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. - removeCallGraph(code); + // Retain surviving functions in their CompUnits for later code + // emission. Start inputs are semantic: they mean unknown callers. + for( FunNode fun : code._linker ) + if( fun != null && !fun.isDead() && !fun._type.isHigh() ) + keepInCompUnit(code,fun); + + for( CompUnit cu : code._compunits.values() ) { + ProjNode mem = cu._start.proj(1); + if( mem != null ) mem.unkeep(); + } // To help with testing, sort StopNode Returns by NID - Arrays.sort(code._stop._inputs._es,0,code._stop.nIns(),(x,y) -> x._nid - y._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) )); } // Reset Types before running the worklist algorithm @@ -90,9 +81,6 @@ 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; } ); } @@ -101,41 +89,49 @@ 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(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); - } + 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(); } - } - // Function is public (callable from Start directly). - // - Always true for main - // - Never true 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; - // 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; + // 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. + if( !(code.owns(fun) && 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--); + } + } } // 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 - for( long fidxs=tfp.fidxs(); fidxs!=0; fidxs=TypeFunPtr.nextFIDX(fidxs) ) { - int fidx = Long.numberOfTrailingZeros(fidxs); - FunNode fun = code.link(fidx); + int[] fidxs = tfp.fidxs(); + 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.lookupFun(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) ) { @@ -146,6 +142,55 @@ 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 || fun.in(1) != start ) { + assert !start.isDead(); + keepInCompUnit(code,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); + } + } + } + + // Keep a surviving function rooted in its compilation unit without + // claiming that it has unknown callers. + private static void keepInCompUnit(CodeGen code, FunNode fun) { + StopCUNode stop = fun._compunit._stop; + if( stop._inputs.find(fun.ret()) == -1 ) + code.add(stop).addDef(fun.ret()); + } + + // The core SCCP algorithm private static void sccp( CodeGen code, Ary<Type> oldTypes ) { // Iter workList is empty at this point @@ -157,108 +202,159 @@ abstract public class Opto { count++; Type oval = n._type, nval = n.compute(); if( oval == nval ) continue; - 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 + 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 n._type = nval; - // If a TFP adds a new function input to a call, link to the new Fun + // 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( 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 + // Link a Call which becomes alive. Like above, adds new Call + // Graph edges to the graph. 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 && n._type instanceof TypeTuple tt ) + linkStart(code,tt); + + // Since n._type changed, visit all output neighbors code._iter.addAll(n._outputs); n.moveDepsToWorklist(code._iter); // Quadratic (expensive) small-step assert - assert check(code); + assert worklistCheck(code); } } - // Quadratic (expensive) small-step assert - private static boolean check(CodeGen code) { - code._start.walk( x -> { + // 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 -> { Type xval = x.compute(); - assert xval == x._type || (x._type.isa( xval ) && code._iter._work.on( x )); + assert xval == x._type; 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._iter.add(x); + code.add(x); return null; }); } + // Resolve arithmetic modes which are ambiguous only because they belong to + // a recursive component. Float wins because integer operands can be + // converted to float. + private static void resolveNumericModes(CodeGen code) { + final BitSet visited = new BitSet(); + code._stop.walk(n -> { + if( n instanceof ModeNode modeNode && modeNode.mode()==0 ) { + visited.clear(); + visited.set(n._nid); // Inspect inputs, not Bool's integer result + int mode = 0; + for( int i=1; i<n.nIns(); i++ ) + mode |= numericEvidence(n.in(i),visited); + if( mode != 0 ) { + // If both evidence, FP wins + modeNode.setMode((byte)((mode&2)==2 ? 2 : 1)).init(); + code.add(n); + } + } + return null; + }); + } + + // Gather numeric evidence through Phi and interprocedural return cycles. + // The visited set makes self- and mutually-recursive calls finite. + private static int numericEvidence(Node n, BitSet visited) { + if( n==null || visited.get(n._nid) ) return 0; + visited.set(n._nid); + + Type t = n._type; + if( t instanceof TypeFloat ) return 2; + if( t instanceof TypeInteger ) return 1; + if( t!=Type.BOTTOM && !(t instanceof TypeTuple) ) + return 0; // Something unrelated, like a ptr + + int evidence = 0; + switch( n ) { + case ModeNode modeNode -> evidence = modeNode.mode(); + case PhiNode phi -> {} + case ReturnNode ret -> {} + case ProjNode proj when proj._idx==2 && proj.in(0) instanceof CallEndNode cend -> { + for( int i=1; i<cend.nIns(); i++ ) + evidence |= numericEvidence(cend.in(i),visited); + } + case ConvertNode convert -> { + Type dst = convert.dst(); + evidence = dst instanceof TypeFloat ? 2 : + dst instanceof TypeInteger ? 1 : 0; + } + default -> { return 0; } + } + for( int i=1; i<n.nIns(); i++ ) + evidence |= numericEvidence(n.in(i),visited); + + return evidence; + } + + // 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 ) { - TypeMemPtr tmp = (TypeMemPtr) Parser.TYPES.get(off._name); - off.subsume( off.asOffset(tmp._obj) ); - i--; - } - } - } - - // 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(); + Node con = off.asOffset(); + if( con != null ) { // Can be null for trying to reference missing field + off.subsume( con ); + i--; } - 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()); + } } - fun.ret().setDef(3,rpc.init()); - return rpc; } - - } diff --git a/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java b/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java new file mode 100644 index 00000000..191016be --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java @@ -0,0 +1,379 @@ +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 { + + // 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); + if( !NEEDS_LOAD.isEmpty() ) + code._stop.walk(n -> { + if( n instanceof CallNode ) + code.add(n); + return null; + }); + + // 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 ) { + TypeStruct clz = (TypeStruct)Parser.TYPES.get(fref._name); + if( clz == null ) + return null; + // Class pointer: + TypeMemPtr clzptr = TypeMemPtr.make((byte)2,clz,true); + // Class pointer constant: + ConstantNode clzCon = code.con(clzptr); + // Replace and optimize + fref.addDef(clzCon); + code.add(fref); + } + // Adds sort by NIDs, which just got shuffled + if( n instanceof AddNode add ) + code.add(add); + 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); + } 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? + try { + NEEDS_LOAD.add(cunit); + // Load dependent classes + ElfReader elf = ElfReader.load(cunit._obj,cunit); + elf.loadPublicSymbols(); + for( String fname : elf._deps ) { + // Check CompUnit for being up to date + CompUnit sub = makeCUnit(code,null,fname,null/*depObj(code,cunit,fname)*/); + // If not seen before or out of date, parse the new nested cunit + if( sub._clz == null && sub._par == null ) + 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); + cunit._clz = elf.loadSimple(code); + 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 ) // Also the instance type, if a constructor exists + Parser.TYPES.put(cunit._cname, ((TypeMem)((TypeFunPtr)inst._t)._ret)._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); + TypeStruct clz = typedElf == null ? null : typedElf.loadPublicTypes(code); + if( clz != null ) + Parser.TYPES.put(clz._name,clz); + else + 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/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java b/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java index 777182dc..48319006 100644 --- a/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java +++ b/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/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java b/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java index da521082..4c00391c 100644 --- a/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java +++ b/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java @@ -56,6 +56,8 @@ 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<>(); @@ -64,6 +66,7 @@ public class RegAlloc { _failed.put(lrg,""); } boolean success() { return _failed.isEmpty(); } + public boolean done() { return _done; } // ----------------------- @@ -72,16 +75,16 @@ public class RegAlloc { short _lrg_num; // Define a new LRG, and assign n - LRG newLRG( Node n ) { + LRG newLRG( Node n, FunNode fun ) { LRG lrg = lrg(n); if( lrg!=null ) return lrg; - lrg = new LRG(_lrg_num++); + lrg = new LRG(_lrg_num++, fun); LRG old = _lrgs.put(n,lrg); assert old==null; return lrg; } // LRG for n - LRG lrg( Node n ) { + public LRG lrg( Node n ) { LRG lrg = _lrgs.get(n); if( lrg==null ) return null; LRG lrg2 = lrg.find(); @@ -130,18 +133,19 @@ public class RegAlloc { } // Printable register number for node n - String reg( Node n ) { return reg(n,null); } - String reg( Node n, FunNode fun ) { + String reg( Node n ) { LRG lrg = lrg(n); - if( lrg==null ) return null; + return lrg==null ? null : reg(lrg); + } + String reg( LRG lrg ) { // 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() || fun==null ) + if( lrg._reg < regs.length || _code._phase.ordinal() <= CodeGen.Phase.RegAlloc.ordinal() || lrg._fun==null ) return RegMask.reg(regs,lrg._reg); // Stack-slot past RA uses the frame layout logic - return "[rsp+"+fun.computeStackOffset(_code,lrg._reg)+"]"; + return "[rsp+"+lrg._fun.computeStackOffset(_code,lrg._reg)+"]"; } // ----------------------- @@ -177,6 +181,7 @@ public class RegAlloc { round++; } postColor(); // Remove no-op spills + _done = true; } private boolean graphColor(byte round) { @@ -245,6 +250,8 @@ 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 @@ -256,7 +263,7 @@ public class RegAlloc { // st4 [V1],len - No good, must split around insertAfterAndReplace( makeSplit("def/empty1",round,lrg), (Node)lrg._machDef, false/*true*/); // Split just before use - if( lrg._1regUseCnt==1 || (lrg._1regDefCnt==1 && ((Node)lrg._machDef).nOuts()==1) ) + if( lrg._machUse!=null && (lrg._1regUseCnt==1 || (lrg._1regDefCnt==1 && defNouts==1)) ) insertBefore((Node)lrg._machUse,lrg._uidx,"use/empty1",round,lrg); return true; } @@ -483,8 +490,8 @@ public class RegAlloc { void findAllLRG( LRG lrg ) { _ns.clear(); int wd = 0; - _ns.push((Node)lrg._machDef); - _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; @@ -543,12 +550,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(kind,round,lrg); + : _code._mach.split(lrg,kind,round); _lrgs.put(split,lrg); return split; } private SplitNode makeSplit( String kind, byte round, LRG lrg ) { - SplitNode split = _code._mach.split(kind,round,lrg); + SplitNode split = _code._mach.split(lrg,kind,round); _lrgs.put(split,lrg); return split; } diff --git a/src/main/java/com/seaofnodes/simple/codegen/RegMask.java b/src/main/java/com/seaofnodes/simple/codegen/RegMask.java index c7dda1da..81e47e8e 100644 --- a/src/main/java/com/seaofnodes/simple/codegen/RegMask.java +++ b/src/main/java/com/seaofnodes/simple/codegen/RegMask.java @@ -33,7 +33,6 @@ 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/src/main/java/com/seaofnodes/simple/codegen/Serialize.java b/src/main/java/com/seaofnodes/simple/codegen/Serialize.java new file mode 100644 index 00000000..8014bb2c --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/codegen/Serialize.java @@ -0,0 +1,617 @@ +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 { + + 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()]); + + // 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, 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, 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, GlobalBits aliases, GlobalBits fidxs, GlobalBits rpcs, HashMap<Integer,String> externFunc) { + // Initialize the mapping from bits/tags to types + Type.TAGOFFS(); + + BAOS baos = new BAOS(); + // A - Print a header + 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); + } + // 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); + // 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); + + // 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); + + // Radix sort by Node ID#, new compressed NIDs + IdentityHashMap<Node,Integer> anodes = new IdentityHashMap<>(); + for( Node n : nodes ) + anodes.put(n,anodes.size()+1); // +1 bias, reserve 0 for null + + // 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._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++ ) { + Node def = n.in(j); + Integer idx = def==null ? Integer.valueOf(0) : anodes.get(def); + 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; + } + 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("Missing magic word"); + + // 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]; + } + + // -------------------------------------------------- + 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( "Missing magic word" ); + + // 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]; + } + + // 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 = n instanceof MemMergeNode && i >= GlobalBits.RESERVED && def != null + ? aliases.map(fileAliases,i) + : 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; + 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 ) { + assert code._start._ltree != null; // Uses loop tree + 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() ) { + // Special case for Cast:BOT which is basically a double-indirect constant + if( use instanceof CheckCastNode cast && cast.in(0)==null ) + for( Node useuse : cast.outs() ) + conUsed(cast,useuse,visit,cons); + // 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 ) { + assert code._start._ltree != null; // Uses loop tree + 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._start != null && !cu._start.isDead() ) { + nodes.add(cu._start); + // Memory proj + nodes.add(cu._start.proj(1)); + // 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); + 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 nodes walk outer loops before inner, so they hit the + // Return first, so Return is at the bottom of the RPO. + if( n instanceof IfNode iff ) { + CProjNode c0 = iff.cproj(0); + CProjNode c1 = iff.cproj(1); + if( c0._ltree.depth() > c1._ltree.depth() ) + { c0 = c1; c1 = iff.cproj(0); } + _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/src/main/java/com/seaofnodes/simple/node/AddFNode.java b/src/main/java/com/seaofnodes/simple/node/AddFNode.java deleted file mode 100644 index 4ad02dbb..00000000 --- a/src/main/java/com/seaofnodes/simple/node/AddFNode.java +++ /dev/null @@ -1,31 +0,0 @@ -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); } - - @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/src/main/java/com/seaofnodes/simple/node/AddNode.java b/src/main/java/com/seaofnodes/simple/node/AddNode.java index c7fd4daa..46607c23 100644 --- a/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -1,16 +1,19 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.*; -import static com.seaofnodes.simple.Parser.con; - 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 "+"; } - @Override long doOp( long x, long y ) { return x + y; } + @Override long doOp( long x, long y ) { return x + y; } + @Override double doOp(double x, double y ) { return x + y; } @Override TypeInteger doOp(TypeInteger x, TypeInteger y) { // Fold ranges like {0-1} + {2-3} into {2-4}. if( !overflow(x._min,y._min) && @@ -21,6 +24,11 @@ public class AddNode extends ArithNode { @Override public Node idealize () { + // Settle the one-shot arithmetic mode before applying family-specific + // algebraic identities. + if( _mode==0 ) + return super.idealize(); + Node lhs = in(1); Node rhs = in(2); if( rhs instanceof AddNode add && add.err()!=null ) @@ -33,8 +41,8 @@ public class AddNode extends ArithNode { return lhs; // Add of same to a multiply by 2 - if( lhs==rhs ) - return new ShlNode(null,lhs,con(1)); + if( lhs==rhs && _mode==1 ) + return new ShlNode(null,lhs,CodeGen.CODE.con(1)); // Goal: a left-spine set of adds, with constants on the rhs (which then fold). @@ -44,7 +52,7 @@ public class AddNode extends ArithNode { // x+(-y) becomes x-y if( rhs instanceof MinusNode minus ) - return new SubNode(lhs,minus.in(1)); + return new SubNode(lhs,minus.in(1),_mode); // Now we might see (add add non) or (add non non) or (add add add) but never (add non add) @@ -52,18 +60,21 @@ public class AddNode extends ArithNode { // Swap to (x + y) + z // Rotate (add add add) to remove the add on RHS if( rhs instanceof AddNode add ) - return new AddNode(new AddNode(lhs,add.in(1)).peephole(), add.in(2)); + return copy(copy(lhs,add.in(1)).peephole(), add.in(2)); // Now we might see (add add non) or (add non non) but never (add non add) nor (add add add) - if( !(lhs instanceof AddNode) ) + if( !(lhs instanceof AddNode) ) { // Rotate; look for (add (phi cons) con/(phi cons)) - return spine_cmp(lhs,rhs,this) ? swap12() : phiCon(this,true); + if( spine_cmp(lhs,rhs,this) ) return swap12(); + Node n = phiCon(this,true); + return n == null ? super.idealize() : n; + } // Now we only see (add add non) // Dead data cycle; comes about from dead infinite loops. Do nothing, // the loop will peep as dead after a bit. - if( lhs.in(1) == lhs ) + if( lhs.nIns()>=2 && lhs.in(1) == lhs ) return null; // Do we have (x + con1) + con2? @@ -73,7 +84,7 @@ public class AddNode extends ArithNode { // because if it later became a constant then we could make this // transformation. if( addDep(lhs.in(2))._type.isConstant() && rhs._type.isConstant() ) - return new AddNode(lhs.in(1),new AddNode(lhs.in(2),rhs).peephole()); + return copy(lhs.in(1),copy(lhs.in(2),rhs).peephole()); // Do we have ((x + (phi cons)) + con) ? @@ -87,13 +98,13 @@ public class AddNode extends ArithNode { // Do we rotate (x + y) + z // into (x + z) + y ? if( spine_cmp(lhs.in(2),rhs,this) ) - return new AddNode(new AddNode(lhs.in(1),rhs).peephole(),lhs.in(2)); + return copy(copy(lhs.in(1),rhs).peephole(),lhs.in(2)); return super.idealize(); } // Rotation is only valid for associative ops, e.g. Add, Mul, And, Or, Xor. - // Do we have ((phi cons)|(x + (phi cons)) + con|(phi cons)) ? + // Do we have ((phi cons)|(x + (phi cons)) + con|(phi cons)|(op y con)) ? // Push constant up through the phi: x + (phi con0+con0 con1+con1...) static Node phiCon(Node op, boolean rotate) { Node lhs = op.in(1); @@ -126,7 +137,7 @@ public class AddNode extends ArithNode { for( int i=1; i<ns.length; i++ ) ns[i] = op.copy(lphi.in(i), rhs instanceof PhiNode ? rhs.in(i) : rhs).peephole(); String label = lphi._label + (rhs instanceof PhiNode rphi ? rphi._label : ""); - Node phi = new PhiNode(label,lphi._minType.meet(op._type),ns).peephole(); + Node phi = new PhiNode(label,ns).peephole(); // Rotate needs another op, otherwise just the phi return lhs==lphi ? phi : op.copy(lhs.in(1),phi); } @@ -164,6 +175,5 @@ public class AddNode extends ArithNode { return lo._nid > hi._nid; } - @Override Node copy(Node lhs, Node rhs) { return new AddNode(lhs,rhs); } - @Override Node copyF() { return new AddFNode(null,null); } + @Override Node copy(Node lhs, Node rhs) { return new AddNode(lhs,rhs,_mode); } } diff --git a/src/main/java/com/seaofnodes/simple/node/AndNode.java b/src/main/java/com/seaofnodes/simple/node/AndNode.java index 70b57d6d..083a9a8c 100644 --- a/src/main/java/com/seaofnodes/simple/node/AndNode.java +++ b/src/main/java/com/seaofnodes/simple/node/AndNode.java @@ -5,9 +5,9 @@ import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; public class AndNode extends ArithNode { - public AndNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); } + 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 label() { return "And"; } @Override public String op() { return "&"; } @Override public String glabel() { return "&"; } diff --git a/src/main/java/com/seaofnodes/simple/node/ArithFNode.java b/src/main/java/com/seaofnodes/simple/node/ArithFNode.java deleted file mode 100644 index 7fa428c6..00000000 --- a/src/main/java/com/seaofnodes/simple/node/ArithFNode.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; -import java.util.BitSet; - -public abstract class ArithFNode extends Node { - public ArithFNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - - @Override - public StringBuilder _print1(StringBuilder sb, BitSet visited) { - in(1)._print0(sb.append("("), visited); - in(2)._print0(sb.append(glabel()), visited); - return sb.append(")"); - } - - 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 TypeFloat.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/src/main/java/com/seaofnodes/simple/node/ArithNode.java b/src/main/java/com/seaofnodes/simple/node/ArithNode.java index f2beaacb..8c21aef7 100644 --- a/src/main/java/com/seaofnodes/simple/node/ArithNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ArithNode.java @@ -1,16 +1,26 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; +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.BitSet; +import java.util.HashMap; +import java.util.IdentityHashMap; -public abstract class ArithNode extends Node { +public abstract class ArithNode extends Node implements ModeNode { // Source location for late reported errors Parser.Lexer _loc; + // Mode is 0 for unknown, 1 for int, 2 for flt + byte _mode; 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); + } @Override public String glabel() { return op(); } abstract String op(); @@ -23,20 +33,93 @@ public abstract class ArithNode extends Node { abstract long doOp(long x, long y); abstract TypeInteger doOp(TypeInteger x, TypeInteger y); + double doOp(double x, double y) { throw Utils.TODO(); } + + @Override public byte mode() { return _mode; } + @Override public Node setMode(byte mode) { + assert _mode==0 && (mode==1 || mode==2); + unlock(); // _mode participates in GVN hash and equality + _mode = mode; + return this; + } + static byte mode( Type t1, Type t2 ) { + if( t1 instanceof TypeFloat || t2 instanceof TypeFloat ) return 2; + if( t1 instanceof TypeInteger && t2 instanceof TypeInteger ) return 1; + if( t1 instanceof TypeMemPtr && t2 instanceof TypeMemPtr ) return 1; + if( t1 instanceof TypeFunPtr && t2 instanceof TypeFunPtr ) return 1; + return 0; + } + + // Check that a settled mode agrees with all data inputs. Takes the Node + // instead of a varargs list both to avoid allocation and to keep this ready + // to become shared mode-aware Node behavior later. + static Parser.ParseException nodeErr(Node n, Parser.Lexer loc, String op, byte mode, boolean allowFloat) { + Type bad = null; + for( int i=1; i<n.nIns(); i++ ) { + Type t = n.in(i)._type; + if( t.isHigh() ) + return null; + // A weak input is not evidence of a bad mode while the graph is + // still being built. Loop Phis commonly await their backedge. + if( CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal() && t == Type.BOTTOM ) + return null; + bad = switch( t ) { + case TypeInteger ti -> mode==1 ? null : t; + case TypeMemPtr ti -> mode==1 ? null : t; + case TypeFunPtr ti -> mode==1 ? null : t; + case TypeFloat tf -> mode==2 ? null : t; + default -> t; + }; + if( bad != null ) break; + } + boolean unsupportedFloat = false; + if( !allowFloat ) + for( int i=1; i<n.nIns(); i++ ) + if( n.in(i)._type instanceof TypeFloat ) { + bad = n.in(i)._type; + unsupportedFloat = true; + break; + } + if( bad == null ) + 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); + } + + // Arithmetic subclasses which have a floating-point interpretation override. + boolean allowFloat() { return false; } // Generic airthmetic op math: high returns high; low returns low; 2 // constants fold; only 2 non-constants call specialized math. @Override - public final TypeInteger compute() { + public final Type compute() { Type t1 = in(1)._type, t2 = in(2)._type; if( t1.isHigh() || t2.isHigh() ) - return TypeInteger.TOP; - if( t1 instanceof TypeInteger x && - t2 instanceof TypeInteger y ) - return x.isConstant() && y.isConstant() - ? con(x,y) - : doOp(x,y).makeWide( (byte)Math.max(x._widen,y._widen) ); - return TypeInteger.BOT; + return _mode==0 ? Type.TOP : + _mode==1 ? TypeInteger.TOP : + TypeFloat.F64.dual(); + byte mode = _mode==0 ? mode(t1,t2) : _mode; + if( mode==0 ) + return Type.BOTTOM; + if( mode==1 ) { + if( t1 instanceof TypeInteger x && + t2 instanceof TypeInteger y ) + return x.isConstant() && y.isConstant() + ? con(x,y) + : doOp(x,y).makeWide( (byte)Math.max(x._widen,y._widen) ); + return TypeInteger.BOT; + } + // FP handling + 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); + } + return TypeFloat.F64; } private TypeInteger con( Type t1, Type t2 ) { @@ -49,36 +132,50 @@ public abstract class ArithNode extends Node { // push-down peephole cycle because the constants all fold first. // Returns "Phi(op(con,con),op(x,y))". - // This check is slightly different from the - // Expected to be called after other ideal checks are done. @Override public Node idealize() { + Type t1 = in(1)._type, t2 = in(2)._type; + + // Can we decide int vs flt? + if( _mode==0 ) { + byte mode = mode(t1,t2); + if( mode!=0 ) return setMode(mode).init(); + } + if( _mode==2 ) { + if( t1 instanceof TypeInteger ) return copy(new ToFloatNode(in(1)).peephole(),in(2)); + if( t2 instanceof TypeInteger ) return copy(in(1),new ToFloatNode(in(2)).peephole()); + } + if( in(1) instanceof PhiNode lhs && in(2) instanceof PhiNode rhs && lhs.nIns() >= 2 && !lhs.inProgress() && - lhs.region()==rhs.region() && !(lhs.region() instanceof FunNode) && - lhs.nIns()>2 && // A 1-input Phi will collapse already // Disallow with self-looping phi; these will collapse - (lhs.in(2)!=lhs && rhs.in(2)!=rhs) ) { - // Profit check: only 1 instance of `this` will remain, all the - // others will fold to constants. - int cnt=0; - for( int i=1; i<lhs.nIns(); i++ ) - if( isCon(lhs,rhs,i) ) - cnt++; - if( lhs.nIns()-1 - cnt <= 1 ) { - // Profit! Push up-phi and fold - Node[] ns = new Node[lhs.nIns()]; - ns[0] = lhs.in(0); - for( int i=1; i<lhs.nIns(); i++ ) { - ns[i] = isCon(lhs,rhs,i) - ? new ConstantNode(con(lhs.in(i)._type, rhs.in(i)._type)).peephole() - : copy(lhs.in(i), rhs.in(i)).peephole(); + (lhs.nIns() > 2 && lhs.in(2)!=lhs && rhs.nIns() > 2 && rhs.in(2)!=rhs) ) { + Node r = lhs.region(); + if( r==rhs.region() && !(r instanceof FunNode) && + // For loops, the backedge *must* fold away, lest we unroll forever + (!(r instanceof LoopNode) || isCon(lhs,rhs,2)) ) { + // Profit check: only 1 instance of `this` will remain, all the + // others will fold to constants. For loops, *all* must fold away + int cnt=0; + for( int i=1; i<lhs.nIns(); i++ ) + if( isCon(lhs,rhs,i) ) + cnt++; + if( lhs.nIns()-1 - cnt <= 1 ) { + // Profit! Push up-phi and fold + Node[] ns = new Node[lhs.nIns()]; + ns[0] = lhs.in(0); + for( int i=1; i<lhs.nIns(); i++ ) { + ns[i] = isCon(lhs,rhs,i) + ? ConstantNode.make(con(lhs.in(i)._type, rhs.in(i)._type)).peephole() + : copy(lhs.in(i), rhs.in(i)).peephole(); + } + String label = lhs._label==rhs._label ? lhs._label : lhs._label + rhs._label; + return new PhiNode(label, ns).peephole(); } - String label = lhs._label==rhs._label ? lhs._label : lhs._label + rhs._label; - return new PhiNode(label,lhs._minType,ns).peephole(); } } + return null; } @@ -88,14 +185,14 @@ public abstract class ArithNode extends Node { static boolean overflow( long x, long y ) { - if( (x ^ y ) < 0 ) return false; // unequal signs, never overflow + if( (x ^ y ) < 0 ) return false; // unequal signs, never overflow return (x ^ (x + y)) < 0; // sum has unequal signs, so overflow } @Override public Parser.ParseException err() { - if( in(1)._type.isHigh() || in(2)._type.isHigh() ) return null; - if( !(in(1)._type instanceof TypeInteger) ) return Parser.error("Cannot '"+op()+"' " + in(1)._type,_loc); - if( !(in(2)._type instanceof TypeInteger) ) return Parser.error("Cannot '"+op()+"' " + in(2)._type,_loc); - return null; + return nodeErr(this,_loc,op(),_mode,allowFloat()); } + + @Override public boolean eq( Node n ) { return _mode==((ArithNode)n)._mode; } + @Override public int hash() { return _mode; } } diff --git a/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/src/main/java/com/seaofnodes/simple/node/BoolNode.java index 2765c308..c8722f67 100644 --- a/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ b/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -1,23 +1,29 @@ 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.BitSet; +import java.util.HashMap; +import java.util.IdentityHashMap; import static com.seaofnodes.simple.type.TypeInteger.*; -abstract public class BoolNode extends Node { +abstract public class BoolNode extends Node implements ModeNode { - public BoolNode(Node lhs, Node rhs) { - super(null, lhs, rhs); + // Mode is 0 for unknown, 1 for int, 2 for flt + byte _mode; + + 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 @Override - public String label() { return getClass().getSimpleName(); } - - @Override public String glabel() { return op(); } @Override @@ -37,9 +43,17 @@ abstract public class BoolNode extends Node { if( in(1)==in(2) ) // LT fails, both EQ and LE succeed return this instanceof LT ? FALSE : TRUE; - if( t1 instanceof TypeInteger i1 && - t2 instanceof TypeInteger i2 ) - return doOp(i1,i2); + byte mode = _mode==0 ? ArithNode.mode(t1,t2) : _mode; + if( mode==0 ) + return BOOL; + // Handle integer compares + if( mode==1 ) { + if( t1 instanceof TypeInteger i1 && + t2 instanceof TypeInteger i2 ) + return doOp(i1,i2); + return BOOL; + } + // Floating compare. Mixed inputs are converted in idealize(). if( t1 instanceof TypeFloat f1 && t2 instanceof TypeFloat f2 && f1.isConstant() && f2.isConstant() ) @@ -49,22 +63,39 @@ abstract public class BoolNode extends Node { TypeInteger doOp(TypeInteger t1, TypeInteger t2) { throw Utils.TODO(); } boolean doOp(double lhs, double rhs) { throw Utils.TODO(); } - Node copyF(Node lhs, Node rhs) { return null; } - public boolean isFloat() { return false; } + public boolean isFloat() { assert _mode!=0; return _mode==2; } + @Override public byte mode() { return _mode; } + @Override public Node setMode(byte mode) { + assert _mode==0 && (mode==1 || mode==2); + unlock(); // _mode participates in GVN hash and equality + _mode = mode; + return this; + } @Override public Node idealize() { // Compare of same if( in(1)==in(2) ) - return this instanceof LT ? Parser.ZERO : new ConstantNode(TRUE); + return this instanceof LT ? CodeGen.CODE.ZERO : ConstantNode.make(TRUE); + // Can we decide int vs flt? + Type t1 = in(1)._type, t2 = in(2)._type; + if( _mode==0 ) { + byte mode = ArithNode.mode(t1,t2); + if( mode!=0 ) return setMode(mode).init(); + } + if( _mode==2 ) { + if( t1 instanceof TypeInteger ) return copy(new ToFloatNode(in(1)).peephole(),in(2)); + if( t2 instanceof TypeInteger ) return copy(in(1),new ToFloatNode(in(2)).peephole()); + } + // Equals pushes constant to the right; 5==X becomes X==5. if( this instanceof EQ ) { if( !(in(2) instanceof ConstantNode) ) { // con==noncon becomes noncon==con if( in(1) instanceof ConstantNode || in(1)._nid > in(2)._nid ) - // Equals sorts by NID otherwise: non.high == non.low becomes non.low == non.high - return in(1)._type instanceof TypeFloat ? new EQF(in(2),in(1)) : new EQ(in(2),in(1)); + // Equals sorts by NID otherwise: non.high == non.low becomes non.low == non.high + return new EQ(in(2),in(1)); } // Equals X==0 becomes a !X if( (in(2)._type == ZERO || in(2)._type == Type.NIL) ) @@ -80,32 +111,44 @@ abstract public class BoolNode extends Node { return null; } + @Override public boolean eq( Node n ) { return _mode==((BoolNode)n)._mode; } + @Override public int hash() { return _mode; } + @Override public Parser.ParseException err() { + return ArithNode.nodeErr(this,null,op(),_mode,!(this instanceof ULT)); + } + 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 "=="; } TypeInteger doOp(TypeInteger i1, TypeInteger i2) { if( i1==i2 && i1.isConstant() ) return TRUE; if( i1._max < i2._min || i1._min > i2._max ) return FALSE; return BOOL; } - Node copy(Node lhs, Node rhs) { return new EQ(lhs,rhs); } - Node copyF() { return new EQF(null,null); } + @Override boolean doOp(double lhs, double rhs) { return lhs==rhs; } + Node copy(Node lhs, Node rhs) { return new EQ(lhs,rhs,_mode); } } 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 "!="; } TypeInteger doOp(TypeInteger i1, TypeInteger i2) { if( i1==i2 && i1.isConstant() ) return TRUE; if( i1._max < i2._min || i1._min > i2._max ) return FALSE; return BOOL; } - Node copy(Node lhs, Node rhs) { return new NE(lhs,rhs); } - Node copyF() { throw Utils.TODO(); } + @Override boolean doOp(double lhs, double rhs) { return lhs!=rhs; } + Node copy(Node lhs, Node rhs) { return new NE(lhs,rhs,_mode); } } 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 glabel() { return "<"; } TypeInteger doOp(TypeInteger i1, TypeInteger i2) { @@ -113,12 +156,14 @@ abstract public class BoolNode extends Node { if( i1._min >= i2._max ) return FALSE; return BOOL; } - Node copy(Node lhs, Node rhs) { return new LT(lhs,rhs); } - Node copyF() { return new LTF(null,null); } + @Override boolean doOp(double lhs, double rhs) { return lhs<rhs; } + Node copy(Node lhs, Node rhs) { return new LT(lhs,rhs,_mode); } } 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 glabel() { return "<="; } TypeInteger doOp(TypeInteger i1, TypeInteger i2) { @@ -126,32 +171,18 @@ abstract public class BoolNode extends Node { if( i1._min > i2._max ) return FALSE; return BOOL; } - Node copy(Node lhs, Node rhs) { return new LE(lhs,rhs); } - Node copyF() { return new LEF(null,null); } - } - - public static class EQF extends EQ { - public EQF(Node lhs, Node rhs) { super(lhs,rhs); } - @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); } - @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); } - @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); } + @Override boolean doOp(double lhs, double rhs) { return lhs<=rhs; } + Node copy(Node lhs, Node rhs) { return new LE(lhs,rhs,_mode); } } // Unsigned less that, for range checks. Not directly user writable. public static class ULT extends BoolNode { - public ULT(Node lhs, Node rhs) { super(lhs,rhs); } + public ULT(Node lhs, Node rhs) { super(lhs,rhs,(byte)1); } + public ULT(Node lhs, Node rhs, byte mode) { + super(lhs,rhs,(byte)1); + assert mode==1; + } + @Override public Tag serialTag() { return Tag.ULT; } public String op() { return "u<" ; } public String glabel() { return "u<"; } TypeInteger doOp(TypeInteger i1, TypeInteger i2) { diff --git a/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java new file mode 100644 index 00000000..cab85e44 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java @@ -0,0 +1,333 @@ +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}; + * those aliases must be represented by parallel precise MemPhis at the same + * program slice. + */ +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; + + public BulkMemPhiNode(String label, Node... inputs) { + this(label,EMPTY,inputs); + } + + private BulkMemPhiNode(String label, BitSet aliases, Node... 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); } + + /** + * Record that an alias has been split out. The graph rewrite installing + * its parallel precise Phi must happen before this semantic change. + */ + public void splitAlias(int alias) { + assert alias > 1; + if( _aliases.get(alias) ) return; + unlock(); + if( _aliases==EMPTY ) + _aliases = new BitSet(); + _aliases.set(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); + } + + 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 Type compute() { + Type t = super.compute(); + return t instanceof TypeMem mem ? mem : + t==Type.BOTTOM ? TypeMem.BOT : + TypeMem.TOP; + } + + @Override + public Node idealize() { + Node progress = super.idealize(); + if( progress != null ) return progress; + + if( !(region() instanceof RegionNode r ) ) + return in(1); // Input has collapse to e.g. starting control. + if( r.inProgress() || r.nIns()<=1 ) + return null; // Input is in-progress + if( nOuts()==0 ) return null; + if( nIns() <= 2 ) return null; + + // "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)); + return this; + } + + // If any input or output uses or defines a specific alias we cover, + // make a private MemPhi for it, and add it to the excluded list. + for( int i=1; i<nIns(); i++ ) { + int alias = inputAlias(in(i)); + if( alias!=0 ) + return slice(alias); + } + + for( int i=0; i<nOuts(); i++ ) { + int alias = outputAlias(out(i)); + if( alias!=0 ) + return slice(alias); + } + + return null; + } + + static public boolean checkMem(Node n, int alias) { + for( Node m : n._inputs ) + // Bulk memory supplies this alias + if( m instanceof BulkMemPhiNode bulk ) { + if( bulk._aliases.get(alias) ) + return false; + } else if( m instanceof MemMergeNode mmm && + mmm.in(1) instanceof BulkMemPhiNode bulk && + bulk._aliases.get(alias) && + (alias >= mmm.nIns() || mmm.in(alias)==null) ) + return false; + return true; + } + + + // True if the MemMerge has no precise slice still covered by this Phi. + private boolean canPeek(MemMergeNode mmm) { + return missingAlias(mmm,false)==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 ScopeNode scope -> 0; + case ReturnNode ret -> 0; + case ProjNode proj -> 0; + case ConstantNode con -> { + assert !(con._con instanceof TypeMem tmem && tmem._alias != 1); + yield 0; + } + case MemMergeNode mmm -> missingAlias(mmm,false); + case MemOpNode mem -> unsplit(mem._alias); + case BulkMemPhiNode bulk -> missingAlias(bulk); + default -> throw Utils.TODO(); + }; + } + + // Return an alias required by a user but still covered by this Phi, or 0. + private int outputAlias(Node use) { + // This decision depends backwards on an immediate user's alias/set. + // 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); + return switch(use) { + case ScopeNode scope -> 0; + case ParmNode parm -> 0; + case MemMergeNode mmm -> mmm.in(1)==this ? missingAlias(mmm,true) : 0; + 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 + : "Unexpected bulk-memory user "+use.getClass().getSimpleName()+"#"+use._nid+": "+use; + yield 0; + } + }; + } + + // Alias zero/one is bulk, and an already excluded precise alias cannot + // trigger another split of this Phi. + private int unsplit(int alias) { + return alias <= 1 || _aliases.get(alias) ? 0 : alias; + } + + // First alias excluded by the neighboring bulk Phi but not by this one. + private int missingAlias(BulkMemPhiNode bulk) { + for( int alias = bulk._aliases.nextSetBit(0); alias >= 0; alias = bulk._aliases.nextSetBit(alias+1) ) + if( !_aliases.get(alias) ) + return alias; + return 0; + } + + // First precise MemMerge slice still covered by this Phi. When inspecting + // a user, ignore slots which point back to this Phi. + private int missingAlias(MemMergeNode mmm, boolean user) { + for( int alias=2; alias<mmm.nIns(); alias++ ) + if( mmm.in(alias)!=null && + mmm.alias(alias)!=mmm.in(1) && + (!user || mmm.in(alias)!=this) && + !_aliases.get(alias) ) + return alias; + return 0; + } + + // Slice out given alias + private MemMergeNode slice( int alias ) { + assert !_aliases.get(alias); + assert !hasDup(alias); + // Inputs are the same; MemPhi will sharpen his own inputs + MemPhiNode mphi = new MemPhiNode("$"+alias,alias); + for( int i=0; i<nIns(); i++ ) mphi.addDef(in(i)); + Node mem = mphi.peephole(); + assert ((TypeMem)mem._type)._alias==alias; + // The precise Phi is a new alias-sensitive user of every memory + // input. Revisit those defs: a predecessor BulkMemPhi may now need + // to split this alias even though none of its inputs or types changed. + for( int i=1; i<mphi.nIns(); i++ ) + CodeGen.CODE.add(mphi.in(i)); + + BitSet aliases = ((BitSet)_aliases.clone()); + aliases.set(alias); + BulkMemPhiNode bphi = new BulkMemPhiNode(_label,aliases); + for( int i=0; i<nIns(); i++ ) + bphi.addDef(in(i)); + // Installing bphi creates a new bulk user with a different exclusion + // set. Queue the original memory inputs before bphi itself can peep + // away and hide those neighbor relationships. + for( int i=1; i<bphi.nIns(); i++ ) + CodeGen.CODE.add(bphi.in(i)); + Node bulk = bphi.peephole(); + CodeGen.CODE.add(bulk); + CodeGen.CODE.add(mphi); + return aggregate(bulk,alias,mem); + } + + // Build aggregate memory over a bulk Phi, preserving every precise slice + // 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(false,null,bulk); + for( int old = _aliases.nextSetBit(0); old >= 0; old = _aliases.nextSetBit(old+1) ) + if( 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. + if( _type instanceof TypeMem old && mmm._type instanceof TypeMem mem ) + mmm._type = TypeMem.make(mem._alias,mem._t,mem._one,mem._clz, + mem._final,old._escFs,old._escAs); + return mmm; + } + + // Find the precise Phi parallel to this bulk Phi at the same merge. + private Node precisePhi(int alias) { + Node found = null; + for( Node use : region().outs() ) + if( use instanceof MemPhiNode mphi && mphi._alias==alias ) { + assert found==null; + found = mphi; + } + // The parallel Phi may already have collapsed to a common input. Do + // not recover it from downstream MemMerges: they are distinct program + // points and can legitimately carry different values for this alias. + // Reconstruct from this Phi's merge inputs instead. MemPhi selects + // the alias from each input MemMerge and collapses when, for example, + // the alias is loop-invariant here. + if( found==null ) { + MemPhiNode mphi = new MemPhiNode("$"+alias,alias); + mphi.addDef(region()); + // Publishing the Phi on the Region makes it visible to recursive + // precisePhi lookups before all loop inputs have been installed. + // Give that recursion a valid lattice value until peephole computes + // the completed precise-memory Phi. + mphi.setType(TypeMem.BOT.makeFrom(alias)); + for( int i=1; i<nIns(); i++ ) + mphi.addDef(preciseInput(in(i),alias)); + found = mphi.peephole(); + // A newly reconstructed precise Phi also creates new users of + // its memory inputs. In particular, a BulkMemPhi input may now + // need to split this alias, a backwards (user-to-def) change not + // covered by processing the new Phi itself. + for( int i=1; i<mphi.nIns(); i++ ) + CodeGen.CODE.add(mphi.in(i)); + CodeGen.CODE.add(found); + } + return found; + } + + // Select an alias already split out of a predecessor's bulk memory. + private Node preciseInput(Node mem, int alias) { + if( mem instanceof MemMergeNode mmm ) + return mmm.alias(alias); + if( mem instanceof BulkMemPhiNode bulk && bulk.isSplit(alias) ) + return bulk.precisePhi(alias); + return mem; + } + + // Every alias excluded by a bulk default must be supplied explicitly. + static boolean checkMerge(MemMergeNode mmm) { + if( !(mmm.in(1) instanceof BulkMemPhiNode bulk) ) return true; + for( int alias = bulk._aliases.nextSetBit(0); alias >= 0; alias = bulk._aliases.nextSetBit(alias+1) ) + if( alias >= mmm.nIns() || mmm.in(alias)==null ) + return false; + return true; + } + + private boolean hasDup( int alias ) { + for( Node use : region().outs() ) + if( use instanceof MemPhiNode mphi && mphi._alias==alias ) + return true; + return false; + } + + @Override public boolean eq(Node n) { + return _aliases.equals(((BulkMemPhiNode)n)._aliases) && super.eq(n); + } + + @Override int hash() { return _aliases.hashCode(); } +} diff --git a/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/src/main/java/com/seaofnodes/simple/node/CFGNode.java index b9c70e5f..2b668da8 100644 --- a/src/main/java/com/seaofnodes/simple/node/CFGNode.java +++ b/src/main/java/com/seaofnodes/simple/node/CFGNode.java @@ -1,13 +1,11 @@ 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.HashMap; -import java.util.HashSet; +import java.util.IdentityHashMap; /** Control Flow Graph Nodes * <p> @@ -100,8 +98,10 @@ public abstract class CFGNode extends Node { // Find nearest enclosing FunNode public FunNode fun() { CFGNode cfg = this; - while( !(cfg instanceof FunNode fun) ) + while( !(cfg instanceof FunNode fun) ) { + if( cfg==null ) return null; cfg = cfg.idom(); + } return fun; } @@ -112,13 +112,13 @@ public abstract class CFGNode extends Node { public LoopTree _ltree; public int _pre; // Pre-order numbers for loop tree finding - static class LoopTree { - LoopTree _par; - final LoopNode _head; - int _depth; + public static class LoopTree { + public LoopTree _par; + public final LoopNode _head; + public int _depth; LoopTree(LoopNode head) { _head = head; } - @Override public String toString() { return "LOOP"+_head._nid; } - int depth() { + @Override public String toString() { return label(_head); } + public int depth() { return _depth==0 ? (_par==null ? 0 : (_depth = _par.depth()+1)) : _depth; } } @@ -127,73 +127,87 @@ 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 is a LoopNode which contains all at depth 1. - 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; } + 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 = _ltree; + post.set(start._nid); + } + }; } - _ltree = stop._ltree = Parser.XCTRL._ltree = new LoopTree((StartNode)this); - _bltWalk(2,null,stop, new BitSet()); + for( int i=0; i<work._len; i++ ) + pre = work.at(i)._bltWalk(pre,work.at(i),stop, post, work); } - int _bltWalk( int pre, FunNode fun, StopNode stop, BitSet post ) { + + int _bltWalk( int pre, FunNode fun, StopNode stop, BitSet post, Ary<FunNode> work ) { // Pre-walked? if( _pre!=0 ) return pre; _pre = pre++; // Pre-walk for( Node use : _outputs ) - 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; - for( Node use : _outputs ) { - if( !(use instanceof CFGNode usecfg) ) continue; - if( skip(usecfg) ) continue; - if( usecfg._type == Type.XCONTROL || // Do not walk dead control - usecfg._type == TypeTuple.IF_NEITHER ) // Nor dead IFs - continue; - // Child visited but not post-visited? - if( !post.get(usecfg._nid) ) { - // Must be a backedge to a LoopNode then - ltree = usecfg._ltree = new LoopTree((LoopNode)usecfg); - } else { - // Take child's loop choice, which must exist - ltree = usecfg._ltree; - // If falling into a loop, use the target loop's parent instead - if( ltree._head == usecfg ) { - if( ltree._par == null ) - // This loop never had an If test choose to take its - // exit, i.e. it is a no-exit infinite loop. - ltree._par = ltree._head.forceExit(fun,stop)._ltree; - ltree = ltree._par; - } + 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 ); } - // Sort inner loops. The decision point is some branch far removed - // from either loop head OR either backedge so requires pre-order - // numbers to figure out innermost. - if( inner == null ) { inner = ltree; continue; } - if( inner == ltree ) continue; // No change - LoopTree outer = ltree._head._pre > inner._head._pre ? inner : ltree; - inner = ltree._head._pre > inner._head._pre ? ltree : inner; - inner._par = outer; + + if( this instanceof ReturnNode ret ) { + ret._ltree = stop._ltree; + } else { + // Post-order work: find innermost loop + LoopTree inner = null, ltree; + for( Node use : _outputs ) { + if( !(use instanceof CFGNode usecfg) ) continue; + if( skip(usecfg) ) continue; + if( usecfg._type == Type.XCONTROL || // Do not walk dead control + usecfg._type == TypeTuple.IF_NEITHER ) // Nor dead IFs + continue; + // Child visited but not post-visited? + if( !post.get(usecfg._nid) ) { + // Must be a backedge to a LoopNode then + ltree = usecfg._ltree = new LoopTree((LoopNode)usecfg); + } else { + // Take child's loop choice, which must exist + ltree = usecfg._ltree; + // If falling into a loop, use the target loop's parent instead + if( ltree._head == usecfg ) { + if( ltree._par == null ) + // This loop never had an If test choose to take its + // exit, i.e. it is a no-exit infinite loop. + ltree._par = ltree._head.forceExit(fun,stop)._ltree; + ltree = ltree._par; + } + } + // Sort inner loops. The decision point is some branch far removed + // from either loop head OR either backedge so requires pre-order + // numbers to figure out innermost. + if( inner == null ) { inner = ltree; continue; } + if( inner == ltree ) continue; // No change + LoopTree outer = ltree._head._pre > inner._head._pre ? inner : ltree; + inner = ltree._head._pre > inner._head._pre ? ltree : inner; + inner._par = outer; + } + // Set selected loop + if( inner!=null ) + _ltree = inner; } - // Set selected loop - if( inner!=null ) - _ltree = inner; // Tag as post-walked post.set(_nid); return pre; } - private boolean skip(CFGNode usecfg) { + public 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 || @@ -201,8 +215,13 @@ public abstract class CFGNode extends Node { (this instanceof ReturnNode && usecfg instanceof CallEndNode); } - - public String label( CFGNode target ) { - return (target instanceof LoopNode ? "LOOP" : "L")+target._nid; + 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; + }; } + } diff --git a/src/main/java/com/seaofnodes/simple/node/CProjNode.java b/src/main/java/com/seaofnodes/simple/node/CProjNode.java index d18f779b..b74619a3 100644 --- a/src/main/java/com/seaofnodes/simple/node/CProjNode.java +++ b/src/main/java/com/seaofnodes/simple/node/CProjNode.java @@ -1,13 +1,16 @@ package com.seaofnodes.simple.node; -import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.codegen.CodeGen; +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.Utils; +import com.seaofnodes.simple.util.BAOS; + import java.util.BitSet; +import java.util.HashMap; +import java.util.IdentityHashMap; -public class CProjNode extends CFGNode { +public class CProjNode extends CFGNode implements Proj { // Which slice of the incoming multipart value public int _idx; @@ -21,6 +24,14 @@ public class CProjNode extends CFGNode { _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; } @@ -40,13 +51,13 @@ public class CProjNode extends CFGNode { public Node idealize() { if( ctrl()._type instanceof TypeTuple tt ) { if( tt._types[_idx]==Type.XCONTROL ) - return Parser.XCTRL; // We are dead + return CodeGen.CODE.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 && addDep(iff.pred()) instanceof NotNode not ) + if( ctrl() instanceof IfNode iff && iff.pred() != null && 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 @@ -65,4 +76,9 @@ public class CProjNode extends CFGNode { @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/src/main/java/com/seaofnodes/simple/node/CallEndMach.java b/src/main/java/com/seaofnodes/simple/node/CallEndMach.java index d26ea33b..05707132 100644 --- a/src/main/java/com/seaofnodes/simple/node/CallEndMach.java +++ b/src/main/java/com/seaofnodes/simple/node/CallEndMach.java @@ -14,13 +14,15 @@ 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 = code._callerSave.copy(); + RegMaskRW kills = (call().external() ? code._cCallerSave : 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 = code._mach.maxArgSlot(call().tfp()); + int xslot = call().external() + ? code._mach.maxArgSlot(call().tfp(),code._cCallingConv) + : code._mach.maxArgSlot(call().tfp()); _xslot = (maxReg+fslot)+xslot; for( int i=0; i<xslot; i++ ) kills.set((maxReg+fslot)+i); diff --git a/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/src/main/java/com/seaofnodes/simple/node/CallEndNode.java index a7e50c39..8e7f47f3 100644 --- a/src/main/java/com/seaofnodes/simple/node/CallEndNode.java +++ b/src/main/java/com/seaofnodes/simple/node/CallEndNode.java @@ -1,9 +1,13 @@ package com.seaofnodes.simple.node; -import com.seaofnodes.simple.*; +import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.BAOS; + import java.util.BitSet; +import java.util.HashMap; +import java.util.IdentityHashMap; /** * CallEnd @@ -11,16 +15,42 @@ import java.util.BitSet; public class CallEndNode extends CFGNode implements MultiNode { // When set true, this Call/CallEnd/Fun/Return is being trivially inlined - private boolean _folding; + boolean _folding; public final TypeRPC _rpc; - public CallEndNode(CallNode call) { super(new Node[]{call}); _rpc = TypeRPC.constant(_nid); } + public CallEndNode(CallNode call, TypeRPC rpc) { + super(new Node[]{call}); + _rpc = rpc; + } 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(nIns()); + // 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); } + boolean folding() { return _folding; } @Override public CFGNode idom(Node dep) { // Folding the idom is the one inlining Return @@ -40,80 +70,100 @@ public class CallEndNode extends CFGNode implements MultiNode { @Override public Type compute() { if( !(in(0) instanceof CallNode call) || call._type != Type.CONTROL ) - return TypeTuple.RET.dual(); - // Mid-fold, just take the one single callers' return type - if( _folding ) return in(1)._type; + return TypeTuple.STATE.dual(); // 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; - 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 + 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().join(tfp._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; + if( !XInt.isHigh(tfp.fidxs()) ) + for( int fidx = XInt.next(tfp.fidxs(),0); fidx >= 0; fidx = XInt.next(tfp.fidxs(),fidx) ) + if( CodeGen.CODE.externFunc(fidx)!=null ) + externs++; + 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 before SCCP, we might call extras or also the unknown target. + StartNode start = CodeGen.CODE._start; + addDep(start); + Type tmem = start._type instanceof TypeTuple tt ? tt._types[1] : (start._type.isHigh() ? TypeMem.TOP : TypeMem.BOT); + Type ret = tfp.ret(); + // If during Opto, assume call won't be called and thus won't return anything. + if( CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Opto.ordinal() ) 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); - } - } + return TypeTuple.make(Type.CONTROL,tmem,ret); } - return TypeTuple.make(call._type,TypeMem.BOT,ret); + + // 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); + // At least as good as the TFP + return state.makeFrom(2,state.ret().join(tfp.ret())); } @Override public Node idealize() { - // Trivial inlining: call site calls a single function; single function - // is only called by this call site. + // Worklist-based inlining. Cannot inline if folding, or calling + // multiple targets or no CallNode (malformed because dying). 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 + if( fptr.isConst() && // We have an immediate call // Function is being called, and its not-null - fptr._type instanceof TypeFunPtr tfp && tfp.notNull() && + fptr._type instanceof TypeFunPtr tfp && tfp.notNull() && !tfp.isHigh() && // Arguments are correct - call.err()==null ) { + call.nargs()==tfp.nargs() && goodArgs(call,tfp) ) { 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. - CFGNode idom = call; - while( true ) { - 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 { - addDep(idom); - } - } else { - addDep(fun); + int isTrivial = trivialInlining( fptr, fun ); + + // Encouraged inlining because small size and constructor. + int maxSize = fun._name!=null && fun.isInit() && !fun.isClz() ? 200 : 100; + if( isTrivial==1 && fun._approxUIDs < maxSize ) { + assert fun.sig().isa(tfp); + assert !CodeGen.CODE._midAssert; // Triggered inlining + CodeGen.CODE.add(fun); + // 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; + assert trivialInlining( fptr2, fun2 )==0; + isTrivial = 0; } - } else { // Function ptr has multiple users (so maybe multiple call sites) + + // Trivial inlining: call site calls a single function; single function + // is only called by this call site. + if( isTrivial==0 ) + return doTrivialInlining(fun); + + } else { // Function not (yet) a constant function pointer addDep(fptr); } } @@ -121,7 +171,105 @@ public class CallEndNode extends CFGNode implements MultiNode { return null; } + 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) ) + { addDep(arg); return false; } + } + return true; + } + + + // Check for trivial inlining: call only calls fun; fun only called by + // call. Returns 0 for trivial, +1 for not-trivial because fptr/fun, and + // -1 for not-trivial because idoms. + public int trivialInlining( Node fptr, FunNode fun ) { + // Heuristic forced inlining off via name + if( fun._name != null && + fun._name.endsWith("_noInline") ) + return -1; + + if( !fun.sig().isa(fptr._type) ) + return -1; // Stall until these align + + // 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; } + + CFGNode idom = call(), prior = this; + while( !(idom instanceof FunNode) ) { + if( idom==null ) { + addDep(prior); + if( prior instanceof RegionNode && !(prior instanceof StartNode) ) + for( int i=1; i<prior.nIns(); i++ ) + addDep(prior.in(i)); + return -1; + } + prior = idom; + idom = idom.idom(); + } + + + // If the *inlined* function is mid-collapse, also do not inline (yet) + idom = fun.ret(); + while( idom != fun ) { + if( idom==null ) return -1; // Forced off, half-folded call + if( idom instanceof FunNode fun2 ) + { addDep(fun2); return -1; } + if( idom instanceof CallEndNode cend && cend._folding ) + { addDep(cend); return -1; } + idom = idom.idom(); + } + + // Only fun user is this call + if( fun.nIns() > 2 ) { addDep(fun); return 1; } + // Trivial inlining: call site calls a single function; single function + // is only called by this call site. + assert fun.in(1)==call(); + + return 0; + } + + + // Do trivial inlining. Inlining does not need to clone code, merely + // triggers folding the Call/Fun and Return/CallEnd away. + private Node doTrivialInlining( FunNode fun ) { + assert !CodeGen.CODE._midAssert; // Triggered inlining + // Add the size heuristic to grow caller, preventing self-recursive + // functions from endlessly inlining. + fun()._approxUIDs += fun._approxUIDs; + // 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(); + return this; + } + + @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/src/main/java/com/seaofnodes/simple/node/CallNode.java b/src/main/java/com/seaofnodes/simple/node/CallNode.java index 47dfdf19..f2825650 100644 --- a/src/main/java/com/seaofnodes/simple/node/CallNode.java +++ b/src/main/java/com/seaofnodes/simple/node/CallNode.java @@ -1,12 +1,16 @@ package com.seaofnodes.simple.node; -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.XInt; +import com.seaofnodes.simple.util.BAOS; import com.seaofnodes.simple.util.Utils; import java.util.BitSet; +import java.util.HashMap; +import java.util.IdentityHashMap; /** * Call @@ -18,8 +22,9 @@ public class CallNode extends CFGNode { public CallNode(Parser.Lexer loc, Node... nodes) { super(nodes); _loc = loc; } public CallNode(CallNode call) { super(call); _loc = call._loc; } - - @Override public String label() { return "Call"; } + @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()); } + static Node make( BAOS bais ) { return new CallNode(null,new Node[bais.packed1()]); } @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { String fname = name(); @@ -33,7 +38,7 @@ public class CallNode extends CFGNode { } public String name() { if( fptr()._type instanceof TypeFunPtr tfp && tfp.isConstant() ) { - FunNode fun = CodeGen.CODE.link(tfp); + FunNode fun = CodeGen.CODE.lookupFun(tfp); if( fun !=null ) return fun._name; if( fptr() instanceof ExternNode ex ) return ex._extern; } @@ -97,14 +102,11 @@ 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 - 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); + 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); if( fun!=null && !fun._folding && !linked(fun) ) progress = link(fun); } @@ -128,30 +130,43 @@ public class CallNode extends CFGNode { fun.addDef(this); for( Node use : fun._outputs ) if( use instanceof ParmNode parm ) - parm.addDef(parm._idx==0 ? new ConstantNode(cend()._rpc).peephole() : arg(parm._idx)); + parm.addDef(parm._idx==0 ? ConstantNode.seed(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().keep(); + cend.delDef(cend._inputs.find(ret)); + CodeGen.CODE.add(cend); + assert !linked(fun); + ret.unkeep(); + } + + // Unlink all linked functions public void unlink_all() { for( int i=0; i<_outputs._len; i++ ) if( out(i) instanceof FunNode fun ) { - 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); + int path = fun._inputs.find(this); + unlink(fun,path); i--; } } @Override public Parser.ParseException err() { + if( fptr()._type == Type.BOTTOM ) + return null; // Wrong, but failing earlier if( !(fptr()._type instanceof TypeFunPtr tfp) ) throw Utils.TODO(); if( !tfp.notNull() ) @@ -162,11 +177,13 @@ 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)) ) { - 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 ); + // 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 ); } - if( tfp.fidxs() < 0 ) + if( tfp.isHigh() ) throw Utils.TODO(); // Infinite unknown TFPs? Should be fairly precise CG return null; diff --git a/src/main/java/com/seaofnodes/simple/node/CastNode.java b/src/main/java/com/seaofnodes/simple/node/CastNode.java deleted file mode 100644 index 6dc8e9fe..00000000 --- a/src/main/java/com/seaofnodes/simple/node/CastNode.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.seaofnodes.simple.node; - -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; -import java.util.BitSet; - -// 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 - public String uniqueName() { return "Cast_" + _nid; } - - @Override public boolean isPinned() { return true; } - @Override public boolean isConst() { return true; } - - @Override - public StringBuilder _print1(StringBuilder sb, BitSet visited) { - return in(1)._print0(sb.append(label()), visited); - } - - @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) : 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/src/main/java/com/seaofnodes/simple/node/CastMach.java b/src/main/java/com/seaofnodes/simple/node/CheckCastMach.java index e45898f5..9943c7b4 100644 --- a/src/main/java/com/seaofnodes/simple/node/CastMach.java +++ b/src/main/java/com/seaofnodes/simple/node/CheckCastMach.java @@ -3,17 +3,18 @@ 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.type.Type; 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); } +public class CheckCastMach extends CheckCastNode implements MachNode { + public CheckCastMach( CheckCastNode 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 int twoAddress( ) { return in(1)._type==Type.BOTTOM ? 0 : 1; } @Override public void encoding( Encoding enc ) { } @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(in(1))); } } diff --git a/src/main/java/com/seaofnodes/simple/node/CheckCastNode.java b/src/main/java/com/seaofnodes/simple/node/CheckCastNode.java new file mode 100644 index 00000000..900ee70e --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/CheckCastNode.java @@ -0,0 +1,82 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.BAOS; + +import java.util.BitSet; +import java.util.HashMap; +import java.util.IdentityHashMap; + +// 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 CheckCastNode extends TypeNode { + public CheckCastNode(Type t, Node ctrl, Node in) { super(t,ctrl, in); } + + public CheckCastNode(CheckCastNode c) { + super(c); // Call parent copy constructor + setType(compute()); // Ensure type is recomputed + } + @Override public Tag serialTag() { return Tag.CheckCast; } + @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 + } + static Node make( BAOS bais, Type[] types) { return new CheckCastNode(types[bais.packed2()], null, null); } + + @Override public String label() { return "("+_con.str()+")"; } + + @Override + public String uniqueName() { return "CheckCast_" + _nid; } + + @Override public boolean isPinned() { return in(0)!=null; } + + @Override + public StringBuilder _print1(StringBuilder sb, BitSet visited) { + return in(1)._print0(sb.append(label()), visited); + } + + @Override + public Type compute() { + // Cast array to int + Type t1 = in(1)._type; + if( _con == TypeInteger.BOT && t1 instanceof TypeMemPtr tmp && tmp._obj.isAry() ) + return _con; + + // If unconditional, it must collapse (or error) + if( in(0)==null ) + return _con; + // If conditional, return the join. The guard test ensures + // the join is correct. + return t1.join(_con); + } + + @Override + public Node idealize() { + return in(1)._type.isa(_con) ? in(1) : null; + } + + @Override + public Parser.ParseException err() { + // Has a condition to test, so OK + if( in(0) != null ) return null; + if( in(1)._type != Type.BOTTOM ) + // No condition to test, so this must optimize away + return Parser.error( "Type " + in(1)._type.str() + " is not of declared type " + _con.str(), null ); + // Only happens for un-initialized refs that must init. + // Allow uses of initializing stores, but other uses represent a + // partially initialized field, and should error + boolean bad = false; + StoreNode init=null; + for( Node use : _outputs ) + if( (use instanceof StoreNode st && st._init && !((TypeMemPtr)st.ptr()._type)._obj._name.startsWith("class") ) ) + init = st; + else if( (use instanceof StoreNode st && st.val() instanceof CheckCastNode cast && cast.in(1)._type == Type.BOTTOM) ) + /*bogus init store in constructor*/; + else bad = true; + if( !bad ) return null; + if( init==null ) return null; // Error somewhere else + // Expecting an init store + TypeMemPtr ptr = (TypeMemPtr)init.ptr()._type; + throw Parser.error("'"+ptr._obj._name+"' is not fully initialized, field '" + init._name + "' is only partially set in the constructor", init._loc); + } +} diff --git a/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java b/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java index c54da2c4..11a16d28 100644 --- a/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java @@ -1,11 +1,14 @@ 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.TypeStruct; import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.util.Utils; import java.util.BitSet; +import java.util.HashMap; /** * A field offset. This becomes a constant *after* other optimizations which @@ -14,12 +17,13 @@ import java.util.BitSet; */ public class ConFldOffNode extends ConstantNode { - public final String _name; // Struct name + public TypeStruct _ts; // Struct holding field public final String _fname; // Field name - public ConFldOffNode( String name, String fname ) { super(TypeInteger.BOT); _name = name; _fname = fname; } + public ConFldOffNode( TypeStruct ts, String fname ) { super(TypeInteger.BOT); _ts = ts; _fname = fname; } + @Override public Tag serialTag() { throw Utils.TODO("should not reach here"); } @Override public String label() { - return _fname == " len" ? "sizeof("+_name+")" : "#"+_fname; + return _fname == " len" ? "sizeof("+_ts._name+")" : "#"+_fname; } @Override public String glabel() { return label(); } @Override public String uniqueName() { return "Off_" + _nid; } @@ -29,15 +33,41 @@ public class ConFldOffNode extends ConstantNode { return sb.append(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(TypeStruct ts) { - return new ConstantNode(TypeInteger.constant(ts.offset(_fname==" len" ? ts._fields.length : ts.find(_fname)))).peephole(); + public Node asOffset() { + int fldx = _fname==" len" ? _ts._fields.length : _ts.find(_fname); + if( fldx == -1 ) return null; + return ConstantNode.make(TypeInteger.constant(_ts.offset(fldx))).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 _name.equals(off._name) && _fname.equals(off._fname) && super.eq(n); + return _ts._name==off._ts._name && _fname.equals(off._fname) && super.eq(n); } - @Override int hash() { return super.hash() * _name.hashCode() * _fname.hashCode(); } + @Override int hash() { return super.hash() * _ts._name.hashCode() * _fname.hashCode(); } } diff --git a/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/src/main/java/com/seaofnodes/simple/node/ConstantNode.java index 22bf03c4..feed8c5c 100644 --- a/src/main/java/com/seaofnodes/simple/node/ConstantNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ConstantNode.java @@ -3,8 +3,11 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeFunPtr; -import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.util.BAOS; + import java.util.BitSet; +import java.util.HashMap; +import java.util.IdentityHashMap; /** * A Constant node represents a constant value. @@ -16,28 +19,63 @@ import java.util.BitSet; * The Constant's value is the value stored in it. */ -public class ConstantNode extends Node { - public final Type _con; +public class ConstantNode extends TypeNode { public ConstantNode( Type type ) { - super(new Node[]{CodeGen.CODE._start}); - _con = _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); _con = con._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 + } + static Node make( BAOS bais, Type[] types) { return raw(types[bais.packed2()]); } + // Semantic constant construction through Opto. 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.Opto.ordinal() ) + return raw(type); + if( type instanceof TypeFunPtr tfp && tfp.isConstant() ) { + FunNode fun = CodeGen.CODE.lookupFun(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); + } 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 public String label() { return "#"+_con; } @Override public String glabel() { return "#"+_con.gprint(); } @Override public String uniqueName() { return "Con_" + _nid; } + @Override public Node copy() { return raw(this); } + @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { if( _con instanceof TypeFunPtr tfp && tfp.isConstant() ) { - FunNode fun = CodeGen.CODE.link(tfp); + FunNode fun = CodeGen.CODE.lookupFun(tfp); if( fun!=null && fun._name != null ) return sb.append("{ ").append(fun._name).append("}"); } @@ -47,6 +85,4 @@ public class ConstantNode extends Node { @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(); } } diff --git a/src/main/java/com/seaofnodes/simple/node/ConvertNode.java b/src/main/java/com/seaofnodes/simple/node/ConvertNode.java new file mode 100644 index 00000000..4e8e93ce --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/ConvertNode.java @@ -0,0 +1,96 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.type.*; + +import java.util.BitSet; +import java.util.HashMap; + +// Convert a value to an authoritative destination type. The destination is +// known structurally from a declaration; the source family might sharpen later. +public class ConvertNode extends Node { + private Type _dst; + + public ConvertNode(Type dst, Node val) { + super(null,val); + // Folding this conversion would replace the Return-linked FunPtrNode + // with an unhooked ConstantNode. + assert !(dst instanceof TypeFunPtr && val instanceof FunPtrNode); + _dst = dst; + } + + public Type dst() { return _dst; } + public Node val() { return in(1); } + + @Override public String label() { return "Convert_"+_dst.str(); } + @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { + return val()._print0(sb.append(label()).append("("),visited).append(")"); + } + + // The declaration fixes the result family during pessimistic parsing. + // Optimistic SCCP must retain stronger facts already inside that family. + @Override public Type compute() { + Type src = val()._type; + if( CodeGen.CODE._phase == CodeGen.Phase.Opto ) { + if( src.isHigh() ) return Type.TOP; + if( src.isa(_dst) ) return src; + } + return _dst; + } + + @Override public Node idealize() { + Type src = val()._type; + if( src==Type.BOTTOM || src==Type.TOP ) return null; + if( src.isa(_dst) ) return val(); + + // Managed arrays are represented by an object pointer, while C wants + // the address of the first element. The representation cast itself is + // zero-code; the Add skips the array header. + if( _dst==TypeInteger.BOT && src instanceof TypeMemPtr tmp && tmp._obj.isAry() ) + return new AddNode(new PtrToIntNode(val()).peephole(),Parser.off(tmp._obj,"[]")); + + // Integer (and nil-as-zero) to floating point. + if( (src instanceof TypeInteger || src==Type.NIL) && _dst instanceof TypeFloat ) + return new ToFloatNode(val()); + + // Narrow integers produce the declared sign/zero extension. + if( src instanceof TypeInteger && _dst instanceof TypeInteger dst ) { + if( dst._min==0 ) + return new AndNode(null,val(),con(dst._max)); + int shift = Long.numberOfLeadingZeros(dst._max)-1; + Node shf = con(shift); + if( shf._type==TypeInteger.ZERO ) return val(); + return new SarNode(null,new ShlNode(null,val(),shf.keep()).peephole(),shf.unkeep()); + } + + // Narrow f64 to f32. + if( src instanceof TypeFloat && _dst instanceof TypeFloat ) + return new RoundF32Node(val()); + + // Wait for more source information, or report an error after Opto. + 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 || + val()._type instanceof TypeNil src && src.nullable() && _dst instanceof TypeNil dst && dst.notNull() ) { + Node out = out(0); + if( out instanceof PhiNode phi ) out = phi.out(0); + if( out instanceof StoreNode st ) + return Parser.error( "'" + ((TypeMemPtr)st.ptr()._type)._obj._name + "' is not fully initialized, field '" + st._name + "' is only partially set in the constructor", null ); + } + return Parser.error("Type "+val()._type.str()+" is not of declared type "+_dst.str(),null); + } + + @Override public boolean eq(Node n) { return _dst==((ConvertNode)n)._dst; } + @Override int hash() { return _dst.hashCode(); } +} diff --git a/src/main/java/com/seaofnodes/simple/node/CtrlNode.java b/src/main/java/com/seaofnodes/simple/node/CtrlNode.java index eda7d684..39c95aa4 100644 --- a/src/main/java/com/seaofnodes/simple/node/CtrlNode.java +++ b/src/main/java/com/seaofnodes/simple/node/CtrlNode.java @@ -7,6 +7,7 @@ import java.util.BitSet; 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 public StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append("Cctrl"); } @Override public boolean isConst() { return true; } diff --git a/src/main/java/com/seaofnodes/simple/node/DivFNode.java b/src/main/java/com/seaofnodes/simple/node/DivFNode.java deleted file mode 100644 index 0d037a30..00000000 --- a/src/main/java/com/seaofnodes/simple/node/DivFNode.java +++ /dev/null @@ -1,26 +0,0 @@ -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); } - - @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/src/main/java/com/seaofnodes/simple/node/DivNode.java b/src/main/java/com/seaofnodes/simple/node/DivNode.java index 0de1c946..dcc9a27a 100644 --- a/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -2,15 +2,19 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.type.TypeFloat; import java.util.BitSet; 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 label() { return "Div"; } - @Override public String op() { return "//"; } + @Override public String op() { return _mode==1 ? "//" : "/"; } - @Override long doOp( long x, long y ) { return y==0 ? 0 : x / y; } + @Override long doOp( long x, long y ) { return y==0 ? 0 : x / y; } + @Override double doOp(double x, double y ) { return x / y; } @Override TypeInteger doOp(TypeInteger x, TypeInteger y) { return TypeInteger.BOT; } @@ -20,9 +24,17 @@ public class DivNode extends ArithNode { // Div of 1. if( in(2)._type == TypeInteger.TRUE ) return in(1); + + // Div of constant + if( _mode==2 ) { + if( in(2)._type == TypeFloat.constant(1.) ) + return in(1); + if( in(2)._type instanceof TypeFloat f && f.isConstant() ) + return new MulNode(in(1),ConstantNode.make(TypeFloat.constant(1.0/f.value())).peephole(),_mode); + } + return super.idealize(); } - @Override Node copy(Node lhs, Node rhs) { return new DivNode(lhs,rhs); } - @Override Node copyF() { return new DivFNode(null,null); } + @Override Node copy(Node lhs, Node rhs) { return new DivNode(lhs,rhs,_mode); } } diff --git a/src/main/java/com/seaofnodes/simple/node/EscapeNode.java b/src/main/java/com/seaofnodes/simple/node/EscapeNode.java new file mode 100644 index 00000000..df654291 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/EscapeNode.java @@ -0,0 +1,114 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.*; +import java.util.BitSet; +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 + public StringBuilder _print1(StringBuilder sb, BitSet visited) { + sb.append("Esc#").append(fld()._alias).append(" {"); + if( self()==null ) sb.append("---"); + else self()._print0(sb,visited).append(","); + priv()._print0(sb,visited).append("}, "); + pub ()._print0(sb,visited); + return sb; + } + + @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 GLB of private + 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() ) + return 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/src/main/java/com/seaofnodes/simple/node/ExternNode.java b/src/main/java/com/seaofnodes/simple/node/ExternNode.java index a10bb84a..9768f41f 100644 --- a/src/main/java/com/seaofnodes/simple/node/ExternNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ExternNode.java @@ -1,23 +1,40 @@ package com.seaofnodes.simple.node; -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.Serialize; import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFunPtr; +import com.seaofnodes.simple.util.BAOS; 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; - public ExternNode(Type t, String ex) { super(t); _extern = ex; } + // 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()]); + } @Override public String label() { return "#"+_con+":"+_extern; } @Override public String glabel() { return _con.print(new SB().p("#"), new BitSet(), true).p(":").p(_extern).toString(); } @Override public String uniqueName() { return "Extern_" + _nid; } @Override public boolean eq(Node n) { return this==n; } + @Override public void gather( HashMap<String,Integer> strs ) { + Serialize.gather(strs,_extern); + } + } diff --git a/src/main/java/com/seaofnodes/simple/node/FRefNode.java b/src/main/java/com/seaofnodes/simple/node/FRefNode.java index 6093b8a6..723e0007 100644 --- a/src/main/java/com/seaofnodes/simple/node/FRefNode.java +++ b/src/main/java/com/seaofnodes/simple/node/FRefNode.java @@ -9,18 +9,28 @@ import java.util.BitSet; * 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 final Var _n; - public FRefNode( Var n ) { super(FREF_TYPE); _n = n; } + public String _name; + public final Parser.Lexer _loc; // Source location + + public FRefNode( String name, Parser.Lexer loc ) { super(FREF_TYPE); _name = name; _loc = loc; } - @Override public String label() { return "FRef"+_n; } + @Override public String label() { return "FRef"+_name; } @Override public String uniqueName() { return "FRef_" + _nid; } @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { - return sb.append("FRef_").append(_n); + return sb.append("FRef_").append(_name); } @Override public Node idealize() { @@ -28,8 +38,8 @@ public class FRefNode extends ConstantNode { return nIns()==1 ? null : in(1); } - public Parser.ParseException err() { return Parser.error("Undefined name '"+_n._name+"'",_n._loc); } + public Parser.ParseException err() { return Parser.error("Undefined name '"+_name+"'",_loc); } @Override public boolean eq(Node n) { return this==n; } - @Override int hash() { return _n._idx; } + @Override int hash() { return _name.hashCode(); } } diff --git a/src/main/java/com/seaofnodes/simple/node/FldOffNode.java b/src/main/java/com/seaofnodes/simple/node/FldOffNode.java new file mode 100644 index 00000000..3a44f588 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/FldOffNode.java @@ -0,0 +1,50 @@ +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.SB; +import com.seaofnodes.simple.util.Utils; +import java.util.BitSet; +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 public String glabel() { return label(); } + @Override public String uniqueName() { return "Off_" + _nid; } + + @Override + public StringBuilder _print1(StringBuilder sb, BitSet visited) { + return sb.append(label()); + } + + @Override public Type compute() { + return Type.BOTTOM; + } + + @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); + 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(); } +} diff --git a/src/main/java/com/seaofnodes/simple/node/FunNode.java b/src/main/java/com/seaofnodes/simple/node/FunNode.java index 2931b9ea..5018f5d6 100644 --- a/src/main/java/com/seaofnodes/simple/node/FunNode.java +++ b/src/main/java/com/seaofnodes/simple/node/FunNode.java @@ -1,41 +1,68 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.Parser; -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.codegen.*; +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.BAOS; import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; 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 - boolean _folding; + public boolean _folding; private TypeFunPtr _sig; // Initial signature private ReturnNode _ret; // Return pointer public String _name; // Debug name - public FunNode( Parser.Lexer loc, TypeFunPtr sig, String name, Node... nodes ) { super(loc,nodes); _name=name; _sig = sig; } + 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( TypeFunPtr sig, String name, boolean ext ) { + this(null,null,sig,name,null,ext); + assert ext; // No need for ext + throw Utils.TODO(); + } public FunNode( FunNode fun ) { - super( fun, fun==null ? null : fun._loc ); - if( fun!=null ) { - _sig = fun.sig(); - _name = fun._name; - } else { - _sig = TypeFunPtr.BOT; - _name = ""; - } + 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(nIns()); // Number of linked calls + baos.packed2(types.get(_sig)); // NPE if fails lookup + baos.packed2(_name==null ? 0 : strs.get(_name)); + baos.packed2(_approxUIDs); + } + 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(); + return fun; } - @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. @@ -47,13 +74,14 @@ public class FunNode extends RegionNode { return null; } - public ParmNode rpc() { - ParmNode rpc = null; + public ParmNode parm(int idx) { + ParmNode pidx = null; for( Node n : _outputs ) - if( n instanceof ParmNode parm && parm._idx==0 ) - { assert rpc==null; rpc=parm; } - return rpc; + if( n instanceof ParmNode parm && parm._idx==idx ) + { assert pidx==null; pidx=parm; } + return pidx; } + 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. @@ -66,13 +94,29 @@ public class FunNode extends RegionNode { 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); _sig = sig; + unlock(); } } + @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=name; - else _name += "."+name; + if( _name==null ) + _name = _compunit==null || _compunit._cname==null || _compunit._cname.indexOf('.')==-1 + ? name + : (_compunit._cname+"."+name).intern(); } @Override @@ -81,10 +125,10 @@ public class FunNode extends RegionNode { if( unknownCallers() ) return Type.CONTROL; Type t = Type.XCONTROL; - 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 + 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. if( !(in(i) instanceof StartNode) ) t = t.meet(in(i)._type); } @@ -94,28 +138,37 @@ public class FunNode extends RegionNode { @Override public Node idealize() { - // Some linked path dies, except main never kills Start + // Some linked path dies, except unknown caller hooks never kill Start. Node progress = deadPath(unknownCallers()); if( progress!=null ) { - if( nIns()==3 && in(2) instanceof CallNode call ) - CODE.add(call.cend()); // If Start and one call, check for inline + if( nIns()==2 && in(1) instanceof CallNode call ) + CODE.add(call.cend()); // If no 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() ) { + if( _ret!=null && _ret._type instanceof TypeTuple tt && + tt.ret() != _sig.ret() && tt.ret().isa(_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 + if( 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 @@ -134,24 +187,58 @@ public class FunNode extends RegionNode { } // Bypass Region idom, always assume depth == 1, one more than Start, - // unless folding then just a ID on input#1 + // unless folding @Override public int idepth() { - return _folding ? super.idepth() : CodeGen.CODE.iDepthAt(1); + if( _folding ) return super.idepth(); + return CodeGen.CODE.iDepthAt(1); } + // Bypass Region idom, always assume idom is Start - @Override public CFGNode idom(Node dep) { return _folding && nIns()==3 ? cfg(2) : (nIns()>1 ? cfg(1) : null); } + @Override public CFGNode idom(Node dep) { + return _folding ? cfg(1) : null; + } // Always in-progress until we run out of unknown callers public boolean unknownCallers() { return nIns()>=2 && in(1) instanceof StartNode; } - @Override public boolean inProgress() { return unknownCallers(); } + 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>"); } + + // 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 + // Build the function body set public BitSet body() { - // Reverse up (stop to start) CFG only, collect bitmap. BitSet cfgs = new BitSet(); cfgs.set(_nid); @@ -161,7 +248,13 @@ public class FunNode extends RegionNode { // 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(); - walkDown(this, cfgs, body, new BitSet()); + BitSet visit = new BitSet(); + int old; + do { + old = body.cardinality(); + visit.clear(); + walkDown(this, cfgs, body, visit); + } while( old != body.cardinality() ); return body; } @@ -175,20 +268,113 @@ public class FunNode extends RegionNode { } 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); - - if( n instanceof CFGNode && !cfgs.get(n._nid) ) + // Visit self as CFG outside the function + if( n instanceof CFGNode && !cfgs.get(n._nid) && unfolded( n ) ) return false; - if( n.in(0)!=null && !cfgs.get(n.in(0)._nid) ) + // 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) && unfolded( n.in( 0 ) ) ) 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 ) + 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 in |= walkDown(use,cfgs,body,visit); + } if( in ) body.set(n._nid); return in; } + // A CFG is folding, and so is basically Data + private static boolean unfolded( Node cfg) { + if( cfg instanceof CallNode call && call.cend().folding() ) return false; + if( cfg instanceof CallEndNode cend && cend.folding() ) return false; + if( cfg instanceof FunNode fun && fun._folding ) return false; + if( cfg instanceof ReturnNode ret && ret._fun._folding ) return false; + return true; + } + + // Clone function body. Give function a new FIDX. + FunNode copyBody() { + // Build the function body BitSet + BitSet body = body(); + assert body.cardinality() < 100; + // Walk the body, cloning + IdentityHashMap<Node,Node> map = new IdentityHashMap<>(); + BitSet visit = CodeGen.CODE.visit(); + bodyCopy( visit, body, map, this ); + visit.clear(); + bodyEdge( visit, body, map, this ); + 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; + + // Remove all callers - this is a private copy + while( fun2.nIns() > 1 ) + if( fun2.in(1) instanceof CallNode call && body.get(call._nid) ) + call.unlink(fun2,1); // Recursive calls unlink + else fun2.removeDeadPath(1); // Start and external calls can just be removed + + // 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; + } + + // 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); + // The cloned Call should not clone any linked edges. + if( n instanceof CallEndNode cend && !cend._folding ) { + m.addDef( map.get(cend.call()) ); + } else { + for( Node e : n._inputs ) { + Node x = e; + if( e != null ) { + x = map.get(e); + if( x == null ) x = e; + } + m.addDef(x); + } + } + for( Node x : n._outputs ) + bodyEdge(visit,body,map,x); + } + + // FunNodes must match signature (equivalent: no 2 FunNodes are ever GVN'able) @Override public boolean eq( Node n ) { return _sig == ((FunNode)n)._sig; @@ -245,4 +431,7 @@ public class FunNode extends RegionNode { return slotRotate*8; } + @Override public void gather( HashMap<String,Integer> strs ) { + Serialize.gather(strs,_name); + } } diff --git a/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java b/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java new file mode 100644 index 00000000..33757640 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java @@ -0,0 +1,65 @@ +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.BitSet; +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); + } + public ReturnNode ret() { return (ReturnNode)in(1); } + public FunNode fun() { return ret().fun(); } + + @Override public String label() { return "#"+_con; } + @Override public String glabel() { return "#"+_con.gprint(); } + @Override public String uniqueName() { return "FunPtr_" + _nid; } + @Override public Node copy() { + FunPtrNode fptr = new FunPtrNode((TypeFunPtr)_con,null,null); + fptr._type = _type; + return fptr; + } + + @Override + public StringBuilder _print1(StringBuilder sb, BitSet visited) { + FunNode fun = CodeGen.CODE.lookupFun(((TypeFunPtr)_con).fidx()); + return fun!=null && fun._name!=null + ? sb.append("{ ").append(fun._name).append("}") + : sb.append(_con); + } + + @Override public boolean isConst() { return true; } + @Override public boolean isPinned() { return true; } + @Override public Type compute() { + ReturnNode ret = ret(); + return ret == null ? _con : ret.fun().sig(); + } + @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/src/main/java/com/seaofnodes/simple/node/GuardMach.java b/src/main/java/com/seaofnodes/simple/node/GuardMach.java new file mode 100644 index 00000000..202b1ab8 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/GuardMach.java @@ -0,0 +1,17 @@ +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.type.Type; +import com.seaofnodes.simple.util.SB; + +public class GuardMach extends GuardNode implements MachNode { + public GuardMach(GuardNode guard) { super(guard); } + @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 in(1)._type==Type.BOTTOM ? 0 : 1; } + @Override public void encoding(Encoding enc) { } + @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(in(1))); } +} diff --git a/src/main/java/com/seaofnodes/simple/node/GuardNode.java b/src/main/java/com/seaofnodes/simple/node/GuardNode.java new file mode 100644 index 00000000..e3c6f0e2 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/GuardNode.java @@ -0,0 +1,61 @@ +package com.seaofnodes.simple.node; + +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.BitSet; +import java.util.HashMap; +import java.util.IdentityHashMap; + +// A branch-proven refinement to zero/null or non-zero/non-null. The value +// family is deliberately taken from the input when it becomes known. +public class GuardNode extends Node { + public final boolean _nonZero; + + public GuardNode(boolean nonZero, Node ctrl, Node val) { + super(ctrl,val); + _nonZero = nonZero; + } + + public GuardNode(GuardNode guard) { + super(guard); + _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)"; } + @Override public String uniqueName() { return "Guard_"+_nid; } + @Override public boolean isPinned() { return true; } + + @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { + return in(1)._print0(sb.append(label()),visited); + } + + @Override public Type compute() { + Type t = in(1)._type; + if( t==Type.BOTTOM || t==Type.TOP ) return t; + // The scalar envelope does not identify a value family. Preserve it + // until SCCP learns whether this is an int, float, pointer, or fptr; + // 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(); + return t2==null ? (_type==null ? Type.TOP : _type) : t2; // No sane answer for e.g. not-zero of a zero + } + + @Override public Node idealize() { + if( in(1)._type==Type.BOTTOM || in(1)._type==Type.TOP ) return null; + if( _type.isHighOrConst() && in(1)._type.isa(_type) ) + return in(1); + return null; + } + + @Override public boolean eq(Node n) { return _nonZero==((GuardNode)n)._nonZero; } + @Override int hash() { return _nonZero ? 1 : 0; } +} diff --git a/src/main/java/com/seaofnodes/simple/node/IfNode.java b/src/main/java/com/seaofnodes/simple/node/IfNode.java index 5d751602..d8d72411 100644 --- a/src/main/java/com/seaofnodes/simple/node/IfNode.java +++ b/src/main/java/com/seaofnodes/simple/node/IfNode.java @@ -12,9 +12,7 @@ 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 String label() { return "If"; } + @Override public Tag serialTag() { return Tag.If; } @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { @@ -45,7 +43,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==0) ) ? TypeTuple.IF_FALSE : TypeTuple.IF_TRUE; + return (t==Type.NIL || t==TypeInteger.ZERO || (t instanceof TypeFunPtr tfp && tfp._fidxs== XInt.EMPTY) ) ? 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; @@ -58,15 +56,14 @@ 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 && addDep(iff.pred())==pred() && 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) ) ) { 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/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/src/main/java/com/seaofnodes/simple/node/LoadNode.java index 58dbfc81..f67f2b97 100644 --- a/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -2,7 +2,9 @@ package com.seaofnodes.simple.node; 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.BAOS; import com.seaofnodes.simple.util.Utils; import java.util.BitSet; @@ -20,9 +22,14 @@ 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, Type glb, Node mem, Node ptr, Node off) { - super(loc, name, alias, true, glb, mem, ptr, off); + 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); } + 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; } // GraphVis DOT code (must be valid Java identifiers) and debugger labels @Override public String label() { return "ld_"+mlabel(); } @@ -34,67 +41,110 @@ public class LoadNode extends MemOpNode { @Override public Type compute() { - Type tmem = mem()._type; - if( !(tmem instanceof TypeMem mem) ) - return tmem; // No memory yet? Assume TOP/BOT - assert !_declaredType.isFRef(); - // No lifting if ptr might null-check - Type tptr = ptr()._type; - if( err() != null ) - return _declaredType; - if( !(tptr instanceof TypeMemPtr tmp) ) - return tptr; // No pointer yet? Assume TOP/BOT + Type mem0 = aliasMem()._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( ptr0 == Type.NIL ) + return TypeScalar.TOP; + if( !(ptr0 instanceof TypeMemPtr ptr) ) + return mem0.isHigh() ? TypeScalar.TOP : scalar(mem._t); + // Load field from object - Field f = tmp._obj.field(_name); + TypeMemPtr tmp = ptr; + 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( f == null ) - return tmp.isHigh() ? Type.TOP : _declaredType; - Type t = f._t; + if( pfld == null ) + return tmp.isHigh() || tmp._obj.isHigh() ? TypeScalar.TOP : TypeScalar.BOT; + + Type t = pfld._t; // Load member of constant array if( t instanceof TypeConAry ary ) t = ary.elem(); // TODO: if offset is known, can peek the constant - // Lift from declared type and memory input - t = t.join(mem._t); - if( _declaredType.isFinal() ) - t = t.makeRO(); // Deep final applied - // Pinch between declared type - t = t.join(_declaredType); - return t; + + // Now, do the same for memory + if( mem._t instanceof TypeStruct ts ) { + assert ts._name==tmp._obj._name; + Field mfld = ts.field(_name); + // Lift from declared type and memory input + if( mfld != null ) + t = t.join(mfld._t); + } else if( mem._alias==_alias ) { + t = t.join(mem._t); + } else if( mem._t == Type.TOP ) { + return TypeScalar.TOP; + } + + // 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() ) + 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) { + return t==Type.TOP ? TypeScalar.TOP : t==Type.BOTTOM ? TypeScalar.BOT : t; } @Override public Node idealize() { Node ptr = ptr(); - Node mem = mem(); + Node mem = aliasMem(); + + // Loads into structs do not need a ctrl edge, as null-ptr checking is + // baked into the type system. Loads into arrays DO need the ctrl + // edge, at least until proper range-checking is in place. + if( in(0)!=null && ptr()._type instanceof TypeMemPtr tmp && + // Never can be an array + (!tmp._obj._open || (tmp._obj._fields.length > 1 && tmp._obj._fields[0]._fname != "#")) ) { + setDef(0,null); + return this; + } - if( mem instanceof CastNode cast ) { - setDef(1,cast.in(1)); + // 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; + unlock(); // Alias participates in GVN semantics + _alias = fld._alias; + // Bulk memory may already have inspected this user. Revisit it + // once the declaring shape is stable; an open forward reference + // can still change this Load's semantics. + if( !tmp._obj._open && !tmp._obj._fref ) + CodeGen.CODE.add(mem()); return this; } + // Must sharpen alias first + if( _alias == 1 ) + return null; // Simple Load-after-Store on same address. if( mem instanceof StoreNode st && - ptr == st.ptr() && off() == st.off() ) { // Must check same object + ptr == st.nnptr() && + off() == st.off() ) { // Must check same object assert _name.equals(st._name); // Equiv class aliasing is perfect return extend(st.val()); } - // Simple load-after-MemMerge to a known alias can bypass. Happens when inlining. - if( mem instanceof MemMergeNode mem2 ) { - Node memA = mem2.alias(_alias); - for( Node ld : memA._outputs ) + // Expose the same effective memory input already observed by compute. + if( mem != mem() ) { + for( Node ld : mem._outputs ) if( ld instanceof LoadNode ) CodeGen.CODE.add(ld); - setDef(1,memA); + if( mem instanceof BulkMemPhiNode ) CodeGen.CODE.add(mem); + setDef(1,mem); return this; } - // Simple Load-after-New on same address. - if( mem instanceof ProjNode p && p.in(0) instanceof NewNode nnn && - ptr == nnn.proj(1) ) // Must check same object - return zero(nnn); // Load zero from new - // Uplift control to a prior dominating load. for( Node memuse : mem._outputs ) // Find a prior load, has same mem,ptr,off but higher ctrl @@ -115,8 +165,11 @@ public class LoadNode extends MemOpNode { return extend(castRO(st.val())); // Proved equal // Can we prove unequal? Offsets do not overlap? if( !off()._type.join(st.off()._type).isHigh() && // Offsets overlap - !neverAlias(ptr,st.ptr()) ) // And might alias - break outer; // Cannot tell, stop trying + !neverAlias(ptr,st.ptr()) ) { // And might alias + addDep( off()); // Offsets can fold, proving unequal + addDep(st.off()); // Offsets can fold, proving unequal + break outer; // Cannot tell, stop trying + } // Pointers cannot overlap mem = st.mem(); // Proved never equal break; @@ -124,23 +177,32 @@ public class LoadNode extends MemOpNode { // Assume related addDep(phi); break outer; - case ConstantNode top: break outer; // Assume shortly dead + case ConstantNode con: + // Load from constant memory + if( con._con instanceof TypeMem tmem ) + return ConstantNode.make(tmem._t); + break outer; // Assume shortly dead case ProjNode mproj: // Memory projection switch( mproj.in(0) ) { case NewNode nnn1: - if( ptr instanceof ProjNode pproj && pproj.in(0) == mproj.in(0) ) - return zero(nnn1); - if( !(ptr instanceof ProjNode pproj && pproj.in(0) instanceof NewNode) ) - break outer; // Cannot tell, ptr not related to New - mem = nnn1.in(nnn1.findAlias(_alias));// Bypass unrelated New - break; + // Direct load from e.g. new array elements + assert _name=="[]"; + Type decl = declaredType(); + return decl==Type.BOTTOM ? null : ConstantNode.make(decl.makeZero()); case StartNode start: break outer; - case CallEndNode cend: break outer; // TODO: Bypass no-alias call + case CallEndNode cend: addDep(mproj); break outer; // TODO: Bypass no-alias call default: throw Utils.TODO(); } - break; - case CastNode cast: mem = cast.in(1); break; + case CheckCastNode cast: mem = cast.in(1); break; case MemMergeNode merge: mem = merge.alias(_alias); break; + case EscapeNode esc: + if( esc.self()==ptr ) // Proved equal + { mem = esc.priv(); break; } + // Two NewNodes are always unequal + if( esc.self().in(0) instanceof NewNode && ptr.in(0) instanceof NewNode ) + { mem = esc.pub(); break; } + // TODO: Can we prove unequal? + break outer; default: throw Utils.TODO(); @@ -157,6 +219,8 @@ public class LoadNode extends MemOpNode { if( mem() instanceof PhiNode memphi && memphi.region()._type == Type.CONTROL && memphi.nIns()== 3 && // Offset can be hoisted off() instanceof ConstantNode && + // Not control dependent + in(0)==null && // Pointer can be hoisted hoistPtr(ptr,memphi) ) { @@ -164,25 +228,34 @@ public class LoadNode extends MemOpNode { if( profit(memphi,2) || // Else must not be a loop to count profit on LHS. (!(memphi.region() instanceof LoopNode) && profit(memphi,1)) ) { + // profit() peepholes inputs and can restore a required control + // edge. Recheck before manufacturing control-free loads. + if( in(0)!=null ) + return null; Node ld1 = ld(1); Node ld2 = ld(2); - return new PhiNode(_name,_declaredType,memphi.region(),ld1,ld2); + PhiNode phi = new PhiNode(_name, memphi.region(),ld1,ld2); + phi.setType(phi.compute().join(_type)); + return phi; } } return null; } - // Load a flavored zero from a New - private Node zero(NewNode nnn) { - TypeStruct ts = nnn._ptr._obj; - Type zero = ts._fields[ts.findAlias(_alias)]._t.makeZero(); - return castRO(new ConstantNode(zero).peephole()); + // Semantic memory input for this Load. Compute defines behavior from + // this view; ideal merely exposes the same edge in the graph. + private Node aliasMem() { + Node mem = mem(); + return _alias != 1 && mem instanceof MemMergeNode merge + ? merge.alias(_alias) + : mem; } private Node ld( int idx ) { Node mem = mem(), ptr = ptr(); - return new LoadNode(_loc,_name,_alias,_declaredType,mem.in(idx),ptr instanceof PhiNode && ptr.in(0)==mem.in(0) ? ptr.in(idx) : ptr, off()).peephole(); + assert in(0)==null; + return new LoadNode(_loc,_name,_alias,null,mem.in(idx),ptr instanceof PhiNode && ptr.in(0)==mem.in(0) ? ptr.in(idx) : ptr, off()).peephole(); } private static boolean neverAlias( Node ptr1, Node ptr2 ) { @@ -215,8 +288,14 @@ public class LoadNode extends MemOpNode { private boolean profit(PhiNode phi, int idx) { Node px = phi.in(idx); if( px==null ) return false; - if( px._type instanceof TypeMem mem && mem._t.isHighOrConst() ) return true; - if( px instanceof StoreNode st1 && ptr()==addDep(st1.ptr() )&& off()==st1.off() ) return true; + if( px._type instanceof TypeMem mem && mem._t.isHighOrConst() ) + // To avoid cyclic pushing a Load up then down, getting here means + // the load *must* replace with the high/constant. + return true; + if( px instanceof StoreNode st1 && ptr()==addDep(st1.nnptr() )&& off()==st1.off() ) + // To avoid cyclic pushing a Load up then down, getting here means + // the load *must* match against the Store + return true; addDep(px); return false; } @@ -231,15 +310,25 @@ 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( shf._type==TypeInteger.ZERO ) + if( shf._type==TypeInteger.ZERO ) { + // A freshly exposed Store value can still be globally unknown. + // Revisit this Load when it settles instead of replacing a typed + // Load with the weaker transient value. + if( !val._type.isa(_type) ) { + addDep(val); + return null; + } return val; + } Node shl = new ShlNode(null,val,shf.keep()).peephole(); return new SarNode(null,shl,shf.unkeep()); } + + @Override public int log_size() { return declaredType().log_size(); } } diff --git a/src/main/java/com/seaofnodes/simple/node/LoopNode.java b/src/main/java/com/seaofnodes/simple/node/LoopNode.java index fc50e17f..44ca35d8 100644 --- a/src/main/java/com/seaofnodes/simple/node/LoopNode.java +++ b/src/main/java/com/seaofnodes/simple/node/LoopNode.java @@ -3,23 +3,22 @@ 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.Utils; -import java.util.BitSet; -import java.util.HashSet; +import com.seaofnodes.simple.util.BAOS; + +import java.util.HashMap; +import java.util.IdentityHashMap; public class LoopNode extends RegionNode { - public LoopNode( Parser.Lexer loc, Node entry ) { super(loc,null,entry,null); } + public LoopNode( Parser.Lexer loc, Node xctrl, Node entry ) { super(loc,xctrl,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; @@ -48,7 +47,7 @@ public class LoopNode extends RegionNode { x = x.idom(); } // Found a no-exit loop. Insert an exit - NeverNode iff = (NeverNode)new NeverNode(back()).peephole(); // Ideal never-branch + NeverNode iff = (NeverNode)new NeverNode(1.2f,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 @@ -58,12 +57,14 @@ 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 = new ConstantNode(Type.TOP).peephole(); + Node top = ConstantNode.make(Type.TOP).peephole(); Node memout = new MemMergeNode(false); memout.addDef(f); // placeholder for control + memout.addDef(top); for( Node u : _outputs ) if( u instanceof PhiNode phi && phi._type.isa(TypeMem.BOT) ) memout.addDef(phi); + memout.init(); Node ctrl = ret.ctrl(), mem = ret.mem(), expr = ret.expr(); if( ctrl!=null && ctrl._type != Type.XCONTROL ) { @@ -74,7 +75,7 @@ public class LoopNode extends RegionNode { // Nope, insert an aligned exit layer RegionNode r = new RegionNode(_loc,null,ctrl).init(); ctrl = r; r._ltree = stop._ltree; - mem = new PhiNode(r,mem ).init(); + mem = new BulkMemPhiNode(r,mem ).init(); expr = new PhiNode(r,expr).init(); } // Append new Never exit @@ -89,8 +90,12 @@ public class LoopNode extends RegionNode { ret.setDef(0,ctrl.unkeep()); ret.setDef(1,mem .unkeep()); ret.setDef(2,expr.unkeep()); - ret._type = null; // This may LIFT the Return type, from [XCtrl,mem,expr] to [Ctrl,mem,expr] - ret.setType(ret.compute()); + // 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); return stop; } diff --git a/src/main/java/com/seaofnodes/simple/node/MachNode.java b/src/main/java/com/seaofnodes/simple/node/MachNode.java index c0b3af66..025b5dc4 100644 --- a/src/main/java/com/seaofnodes/simple/node/MachNode.java +++ b/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/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java index 9ab57714..a88748b5 100644 --- a/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java +++ b/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java @@ -2,7 +2,10 @@ package com.seaofnodes.simple.node; 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.*; /** @@ -12,18 +15,21 @@ import java.util.*; public class MemMergeNode extends Node { /* - * In-Progress means this is being used by the Parser to track memory - * aliases. No optimizations are allowed. When no longer "in progress" - * normal peeps work. + * In-Progress is the mutable construction form. No optimizations are + * allowed. If not "in progress" then normal peeps work. */ public final boolean _inProgress; public MemMergeNode( boolean inProgress) { _type = TypeMem.BOT; _inProgress = inProgress; } + public MemMergeNode( boolean inProgress, Node ...nodes) { super(nodes); _type = TypeMem.BOT; _inProgress = inProgress; } public MemMergeNode(MemMergeNode mem) { super(mem); _inProgress = false; } + @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(false); mem.setDefX(bais.packed1()-1,null); return mem; } - // If being used by a Scope, this is "in progress" from the Parser. - // Otherwise, it's a memory merge + // The mutable construction form is "in progress"; otherwise this is a + // graph-semantic memory merge. boolean inProgress() { return _inProgress; } @Override public String label() { return "ALLMEM"; } @@ -61,29 +67,82 @@ public class MemMergeNode extends Node { @Override public Type compute() { - for( Node n : _inputs ) - if( n != null && !n._type.isHigh() ) - return TypeMem.BOT; - return TypeMem.TOP; + 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); + while( true ) { + if( in !=null && in._type.isHigh() ) + return TypeMem.TOP; + if( in instanceof MemMergeNode mem ) { + in = mem.in(i); + } else break; + } + 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 && 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); + } + } + return TypeMem.makePrivate(ts); + + } else { + // Default mixed mem is just BOT + TypeMem mem = defmem; + boolean progress = true; + while( progress ) { + progress = false; + for( int i=2; i<nIns(); i++ ) { + // Has this alias escaped already? + if( XInt.bit(mem._escAs,i) ) { + Node n = in(i); + if( n != null && !n._type.isHigh() ) { + TypeMem mem2 = (TypeMem)mem.meet(n._type); + // More escape aliases + if( mem2 != mem ) + { mem = mem2; progress = true; } + } + } + } + } + return mem; + } } @Override public Node idealize() { if( inProgress() ) return null; + if( nIns()==0 ) return null; // Fold defaults into the default boolean progress=false, allDefault=true; for( int i=2; i<nIns(); i++ ) { - if( in(i) instanceof CastNode cast ) + if( in(i) instanceof CheckCastNode cast ) { setDef(i,cast.in(1)); progress=true; } - if( in(1) == in(i) ) { setDef(i,null); progress=true; } - else { allDefault=false; } + if( in(1) == in(i) && in(1) != null ) { setDef(i,null); progress=true; } + else { allDefault=false; } } // If not merging any memory (all memory is just the default) if( allDefault ) return in(1); // Become default memory - // Collapse stacked all-mem + // Collapse stacked merged-mem if( in(1) instanceof MemMergeNode mem ) { // Goal is to swap my default mem with mem's default mem for( int i=2; i<mem.nIns(); i++ ) { @@ -93,6 +152,7 @@ public class MemMergeNode extends Node { setDefX(i,mem.in(i)); } } + CodeGen.CODE.add(mem.in(1)); setDef(1,mem.in(1)); return this; } @@ -104,13 +164,12 @@ public class MemMergeNode extends Node { 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); } - Node alias( int alias, Node st ) { - while( alias >= nIns() ) addDef(null); - return setDef(alias,st); - } + public Node alias( int alias, Node st ) { return setDefX(alias,st); } // Read or update from memory. // A shared implementation allows us to create lazy phis both during @@ -119,25 +178,19 @@ public class MemMergeNode extends Node { // Memory projections are made lazily; if one does not exist // then it must be START.proj(1) Node old = alias(alias); - if( old instanceof ScopeNode loop ) { - MemMergeNode loopmem = loop.mem(); - Node memdef = loopmem.alias(alias); - // Lazy phi! - old = memdef instanceof PhiNode phi && loop.ctrl()==phi.region() - // Loop already has a real Phi, use it - ? memdef - // 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. - : loopmem.alias(alias, new PhiNode(Parser.memName(alias), TypeMem.make(alias,Type.BOTTOM),loop.ctrl(),loopmem._mem(alias,null),null).peephole() ); - alias(alias,old); - } + assert !(old instanceof ScopeNode); // Parser lazy memory is handled by ScopeNode // Memory projections are made lazily; expand as needed return st==null ? old : alias(alias,st); // Not lazy, so this is the answer } + // Remove lazy Phis on all aliases + public void removeLazyAll() { + for( int i = 1; i < nIns(); i++) + _mem(i,null); + } - void _merge( MemMergeNode that, RegionNode r) { + public void _merge( MemMergeNode that, RegionNode r) { + assert _inProgress; int len = Math.max(nIns(),that.nIns()); for( int i = 1; i < len; i++) if( alias(i) != that.alias(i) ) { // No need for redundant Phis @@ -145,13 +198,33 @@ public class MemMergeNode extends Node { // by alias as it will trigger a phi creation Node lhs = this._mem(i,null); Node rhs = that._mem(i,null); - alias(i, new PhiNode(Parser.memName(i), lhs._type.meet(rhs._type).glb(true), r, lhs, rhs).peephole()); + alias(i, new MemPhiNode(Parser.memName(i), i, r, lhs, rhs).peephole()); } } - // Fill in the backedge of any inserted Phis + // Fill in the backedge of any inserted Phis. Keep precise aliases for + // what is known at the loop head. For aliases discovered in the loop + // body, gather them into a actual (not inProgress) MemMerge and merge into + // the default alias. void _endLoopMem( ScopeNode scope, MemMergeNode back, MemMergeNode exit ) { + assert _inProgress && back._inProgress && exit._inProgress; Node exit_def = exit.alias(1); + MemMergeNode more = null; // Dont often need the "more discovered" merge, make it lazy + int len = Math.max(nIns(),back.nIns()); + for( int i=2; i<len; i++ ) { + if( !(i < this.nIns() && this.in(i)!=null) && // Alias is not known to loop head + (i < back.nIns() && back.in(i)!=null) ) { // Alias IS known to loop back + // Gather + if( more==null ) { + more = new MemMergeNode(false); + more.alias(1,back.in(1)); + back.alias(1,more); + } + more.alias(i,back.in(i)); + back.setDef(i,null); + } + } + for( int i=1; i<nIns(); i++ ) { if( in(i) instanceof PhiNode phi && phi.region()==scope.ctrl() ) { assert phi.in(2)==null; @@ -164,7 +237,7 @@ public class MemMergeNode extends Node { // Now one-time do a useless-phi removal void _useless( ) { - for( int i=2; i<nIns(); i++ ) { + for( int i=1; i<nIns(); i++ ) { if( in(i) instanceof PhiNode phi ) { // Do an eager useless-phi removal Node in = phi.peephole(); diff --git a/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/src/main/java/com/seaofnodes/simple/node/MemOpNode.java index ead82322..d824bc3a 100644 --- a/src/main/java/com/seaofnodes/simple/node/MemOpNode.java +++ b/src/main/java/com/seaofnodes/simple/node/MemOpNode.java @@ -1,11 +1,15 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeMemPtr; +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.lang.StringBuilder; import java.util.BitSet; +import java.util.HashMap; +import java.util.IdentityHashMap; /** * Convenience common base for Load and Store. @@ -16,84 +20,95 @@ import java.util.BitSet; * 3 - Offset, integer types * 4 - Value, for Stores only */ -public abstract class MemOpNode extends Node { +public abstract class MemOpNode extends TypeNode { // The equivalence alias class - public final int _alias; + 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-dependences on prior Stores, and never cause an + // Loads might pick up anti-dependencies 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; - // 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 + // Field name. 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 glb, Node mem, Node ptr, Node off) { - super(null, mem, ptr, off); + public MemOpNode(Parser.Lexer loc, String name, int alias, boolean isLoad, Type glb, Node ctrl, Node mem, Node ptr, Node off) { + super(glb, ctrl, 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 glb, Node mem, Node ptr, Node off, Node value) { - this(loc,name, alias, isLoad, glb, mem, ptr, off); + public MemOpNode(Parser.Lexer loc, String name, int alias, boolean isLoad, Type glb, Node ctrl, Node mem, Node ptr, Node off, Node value) { + this(loc, name, alias, isLoad, glb, ctrl, mem, ptr, off); addDef(value); } - 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"); + 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); } - // 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; + @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 } - static String mlabel(String name) { return "[]".equals(name) ? "ary" : ("#".equals(name) ? "len" : name); } + static String mlabel(String name) { return "[]"==name ? "ary" : ("#"==name ? "len" : name); } String mlabel() { return mlabel(_name); } 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 public StringBuilder _print1( StringBuilder sb, BitSet visited ) { return _printMach(sb,visited); } public StringBuilder _printMach( StringBuilder sb, BitSet visited ) { throw Utils.TODO(); } - public int log_size() { return _declaredType.log_size(); } + public int log_size() { return _con.log_size(); } @Override public boolean eq(Node n) { MemOpNode mem = (MemOpNode)n; // Invariant - return _alias==mem._alias; // When comparing types error to use "equals"; always use "==" + return _alias==mem._alias && super.eq(mem); } @Override - int hash() { return _alias; } + int hash() { return _alias ^ super.hash(); } @Override public Parser.ParseException err() { @@ -102,8 +117,15 @@ public abstract class MemOpNode extends Node { 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 null; - return Parser.error( "Might be null accessing '" + _name + "'",_loc); + 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.Opto.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.Opto.ordinal() ) + return Parser.error("Accessing unknown field '"+_name+"' from '*"+tmp._obj._name+"'",_loc); + return null; + } } diff --git a/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java new file mode 100644 index 00000000..e8e2592c --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java @@ -0,0 +1,72 @@ +package com.seaofnodes.simple.node; + +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. */ +public class MemPhiNode extends PhiNode { + + public final int _alias; + + public MemPhiNode(String label, int alias, Node... 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); + } + + 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 Type compute() { + Type t = super.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); + } + + @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++ ) + if( in(i) instanceof MemMergeNode mem ) { + setDef(i,mem.alias(_alias)); + return this; + } + return super.idealize(); + } + + @Override public boolean eq(Node n) { + return _alias==((MemPhiNode)n)._alias && super.eq(n); + } + + @Override int hash() { return _alias; } +} diff --git a/src/main/java/com/seaofnodes/simple/node/MinusFNode.java b/src/main/java/com/seaofnodes/simple/node/MinusFNode.java deleted file mode 100644 index 5bb0889f..00000000 --- a/src/main/java/com/seaofnodes/simple/node/MinusFNode.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; - -import java.util.BitSet; - -public class MinusFNode extends Node { - public MinusFNode(Node in) { super(null, in); } - - @Override public String label() { return "MinusF"; } - - @Override public String glabel() { return "-"; } - - @Override - public StringBuilder _print1(StringBuilder sb, BitSet visited) { - in(1)._print0(sb.append("(-"), visited); - return sb.append(")"); - } - - @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/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/src/main/java/com/seaofnodes/simple/node/MinusNode.java index 3ab74625..6101fba2 100644 --- a/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -1,16 +1,32 @@ package com.seaofnodes.simple.node; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; +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; +import java.util.IdentityHashMap; -public class MinusNode extends Node { - public MinusNode(Node in) { super(null, in); } +public class MinusNode extends Node implements ModeNode { + // Mode is 0 for unknown, 1 for int, 2 for flt + byte _mode; - @Override public String label() { return "Minus"; } + 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 String glabel() { return "-"; } + @Override public byte mode() { return _mode; } + @Override public Node setMode(byte mode) { + assert _mode==0 && (mode==1 || mode==2); + unlock(); // _mode participates in GVN hash and equality + _mode = mode; + return this; + } @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { @@ -20,11 +36,22 @@ public class MinusNode extends Node { @Override public Type compute() { - if( in(1)._type.isHigh() ) return TypeInteger.TOP; - if( in(1)._type instanceof TypeInteger i0 && - i0._min != Long.MIN_VALUE && i0._max != Long.MIN_VALUE ) - return TypeInteger.make(-i0._max,-i0._min,i0._widen); - return TypeInteger.BOT; + Type t = in(1)._type; + if( t.isHigh() ) + return _mode==0 ? Type.TOP : + _mode==1 ? TypeInteger.TOP : + t instanceof TypeFloat ? t : TypeFloat.F64.dual(); + if( _mode==0 ) + return Type.BOTTOM; + if( _mode==1 ) { + if( t instanceof TypeInteger i0 && + i0._min != Long.MIN_VALUE && i0._max != Long.MIN_VALUE ) + return TypeInteger.make(-i0._max,-i0._min,i0._widen); + return TypeInteger.BOT; + } + if( t instanceof TypeFloat f ) + return f.isConstant() ? TypeFloat.constant(-f.value()) : f; + return TypeFloat.F64; } @Override @@ -33,7 +60,19 @@ public class MinusNode extends Node { if( in(1) instanceof MinusNode minus ) return minus.in(1); + // Can we decide int vs flt? + if( _mode==0 ) { + byte mode = (byte)( + in(1)._type instanceof TypeInteger ? 1 : + in(1)._type instanceof TypeFloat ? 2 : 0); + if( mode!=0 ) return setMode(mode).init(); + } + return null; } - @Override Node copyF() { return new MinusFNode(null); } + @Override public Parser.ParseException err() { + return ArithNode.nodeErr(this,null,"-",_mode,true); + } + @Override public boolean eq( Node n ) { return _mode==((MinusNode)n)._mode; } + @Override public int hash() { return _mode; } } diff --git a/src/main/java/com/seaofnodes/simple/node/ModeNode.java b/src/main/java/com/seaofnodes/simple/node/ModeNode.java new file mode 100644 index 00000000..dc635447 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/ModeNode.java @@ -0,0 +1,12 @@ +package com.seaofnodes.simple.node; + +/** + * A node whose numeric input family is selected once: unknown, integer, or + * floating point. Implementors remain ordinary Nodes; this interface only + * centralizes the structural mode contract shared by arithmetic, comparisons, + * and unary minus. + */ +public interface ModeNode { + byte mode(); + Node setMode(byte mode); +} diff --git a/src/main/java/com/seaofnodes/simple/node/MulFNode.java b/src/main/java/com/seaofnodes/simple/node/MulFNode.java deleted file mode 100644 index 5c04af43..00000000 --- a/src/main/java/com/seaofnodes/simple/node/MulFNode.java +++ /dev/null @@ -1,36 +0,0 @@ -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); } - - @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/src/main/java/com/seaofnodes/simple/node/MulNode.java b/src/main/java/com/seaofnodes/simple/node/MulNode.java index debc2c56..e773ed57 100644 --- a/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -1,17 +1,20 @@ 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 java.util.BitSet; 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 label() { return "Mul"; } @Override public String op() { return "*"; } - @Override long doOp( long x, long y ) { return x * y; } + @Override long doOp( long x, long y ) { return x * y; } + @Override double doOp(double x, double y ) { return x * y; } @Override TypeInteger doOp(TypeInteger x, TypeInteger y) { return TypeInteger.BOT; } @@ -33,7 +36,7 @@ public class MulNode extends ArithNode { // canonicalize to (x*1) long c = i.value(); if( c==1 ) return lhs; - if( c==0 ) return Parser.ZERO; + if( c==0 ) return CodeGen.CODE.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. @@ -58,6 +61,5 @@ public class MulNode extends ArithNode { return super.idealize(); } - @Override Node copy(Node lhs, Node rhs) { return new MulNode(lhs,rhs); } - @Override Node copyF() { return new MulFNode(null,null); } + @Override Node copy(Node lhs, Node rhs) { return new MulNode(lhs,rhs,_mode); } } diff --git a/src/main/java/com/seaofnodes/simple/node/NeverNode.java b/src/main/java/com/seaofnodes/simple/node/NeverNode.java index 516f218b..e2c279d2 100644 --- a/src/main/java/com/seaofnodes/simple/node/NeverNode.java +++ b/src/main/java/com/seaofnodes/simple/node/NeverNode.java @@ -10,7 +10,8 @@ import java.util.BitSet; // "Never true" for infinite loop exits public class NeverNode extends IfNode { public NeverNode(NeverNode ctrl) { super(ctrl); } - public NeverNode(Node ctrl) { super(ctrl,null); } + public NeverNode(float ignore, Node ctrl) { super(ctrl,null); } + @Override public Tag serialTag() { return Tag.Never; } @Override public String label() { return "Never"; } diff --git a/src/main/java/com/seaofnodes/simple/node/NewNode.java b/src/main/java/com/seaofnodes/simple/node/NewNode.java index 9310aa38..26a9c0fd 100644 --- a/src/main/java/com/seaofnodes/simple/node/NewNode.java +++ b/src/main/java/com/seaofnodes/simple/node/NewNode.java @@ -2,107 +2,70 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.BAOS; import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; import java.util.BitSet; +import java.util.HashMap; +import java.util.IdentityHashMap; /** - * Allocation! Allocate a chunk of memory, and pre-zero it. - * The inputs include control and size, and ALL aliases being set. - * The output is large tuple, one for every alias plus the created pointer. - * New is expected to be followed by projections for every alias. - */ + * Allocation! Allocate a chunk of memory, no init. + * Takes in ctrl and size, and TypeStruct. + * Produces a ptr and private memory. +*/ public class NewNode extends Node implements MultiNode { + public TypeStruct _ts; + public NewNode( TypeStruct ts, Node ctrl, Node size ) { super(ctrl,size); _ts = ts; } - public final TypeMemPtr _ptr; - public final int _len; + public Node ctrl() { return in(0); } + public Node size() { return in(1); } - public NewNode(TypeMemPtr ptr, Node... nodes) { - super(nodes); - assert !ptr.nullable(); - _ptr = ptr; - _len = ptr._obj._fields.length; - // Control in slot 0 - assert nodes[0]._type==Type.CONTROL || nodes[0]._type == Type.XCONTROL; - // Malloc-length in slot 1 - assert nodes[1]._type instanceof TypeInteger || nodes[1]._type==Type.NIL; - for( int i=0; i<_len; i++ ) - assert ptr._obj._fields[i]._one || nodes[2 + i]._type.isa( TypeMem.BOT ); + // --- 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(NewNode nnn) { super(nnn); _ptr = nnn._ptr; _len = nnn._len; } - - public Node mem (int idx) { return in(idx+2); } - - @Override public String label() { return "new_"+glabel(); } @Override public String glabel() { - return _ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._t.str() : _ptr._obj.str(); + return _ts.isAry() ? "ary_"+_ts._fields[1]._t.str() : _ts.str(); } + @Override public String label() { return "new_"+glabel(); } - @Override - public StringBuilder _print1(StringBuilder sb, BitSet visited) { + @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { sb.append("new "); - return sb.append(_ptr._obj.str()); + return sb.append(_ts.str()); } - int findAlias(int alias) { - int idx = _ptr._obj.findAlias(alias); - assert idx!= -1; // Error, caller should be calling - return idx+2; // Skip ctrl, size + @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)); } - // 0 - ctrl - // 1 - byte size - // 2-len+2 - aliases, one per field - // len+2 - 2*len+2 - initial values, one per field - public Node size() { return in(1); } + @Override public Node idealize() { return null; } - @Override - public TypeTuple compute() { - Field[] fs = _ptr._obj._fields; - Type[] ts = new Type[fs.length+2]; - if( in(0)._type.isHigh() ) { - ts[0] = Type.XCONTROL; - ts[1] = _ptr.makeHigh(); - for( int i=0; i<fs.length; i++ ) - ts[i+2] = Type.TOP; - } else { - ts[0] = Type.CONTROL; - ts[1] = _ptr; - for( int i=0; i<fs.length; i++ ) { - if( _ptr._obj._fields[i]._one ) { - // Once-only fields use the declared type - ts[i+2] = _ptr._obj._fields[i]._t; - } else { - // Others take from the inputs - Type mt = in(i+2)._type; - TypeMem mem = mt==Type.TOP ? TypeMem.TOP : (TypeMem)mt; - //Type tfld = mem._t.meet(mem._t.makeZero()); - Type tfld = mem._t; - Type tfld2 = tfld.join(fs[i]._t ); - ts[i+2] = TypeMem.make(fs[i]._alias,tfld2); - } - } - } - return TypeTuple.make(ts); + @Override boolean _upgradeType( HashMap<String,Type> TYPES) { + Type old = _ts; _ts = (TypeStruct)_ts.upgradeType(TYPES); return old != _ts; } - @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; - } + // 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 boolean eq(Node n) { return this == n; } - - @Override - int hash() { return _ptr.hashCode(); } + @Override int hash() { return _ts.hashCode(); } // ------------ // MachNode specifics, shared across all CPUs @@ -111,19 +74,19 @@ 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).firstReg(); - _arg3Mask = code._mach.callArgMask(TypeFunPtr.CALLOC,3,0); + _arg2Reg = code._mach.callArgMask(TypeFunPtr.CALLOC,2,0,code._cCallingConv).firstReg(); + _arg3Mask = code._mach.callArgMask(TypeFunPtr.CALLOC,3,0,code._cCallingConv); // 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._callerSave.copy(); + RegMaskRW kills = code._cCallerSave.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); + int xslot = code._mach.maxArgSlot(TypeFunPtr.CALLOC,code._cCallingConv); _xslot = (maxReg+fslot)+xslot; for( int i=0; i<xslot; i++ ) kills.set((maxReg+fslot)+i); @@ -132,9 +95,10 @@ public class NewNode extends Node implements MultiNode { public String op() { return "alloc"; } public RegMask regmap(int i) { return i==1 ? _arg3Mask : null; } public RegMask outregmap() { return null; } - public RegMask outregmap(int idx) { return idx==1 ? _retMask : 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("#calloc, ").p(code.reg(size())); + sb.p("#malloc, ").p(code.reg(size())); } + } diff --git a/src/main/java/com/seaofnodes/simple/node/Node.java b/src/main/java/com/seaofnodes/simple/node/Node.java index 458a3407..1ce8382a 100644 --- a/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/src/main/java/com/seaofnodes/simple/node/Node.java @@ -2,14 +2,12 @@ package com.seaofnodes.simple.node; 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.print.JSViewer; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeFloat; -import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.*; import java.util.*; import java.util.function.Function; import static com.seaofnodes.simple.codegen.CodeGen.CODE; @@ -78,8 +76,83 @@ public abstract class Node implements Cloneable { _hash = 0; } + // Disk/serialized opcode tags + public enum Tag { + Add,And,EQ,NE,LT,LE,ULT, + CallEnd,Call,CheckCast,ConFldOff,Con,CProj, + Div,Escape,Extern,Fun,FunPtr,If,Load,Loop, + MemMerge,Minus,Mul, + New,Never,Not,Or,Parm,Phi,Proj, + ReadOnly,Return,Region,RoundF32, + Sar,Shl,Shr,Start,Stop,Store,Struct,Sub,ToFloat, + XCtrl,Xor,StartCU,StopCU,Guard,MemPhi,BulkMemPhi,PtrToInt; + 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 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 XCtrl -> new XCtrlNode(); + case Xor -> { int mode=bais.packed1(); assert mode==1; yield new XorNode(null,null,null); } + case Guard -> GuardNode.make(bais); + + 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); + case CallEnd-> CallEndNode.make(bais ,types); + case CheckCast -> CheckCastNode.make(bais,types); + 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 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 Struct-> StructNode.make(bais, types); + case Region-> RegionNode.make(bais); + + default -> throw Utils.TODO(); + }; + } + }; + 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 abstract String label(); + public String label() { return serialTag().toString(); } // Unique label for graph visualization, e.g. "Add12" or "Region30" or "EQ99" public String uniqueName() { @@ -115,7 +188,7 @@ public abstract class Node implements Cloneable { // This is the common print: check for repeats, check for DEAD and print // "DEAD" else call the per-Node print1. public final StringBuilder _print0(StringBuilder sb, BitSet visited) { - if (visited.get(_nid) && !(this instanceof ConstantNode) ) + if( visited.get(_nid) && !(this instanceof ConstantNode) ) return sb.append(label()); visited.set(_nid); return isDead() @@ -229,7 +302,8 @@ public abstract class Node implements Cloneable { } // Breaks the edge invariants, used temporarily - protected <N extends Node> void addUse(N n) { _outputs.add(n); } + @SuppressWarnings("unchecked") + protected <N extends Node> N addUse(Node n) { _outputs.add(n); return (N)this; } // 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. @@ -250,6 +324,12 @@ public abstract class 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> @@ -284,11 +364,13 @@ public abstract class Node implements Cloneable { // Shortcuts to stop DCE mid-parse // Add bogus null use to keep node alive - @SuppressWarnings("unchecked") - public <N extends Node> N keep() { addUse(null); return (N)this; } + public <N extends Node> N keep() { return addUse(null); } // 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() { @@ -415,7 +497,15 @@ public abstract class Node implements Cloneable { // 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() ) + if( !isConst() && !(this instanceof ParmNode parm && parm.inProgress()) && + // A high Load through a bad pointer still owns the source error; + // do not replace it with an unlocated constant before typeCheck. + !(this instanceof LoadNode && err()!=null) && + // A CallEnd can be temporarily high while optimistic call-graph + // linking is still discovering its target Returns. Its result + // projections must remain attached so they can sharpen later. + !(this instanceof ProjNode proj && proj.in(0) instanceof CallEndNode && _type.isHigh()) && + _type.isHighOrConst() ) return ConstantNode.make(_type).peephole(); // Global Value Numbering @@ -483,7 +573,8 @@ public abstract class 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"; + 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 if( old == type ) return old; _type = type; // Set _type late for easier assert debugging CODE.addAll(_outputs); @@ -549,19 +640,30 @@ public abstract class Node implements Cloneable { // revisit them if `this` changes. Ary<Node> _deps; + private boolean addDepImpl( Node dep ) { + // Running peepholes during the big assert cannot have side effects + // like adding dependencies. + if( CODE._midAssert ) return false; + 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; + } + /** * 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. */ - <N extends Node> N addDep( N dep ) { - // Running peepholes during the big assert cannot have side effects - // like adding dependencies. - if( CODE._midAssert ) return dep; - if( dep._deps==null ) dep._deps = new Ary<>(Node.class); - if( dep._deps .find(this) != -1 ) return dep; // Already on list + 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 - if( dep._outputs.find(this) != -1 ) return dep; + dep._deps.add(this); + return dep; + } + + public <N extends Node> N addDepForwards( N dep ) { + if( !addDepImpl(dep) ) return dep; dep._deps.add(this); return dep; } @@ -574,6 +676,7 @@ public abstract class Node implements Cloneable { _deps.clear(); } + // Two nodes are equal if they have the same inputs and the same "opcode" // which means the same Java class, plus same internal parts. @Override public final boolean equals( Object o ) { @@ -630,27 +733,6 @@ public abstract class Node implements Cloneable { /** Pinned in the schedule; these are data nodes whose input#0 is not allowed to change */ public boolean isPinned() { 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 @@ -699,7 +781,7 @@ public abstract class Node implements Cloneable { public Parser.ParseException err() { return null; } // Common integer constants - public static ConstantNode con(long x) { return (ConstantNode)(new ConstantNode(TypeInteger.constant(x)).peephole()); } + public static ConstantNode con(long x) { return (ConstantNode)ConstantNode.make(TypeInteger.constant(x)).peephole(); } // Utility to walk the entire graph applying a function; return the first // not-null result. @@ -720,6 +802,20 @@ public abstract class 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 */ diff --git a/src/main/java/com/seaofnodes/simple/node/NotNode.java b/src/main/java/com/seaofnodes/simple/node/NotNode.java index 4b80e61c..22b5bffe 100644 --- a/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ b/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -6,8 +6,7 @@ import java.util.BitSet; public class NotNode extends Node { public NotNode(Node in) { super(null, in); } - - @Override public String label() { return "Not"; } + @Override public Tag serialTag() { return Tag.Not; } @Override public String glabel() { return "!"; } diff --git a/src/main/java/com/seaofnodes/simple/node/OrNode.java b/src/main/java/com/seaofnodes/simple/node/OrNode.java index ac0d8454..ba250c26 100644 --- a/src/main/java/com/seaofnodes/simple/node/OrNode.java +++ b/src/main/java/com/seaofnodes/simple/node/OrNode.java @@ -5,7 +5,8 @@ import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; public class OrNode extends ArithNode { - public OrNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); } + 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/src/main/java/com/seaofnodes/simple/node/ParmNode.java b/src/main/java/com/seaofnodes/simple/node/ParmNode.java index 880f936c..6488b827 100644 --- a/src/main/java/com/seaofnodes/simple/node/ParmNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ParmNode.java @@ -1,30 +1,59 @@ package com.seaofnodes.simple.node; -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeMemPtr; +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.BAOS; + import java.util.BitSet; +import java.util.HashMap; +import java.util.IdentityHashMap; public class ParmNode extends PhiNode { // Argument indices are mapped one-to-one on CallNode inputs public final int _idx; // Argument index + public Type _declaredType; public ParmNode(String label, int idx, Type declaredType, Node... inputs) { - super(label,declaredType,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(nIns()); // Number of 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._minType ); _idx = parm._idx; } @Override public String label() { return MemOpNode.mlabel(_label); } @Override public String glabel() { return _label; } + @Override protected Type declaredType() { return _declaredType; } + @Override protected Type constrain(Type t) { return t.join(_declaredType); } + + @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 public StringBuilder _print1(StringBuilder sb, BitSet visited) { - if( "main".equals(fun()._name) && _label.equals("arg") ) + if( fun()._name!=null && fun()._name.endsWith("<clinit>") && _label.equals("arg") ) return sb.append("arg"); sb.append("Parm_").append(_label).append("("); for( Node in : _inputs ) { @@ -48,14 +77,6 @@ public class ParmNode extends PhiNode { // If function is folding, do all possible peeps if( fun()._folding ) return super.idealize(); - // Can upgrade minType even while in-progress - if( _minType instanceof TypeMemPtr tmp && _minType.isFRef() ) { - TypeMemPtr tmp2 = (TypeMemPtr) Parser.TYPES.get(tmp._obj._name); - if( tmp2!=null && tmp2 != _minType ) { - _minType = tmp2; - return this; - } - } // Skip most phi optimizations on parms return null; } diff --git a/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/src/main/java/com/seaofnodes/simple/node/PhiNode.java index bdc0d0fa..2c57eb8b 100644 --- a/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -1,39 +1,51 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.*; -import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.Serialize; import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.Utils; +import com.seaofnodes.simple.util.BAOS; +import com.seaofnodes.simple.util.SB; + import java.util.BitSet; +import java.util.HashMap; +import java.util.IdentityHashMap; public class PhiNode extends Node { public final String _label; - // The Phi type we compute must stay within the domain of the Phi. Example - // Int stays Int, Ptr stays Ptr, Control stays Control, Mem stays Mem. - Type _minType; - - int lattice_drop; - - public PhiNode(String label, Type minType, Node... inputs) { + public PhiNode( String label, Node... inputs) { super(inputs); _label = label; - assert minType!=null; - _minType = minType; + } + 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, Type minType) { super(phi); _label = label; _type = _minType = minType; } + public PhiNode(PhiNode phi, String label) { super(phi); _label = label; } // Used by instruction Selection - public PhiNode(PhiNode phi) { this(phi,phi._label,phi._minType ); } + public PhiNode(PhiNode phi) { this(phi,phi._label); } // Used by the infinite-loop exit breaker public PhiNode(RegionNode r, Node sample) { super(new Node[]{r}); _label = ""; - _minType = sample._type; + _type = sample._type; 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); + } @Override public String label() { return "Phi_"+MemOpNode.mlabel(_label); } @@ -55,7 +67,7 @@ public class PhiNode extends Node { } public CFGNode region() { return (CFGNode)in(0); } - @Override public boolean isMem() { return _minType instanceof TypeMem; } + @Override public boolean isMem() { return _type instanceof TypeMem; } @Override public boolean isPinned() { return true; } boolean isRPC() { return false; } @@ -65,14 +77,10 @@ public class PhiNode extends Node { 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() ) - // Loop-Phis must lift to the declared type, because that is how - // the Parser keeps precise types until the loop finishes parsing. - // Similar, ParmNodes use precise minType until all calls are - // linked (post opto). - return r instanceof LoopNode || (this instanceof ParmNode) ? _minType : Type.BOTTOM; + return declaredType(); // 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; @@ -82,7 +90,8 @@ public class PhiNode extends Node { t = t.meet(in(i)._type); } } - Type newt = t.join( _minType ); + Type declared = declaredType(); + Type newt = constrain(t); // phi loop widening part if( region() instanceof LoopNode && // Only around loops @@ -90,60 +99,45 @@ public class PhiNode extends Node { // Types changed and are falling (the optimistic case, expected to fall forever) newi != _type ) { if( !newi.isConstant() && (!(_type instanceof TypeInteger oldi) || newi._widen <= oldi._widen) ) - return newi.same_but_slightly_wider_than(_minType); + return newi.same_but_slightly_wider_than( + declared instanceof TypeInteger ? declared : TypeInteger.BOT); } return newt; } + // Ordinary Phis have no parser-supplied type constraint. + protected Type declaredType() { return Type.BOTTOM; } + protected Type constrain(Type t) { return t; } + @Override public Node idealize() { if( !(region() instanceof RegionNode r ) ) return in(1); // Input has collapse to e.g. starting control. - // Can upgrade minType even while in-progress - if( _minType instanceof TypeMemPtr tmp && _minType.isFRef() ) { - TypeMemPtr tmp2 = (TypeMemPtr) Parser.TYPES.get(tmp._obj._name); - if( tmp2!=null && tmp2 != _minType ) { - _minType = tmp2; - return this; - } - } - if( r.inProgress() || r.nIns()<=1 ) + if( r.inProgress() || r.nIns()<=1 || nOuts()==0 ) return null; // Input is in-progress // If we have only a single unique input, become it. Node live = singleUniqueInput(); - if( live != null ) { - if( live._type.isa(_type) ) - return live; - // Keep the Phi upcast - return new CastNode(_type,null,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; - // Simple Phi-after-MemMerge to a known alias can bypass. Happens when inlining. - if( _type instanceof TypeMem tmem && tmem._alias!=1 ) { - for( int i=1; i<nIns(); i++ ) - if( in(i) instanceof MemMergeNode mem ) { - setDef(i,mem.alias(tmem._alias)); - return this; - } - } - // Generic "pull down op" Node progress; if( same_op() && (progress = drop_same_op()) != null ) return progress; - // If merging Phi(N, cast(N)) - we are losing the cast JOIN effects, so just remove. + // If merging Phi(ZERO, guardNZ(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( in(1) instanceof GuardNode cast && cast._nonZero && in(2)._type.makeZero()==in(2)._type && cast.in(1)!=this ) return cast.in(1); + if( in(2) instanceof GuardNode cast && cast._nonZero && in(1)._type.makeZero()==in(1)._type && cast.in(1)!=this ) return cast.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. @@ -153,12 +147,12 @@ public class PhiNode extends Node { if( in(2)._type == in(2)._type.makeZero() ) nullx = 2; if( nullx != -1 ) { Node val = in(3-nullx); - if( val instanceof CastNode cast ) + if( val instanceof GuardNode cast && cast._nonZero ) 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); + CFGNode idom = r.cfg(nullx); while( idom != null && idom.nIns() > 0 && idom.in(0) != iff ) idom = idom.idom(); if( idom instanceof CProjNode proj && proj._idx==1 ) return val; @@ -172,17 +166,37 @@ public class PhiNode extends Node { // 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 busy=null; for( int i=1; i<nIns(); i++ ) { Node op = in(i); + if( op.nOuts()==0 ) + { op.kill(); return false; } if( in(1).getClass() != op.getClass() || op.in(0)!=null || in(1).nIns() != op.nIns() ) return false; // Wrong class or CFG bound or mismatched inputs - if( in(1) instanceof MemOpNode mem && mem._alias != ((MemOpNode)op)._alias ) - return false; - if( op.nOuts() > 1 ) { // Too many users, but addDep in case lose users - for( Node out : op._outputs ) - if( out!=null && out!=this ) - addDep(out); + if( in(1) instanceof MemOpNode mem ) { + // Mismatched aliases + if( mem._alias != ((MemOpNode)op)._alias ) return false; + // Load is clobbered somewhere, and can not be pulled forward past the Phi? + if( mem instanceof LoadNode ) + for( Node use : op.outs() ) + if( use instanceof StoreNode ) + return false; + } + if( in(1) instanceof EscapeNode ) return false; + if( in(1) instanceof MemMergeNode ) + return false; // Have to keep aliases straight + if( op.nOuts() > 1 ) { + if( busy==null ) busy = op; + else { // Too many users, but addDep in case lose users + for( Node out : op._outputs ) + if( out!=null && out!=this ) + addDep(out); + for( Node out : busy._outputs ) + if( out!=null && out!=this ) + addDep(out); + return false; + } } for( int j=1; j<in(1).nIns(); j++ ) if( op.in(j) instanceof ScopeNode || (op.in(j)==null ^ in(1).in(j)==null) ) @@ -205,7 +219,7 @@ public class PhiNode extends Node { if( in(i).in(j) != x ) { needsPhi=true; break; } if( needsPhi ) { - x = new PhiNode(_label,op.in(j)._type.glb(false)); + x = make(_label,op.in(j)._type.glb(false)); x.addDef(region()); for( int i=1; i<nIns(); i++ ) x.addDef(in(i).in(j)); @@ -253,39 +267,28 @@ public class PhiNode extends Node { } // Never equal if inProgress. - // Also, joins @Override public boolean eq( Node n ) { - if( inProgress() ) return false; - Type min = ((PhiNode)n)._minType; - if( _minType==min ) return true; - Type mt = min.meet(_minType); - if( min!=mt && _minType!=mt ) return false; - //// Theory says these 2 Phis CAN be merged/GVNd, but I need to pick the - //// most general minType. - //_minType = ((PhiNode)n)._minType = mt; - //return true; - return false; + return !inProgress() && super.eq(n); } @Override public Parser.ParseException err() { - if( _type != Type.BOTTOM ) return null; + if( _type != Type.BOTTOM && _type != TypeScalar.BOT ) return null; - // BOTTOM means we mixed e.g. int and ptr + // Scalar BOT means we mixed scalar families, e.g. int and ptr. + // Global BOTTOM retains the same role for non-scalar families. for( int i=1; i<nIns(); i++ ) // Already an error, but better error messages come from elsewhere - if( in(i)._type == Type.BOTTOM ) + if( in(i)._type == Type.BOTTOM || in(i)._type == TypeScalar.BOT ) return null; - // 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); + 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); } } diff --git a/src/main/java/com/seaofnodes/simple/node/Proj.java b/src/main/java/com/seaofnodes/simple/node/Proj.java new file mode 100644 index 00000000..c8b9378f --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/Proj.java @@ -0,0 +1,2 @@ +package com.seaofnodes.simple.node; +public interface Proj { int idx(); } diff --git a/src/main/java/com/seaofnodes/simple/node/ProjNode.java b/src/main/java/com/seaofnodes/simple/node/ProjNode.java index 586527c8..97b9ed45 100644 --- a/src/main/java/com/seaofnodes/simple/node/ProjNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ProjNode.java @@ -1,12 +1,15 @@ package com.seaofnodes.simple.node; +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.BitSet; +import java.util.HashMap; +import java.util.IdentityHashMap; -public class ProjNode extends Node { +public class ProjNode extends Node implements Proj { // Which slice of the incoming multipart value public final int _idx; @@ -20,6 +23,14 @@ public class ProjNode extends Node { _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; } @@ -51,4 +62,10 @@ public class ProjNode extends Node { @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/src/main/java/com/seaofnodes/simple/node/PtrToIntMach.java b/src/main/java/com/seaofnodes/simple/node/PtrToIntMach.java new file mode 100644 index 00000000..9a9b94fd --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/PtrToIntMach.java @@ -0,0 +1,17 @@ +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; + +/** Zero-code machine form of {@link PtrToIntNode}. */ +public class PtrToIntMach extends PtrToIntNode implements MachNode { + public PtrToIntMach(PtrToIntNode ptr) { super(ptr); } + @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/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java b/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java new file mode 100644 index 00000000..d781620b --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java @@ -0,0 +1,40 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.type.TypeMemPtr; + +import java.util.BitSet; + +/** + * Reinterpret a non-null managed pointer as its raw integer address. + * + * This node changes representation families but emits no code. In particular, + * it is not a {@link CheckCastNode}: no control-dependent proof can turn a + * pointer into an integer. Array-to-C-pointer conversion adds the array body + * offset separately. + */ +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 public String glabel() { return "(i64*)"; } + @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { + return in(1)._print0(sb.append("(i64*)"),visited); + } + + @Override public Type compute() { + return in(1)._type.isHigh() ? TypeInteger.TOP : TypeInteger.BOT; + } + + @Override public Node idealize() { return null; } + + @Override public Parser.ParseException err() { + Type t = in(1)._type; + if( t instanceof TypeMemPtr ptr ) + return ptr.notNull() ? null : Parser.error("Cannot convert a nullable pointer to an integer",null); + return Parser.error("Cannot convert "+t.str()+" to an integer pointer",null); + } +} diff --git a/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java b/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java index d8fe6286..c36be85a 100644 --- a/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java @@ -10,7 +10,7 @@ import java.util.BitSet; public class ReadOnlyNode extends Node { public ReadOnlyNode( Node n ) { super(null,n); } public ReadOnlyNode( ReadOnlyNode n ) { super(n); } - @Override public String label() { return "ReadOnly"; } + @Override public Tag serialTag() { return Tag.ReadOnly; } @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { diff --git a/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/src/main/java/com/seaofnodes/simple/node/RegionNode.java index b115a260..dc48f6f2 100644 --- a/src/main/java/com/seaofnodes/simple/node/RegionNode.java +++ b/src/main/java/com/seaofnodes/simple/node/RegionNode.java @@ -1,9 +1,9 @@ package com.seaofnodes.simple.node; -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 { @@ -13,9 +13,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 String label() { return "Region"; } + @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 StringBuilder _print1(StringBuilder sb, BitSet visited) { @@ -75,7 +75,7 @@ public class RegionNode extends CFGNode { // Fold Region for( int j=1; j<region.nIns(); j++ ) addDef(region.in(j)); - setDef(i,Parser.XCTRL); + setDef(i,CodeGen.CODE.XCTRL); return this; } } @@ -111,7 +111,7 @@ public class RegionNode extends CFGNode { CodeGen.CODE.addAll(phi.delDef(path)._outputs); } } - return isDead() ? Parser.XCTRL : delDef(path); + return isDead() ? CodeGen.CODE.XCTRL : delDef(path); } private int findDeadInput(int startIdx) { diff --git a/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/src/main/java/com/seaofnodes/simple/node/ReturnNode.java index 0aa70214..b60d27d7 100644 --- a/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -27,6 +27,7 @@ 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); } @@ -35,9 +36,6 @@ public class ReturnNode extends CFGNode { @Override public FunNode fun() { return _fun; } @Override - public String label() { return "Return"; } - - @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { sb.append("return "); if( expr()==null ) sb.append("----"); @@ -50,12 +48,10 @@ public class ReturnNode extends CFGNode { @Override public Type compute() { - if( inProgress () ) return TypeTuple.RET; // In progress return TypeTuple.make(ctrl()._type,mem()._type,expr()._type); } @Override public Node idealize() { - if( inProgress () ) return null; if( _fun.isDead() ) return null; // Upgrade signature based on return type @@ -64,87 +60,44 @@ 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(); - } - - // Gather parse-time return types for error reporting - private Type mt = Type.TOP; - 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; - mt = mt.meet(t); - 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._minType == TypeMem.BOT; - PhiNode rez = (PhiNode)r.out(1); assert rez._minType == Type.BOTTOM; - // 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 ) return null; // Exit path is dead - return expr()._type==Type.BOTTOM || expr()._type==Type.TOP ? mixerr(ti,tf,tp,tn,_fun._loc) : null; + if( ctrl()._type == Type.CONTROL && + expr()._type == Type.TOP ) + return Parser.error("No defined return type",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); + } + } + return null; } - 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); + @Override public boolean eq( Node n ) { + return !_fun.isDead() && !((ReturnNode)n)._fun.isDead(); } - // ------------ // MachNode specifics, shared across all CPUs public String op() { - return _fun._frameAdjust > 0 ? "addi" : "ret"; + return _fun._frameAdjust > 0 ? "epilog" : "ret "; } // Correct Nodes outside the normal edges public void postSelect(CodeGen code) { - FunNode fun = (FunNode)rpc().in(0); - _fun = fun; - fun.setRet(this); + _fun.setRet(this); } public RegMask regmap(int i) { return i==2 @@ -156,13 +109,14 @@ public class ReturnNode extends CFGNode { public void asm(CodeGen code, SB sb) { int frameAdjust = fun()._frameAdjust; if( frameAdjust>0 ) - sb.p("rsp += #").p(frameAdjust).p("\nret"); + sb.p("\n").p("rsp += #").p(frameAdjust).p("\nret "); // Post code-gen, just print the "ret" - if( code._phase.ordinal() <= CodeGen.Phase.RegAlloc.ordinal() ) + if( code._phase.ordinal() < CodeGen.Phase.RegAlloc.ordinal() || + (code._phase.ordinal() == CodeGen.Phase.RegAlloc.ordinal() && !code._regAlloc.done()) ) // 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),fun())).p(" "); + sb.p(code.reg(in(i))).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/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java b/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java index 7936086d..322a6bc0 100644 --- a/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java +++ b/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java @@ -7,8 +7,7 @@ import java.util.BitSet; public class RoundF32Node extends Node { public RoundF32Node(Node lhs) { super(null, lhs); } - - @Override public String label() { return "RoundF32"; } + @Override public Tag serialTag() { return Tag.RoundF32; } @Override public String glabel() { return "(f32)"; } diff --git a/src/main/java/com/seaofnodes/simple/node/SarNode.java b/src/main/java/com/seaofnodes/simple/node/SarNode.java index ee15fa65..8ad5988d 100644 --- a/src/main/java/com/seaofnodes/simple/node/SarNode.java +++ b/src/main/java/com/seaofnodes/simple/node/SarNode.java @@ -5,9 +5,9 @@ import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; public class SarNode extends ArithNode { - public SarNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); } + 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 label() { return "Sar"; } @Override public String op() { return ">>"; } @Override public String glabel() { return ">>"; } diff --git a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java index 36746a81..58508a1c 100644 --- a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -3,11 +3,11 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.Var; import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.ParseAll; +import com.seaofnodes.simple.codegen.CompUnit; 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.*; /** @@ -31,21 +31,23 @@ public class ScopeNode extends MemMergeNode { // Lexical scope is typed: public static class Kind { // Basic block scoping - public static class Block extends Kind { } + 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; + } // Function scope - 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 static class Func extends Kind { public Func(String name) { _name=name;} public final String _name; } 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 casted results + // Extra guards; tested predicates and cast results private final Ary<Node> _guards; // A new ScopeNode @@ -64,8 +66,8 @@ public class ScopeNode extends MemMergeNode { int j=1; for( int i=0; i<nIns(); i++ ) { if( j < depth() && i == _kinds.at(j)._lexSize ) { sb.append("| "); j++; } - Var v = _vars.at(i); - sb.append(v.type()); + Var v = var(i); + sb.append(v._type()); sb.append(" "); if( v._final ) sb.append("!"); sb.append(v._name); @@ -85,7 +87,7 @@ public class ScopeNode extends MemMergeNode { public Node ctrl() { return in(0); } - public MemMergeNode mem() { return (MemMergeNode)in(1); } + public Node mem() { return in(1); } public Var var(int i) { return _vars.at(i); } /** @@ -108,7 +110,11 @@ public class ScopeNode extends MemMergeNode { // Pop a lexical scope public void pop() { - promote(); // Promote forward references to the next outer scope + promote(null); // Promote forward references to the next outer scope + _pop(); + } + // Pop a scope, no promote FRefs + public void _pop() { int n = _kinds.pop()._lexSize; popUntil(n); // Pop off inputs going out of scope _vars.setLen(n); // Pop off variables going out of scope @@ -117,59 +123,85 @@ 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() { - Kind kind = _kinds.last(); + public void promote(CodeGen code) { + Kind kind = klast(); int n = kind._lexSize; for( int i=n; i<nIns(); i++ ) { Var v = var(i); + Node vn = in(i); if( !v.isFRef() ) continue; - if( depth()==1 ) - throw Parser.error("Undefined name '" + v._name + "'",v._loc); - _vars .swap(n,i); - _inputs.swap(n,i); - v._idx = n; + // 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; + } kind._lexSize = ++n; } } - 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 ; } - - public Kind kind( Var v ) { + 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( v._idx >= _kinds.at(i)._lexSize ) - return _kinds.at(i); - throw Utils.TODO(); + 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; } - // 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. - - // 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. + // 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"); + } - // - Walking out of a method and (skipping Blocks) into the matching struct - // is OK, and this is a instance var load. + // 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"); + } - // - Walking out of a method and into the wrong struct is an error; - // including nested structs containing (no nested classes yet) + // Which lexical scope contains this Var? + public int kindx( Var v ) { + for( int i=depth()-1; i>=0; i-- ) + if( v._idx >= _kinds.at(i)._lexSize ) + return i; + return -1; + } - // Returns error, forward-ref, ok containing struct, ok containing function/block. + // Which lexical scope contains this Var? + public Kind kind( Var v ) { + int kx = kindx(v); + return kx == -1 ? null : _kinds.at(kx); + } - 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(); + 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"); } + // Which function scope contains this Var? + public Kind kindFcn( Var v ) { + int kx = kindFcnx(v); + return kx == -1 ? null : _kinds.at(kx); + } // Find name in reverse, return an index into _vars or -1. Linear scan // instead of hashtable, but probably doesn't matter until the scan @@ -185,9 +217,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.charAt(0)!='$' ; // Later scopes do not define memory + assert _kinds.isEmpty() || name!=MEM0 ; // Later scopes do not define memory if( depth() > 0 ) - for( int i=_vars.size()-1; i>=_kinds.last()._lexSize; i-- ) { + for( int i=_vars.size()-1; i>=klast()._lexSize; i-- ) { Var n = var(i); if( n._name.equals(name) ) { if( !n.isFRef() ) return false; // Double define @@ -195,21 +227,47 @@ 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==Parser.XCTRL); + Var v = new Var(nIns(),name,declaredType,xfinal,loc,init==CodeGen.CODE.XCTRL); _vars.add(v); // Creating a forward reference - if( init==Parser.XCTRL ) - init = new FRefNode(v).init(); + if( init==CodeGen.CODE.XCTRL ) + //init = new FRefNode(v).init(); + throw Utils.TODO(); addDef(init); return true; } - // Read from memory - public Node mem( int alias ) { return mem()._mem(alias,null); } - // Write to memory - public void mem( int alias, Node st ) { mem()._mem(alias,st); } + // Normal lookup was tried and failed. Insert a forward ref outside any + // enclosing Kind.Func scope, skipping any nested Blocks or Allocs and + // return the Var. May insert at the outermost scope, which means this + // must be defined externally, or it's an error. + + public Var defineFRef( String id, Type t, boolean xfinal, Parser.Lexer loc ) { + // Kind/Lexical scope index + int kidx = enclosingFunction(); + int idx = _kinds.at(kidx)._lexSize; + Var var = new Var(idx,id,t,xfinal,loc,true); + FRefNode fref = new FRefNode(id,loc).init(); + fref._type = fref._con = t; + // Insert in the lex scope just prior to kidx + insert(var,fref,kidx); + 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); + } /** @@ -247,7 +305,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,new PhiNode(v._name, v.type(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).peephole()); + : loop.setDef(v._idx,PhiNode.make(v._name, v.type(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).init()); setDef(v._idx,old); } //assert !v._final || st==null; @@ -285,15 +343,9 @@ public class ScopeNode extends MemMergeNode { n.keep(); dup.addDef(ctrl()); // Control input is just copied - // Memory input is a shallow copy - MemMergeNode memdup = new MemMergeNode(true), mem = mem(); - memdup.addDef(null); - memdup.addDef(loop ? this : mem.in(1)); - for( int i=2; i<mem.nIns(); i++ ) - // For lazy phis on loops we use a sentinel - // that will trigger phi creation on update - memdup.addDef(loop ? this : mem.in(i)); - dup.addDef(memdup); + // 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++ ) @@ -313,39 +365,49 @@ public class ScopeNode extends MemMergeNode { */ public RegionNode mergeScopes(ScopeNode that, Parser.Lexer loc) { RegionNode r = ctrl(new RegionNode(loc,null,ctrl(), that.ctrl()).keep()); - mem()._merge(that.mem(),r); - this ._merge(that ,r); + this._merge(that,r); that.kill(); // Kill merged scope CodeGen.CODE.add(r); return r.unkeep(); } - private void _merge(ScopeNode that, RegionNode r) { - for( int i = 2; i < nIns(); i++) + 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++) 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 Var v = var(i); Node lhs = this.in(this.update(v,null)); Node rhs = that.in(that.update(v,null)); - setDef(i, new PhiNode(v._name, v.type(), r, lhs, rhs).peephole()); + setDef(i, PhiNode.make(v._name, v.type(), r, lhs, rhs).peephole()); } } // 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 ) { - for( int i = nIns(); i < scope.nIns(); i++ ) { + 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++ ) { Var n = scope.var(i); + if( i<nIns() && var(i)==n ) continue; if( n.isFRef() ) { // RHS has forward refs - _vars.add(n); // Copy to LHS - addDef(scope.in(i)); + _vars.insert(n,i); + insertDef(i,scope.in(i)); } else throw Parser.error("Cannot define a '"+n._name+"' on one arm of an if",n._loc); } } + // ------------------------------------------------ // 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 ) { @@ -353,34 +415,30 @@ public class ScopeNode extends MemMergeNode { assert ctrl instanceof LoopNode loop && loop.inProgress(); ctrl.setDef(2,back.ctrl()); - mem()._endLoopMem( this, back.mem(), exit.mem() ); - this ._endLoop ( this, back , exit ); + this._endLoop( this, back, exit); back.kill(); // Loop backedge is dead // Now one-time do a useless-phi removal - mem()._useless(); - this ._useless(); - - // The exit mem's lazy default value had been the loop top, - // now it goes back to predating the loop. - exit.mem().setDef(1,mem().in(1)); + this._useless(); } // Fill in the backedge of any inserted Phis void _endLoop( ScopeNode scope, Node back, Node exit ) { - for( int i=2; i<nIns(); i++ ) { + for( int i=1; i<nIns(); i++ ) { if( var(i)._final ) continue; // Final vars did not get modified in the loop - if( var(i).type().isHighOrConst() ) continue; // Cannot lift higher than a constant, so no Phi - if( back.in(i) != scope ) { - PhiNode phi = (PhiNode)in(i); + Node n = in(i); + if( var(i).type().isHighOrConst() ) { // Cannot lift higher than a constant, so no Phi + n.subsume(n=ConstantNode.make(var(i).type()).init()); + } else if( back.in(i) != scope ) { + PhiNode phi = (PhiNode)n; 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,in(i)); + exit.setDef(i,n); } } - + // ------------------------------------------------ // 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. public void addGuards( Node ctrl, Node pred, boolean invert ) { @@ -392,33 +450,30 @@ 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. - Type tnz = pred._type.nonZero(); - if( tnz!=null ) - _addGuard(tnz,ctrl,pred); + _addGuard(true,ctrl,pred); // Compute the negative test type. - if( pred instanceof NotNode not ) { - Node npred = not.in(1); - Type tzero = npred._type.makeZero(); - _addGuard(tzero,ctrl,npred); - } + if( zeroPred != null ) + _addGuard(false,ctrl,zeroPred); } - private void _addGuard(Type guard, Node ctrl, Node pred) { - Type tcast = guard.join(pred._type); - if( tcast != pred._type && !tcast.isHigh() ) { - Node cast = new CastNode(tcast,ctrl,pred.keep()).peephole().keep(); + 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(cast); - replace(pred,cast); + _guards.add(guard); + replace(pred,guard); } } // Remove matching pred/cast pairs from this guarded region. - public ScopeNode removeGuards( Node ctrl ) { + public void removeGuards( Node ctrl ) { assert ctrl instanceof CFGNode; // 0,1 or 2 guards while( true ) { @@ -428,7 +483,6 @@ 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 @@ -465,4 +519,44 @@ public class ScopeNode extends MemMergeNode { setDef(i,cast); } + // ------------------------------------------------------------------------ + // 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/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/src/main/java/com/seaofnodes/simple/node/ShlNode.java index 2557bf6b..459b26eb 100644 --- a/src/main/java/com/seaofnodes/simple/node/ShlNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ShlNode.java @@ -1,13 +1,14 @@ 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; public class ShlNode extends ArithNode { - public ShlNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); } + 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 label() { return "Shl"; } @Override public String op() { return "<<"; } @Override public String glabel() { return "<<"; } @@ -38,7 +39,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(), Parser.con(sum) ); + return new AddNode(new ShlNode(_loc,add.in(1),rhs).peephole(), CodeGen.CODE.con(sum) ); } } diff --git a/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/src/main/java/com/seaofnodes/simple/node/ShrNode.java index 269f6b7f..5e4870d1 100644 --- a/src/main/java/com/seaofnodes/simple/node/ShrNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ShrNode.java @@ -5,9 +5,9 @@ import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; public class ShrNode extends ArithNode { - public ShrNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); } + 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 label() { return "Shr"; } @Override public String op() { return ">>>"; } @Override public String glabel() { return ">>>"; } diff --git a/src/main/java/com/seaofnodes/simple/node/SplitNode.java b/src/main/java/com/seaofnodes/simple/node/SplitNode.java index d061cdbd..4b58187f 100644 --- a/src/main/java/com/seaofnodes/simple/node/SplitNode.java +++ b/src/main/java/com/seaofnodes/simple/node/SplitNode.java @@ -1,14 +1,17 @@ package com.seaofnodes.simple.node; 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; import java.util.BitSet; 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; - public SplitNode(String kind, byte round, Node[] nodes) { super(nodes); _kind = kind; _round = round; } + 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; } @Override public String op() { return "mov"; } @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { sb.append("mov("); @@ -16,6 +19,7 @@ public abstract class SplitNode extends MachConcreteNode { else in(1)._print0(sb,visited); return sb.append(")"); } + 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/src/main/java/com/seaofnodes/simple/node/StartCUNode.java b/src/main/java/com/seaofnodes/simple/node/StartCUNode.java new file mode 100644 index 00000000..d828c5f1 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/StartCUNode.java @@ -0,0 +1,33 @@ +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 CodeGen.CODE.iDepthAt(1); } + @Override public CFGNode idom(Node dep) { return cfg0(); } + @Override public void gather( HashMap<String,Integer> strs ) { + Serialize.gather(strs,_fname); + } +} diff --git a/src/main/java/com/seaofnodes/simple/node/StartNode.java b/src/main/java/com/seaofnodes/simple/node/StartNode.java index e3806de7..7d2b2773 100644 --- a/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ b/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -3,48 +3,78 @@ package com.seaofnodes.simple.node; 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.BitSet; import java.util.HashSet; -import static com.seaofnodes.simple.util.Utils.TODO; /** - * The Start node represents the start of the function. + * 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. */ public class StartNode extends LoopNode implements MultiNode { final Type _arg; - public StartNode(Type arg) { super(null,null); _arg = arg; _type = compute(); } + public StartNode(StartNode start, StopNode stop, Type arg) { super(null,start,stop); _arg = arg; } public StartNode(StartNode start) { super(start); _arg = start==null ? null : start._arg; } - - @Override public String label() { return "Start"; } + @Override public Tag serialTag() { return Tag.Start; } + public Type arg() { return _arg; } @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(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, its "main" + // IfNode or other multi-way branch. For Start, it's the class init for + // this compilation unit. @Override public CFGNode uctrl() { - // Find "main", it's the start. + // Find module.<clinit>, it's the start. CFGNode C = null; for( Node use : _outputs ) - if( use instanceof FunNode fun && fun.sig().isa(CodeGen.CODE._main) ) + if( use instanceof FunNode fun && fun.isModInit() ) { assert C==null; C = fun; } + if( C==null ) + for( FunNode fun : CodeGen.CODE._linker ) + if( fun!=null && !fun.isDead() && fun.isModInit() ) + { 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() { - return TypeTuple.make(Type.CONTROL,TypeMem.TOP,_arg); + 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); } @Override public Node idealize() { return null; } @@ -53,4 +83,5 @@ public class StartNode extends LoopNode implements MultiNode { @Override public int idepth() { return CodeGen.CODE.iDepthAt(0); } @Override public CFGNode idom(Node dep) { return null; } + @Override public boolean eq( Node n ) { return true; } } diff --git a/src/main/java/com/seaofnodes/simple/node/StopCUNode.java b/src/main/java/com/seaofnodes/simple/node/StopCUNode.java new file mode 100644 index 00000000..ab0f5da6 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/StopCUNode.java @@ -0,0 +1,122 @@ +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 == CodeGen.Phase.Opto && + _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 != CodeGen.Phase.Opto ) 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/src/main/java/com/seaofnodes/simple/node/StopNode.java b/src/main/java/com/seaofnodes/simple/node/StopNode.java index 4b03bf4e..45f5519e 100644 --- a/src/main/java/com/seaofnodes/simple/node/StopNode.java +++ b/src/main/java/com/seaofnodes/simple/node/StopNode.java @@ -1,34 +1,45 @@ package com.seaofnodes.simple.node; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.util.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.BAOS; + import java.util.BitSet; +import java.util.HashMap; +import java.util.IdentityHashMap; +// A collection of function ReturnNodes for a compilation unit. public class StopNode extends CFGNode { - public final String _src; - - public StopNode(String src) { + public StopNode() { super(); - _src = src; _type = compute(); } - public StopNode(StopNode stop) { super(stop); _src = stop==null ? null : stop._src; } - - @Override - public String label() { - return "Stop"; + 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(nIns()); } + static Node make( BAOS bais ) { + StopNode stop = new StopNode(); + stop.setDefX(bais.packed1()-1,null); + return stop; } @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { // For the sake of many old tests, and single value prints as "return val" - ReturnNode ret1 = ret(); + ReturnNode ret1 = null; + for( Node n : _inputs ) { + if( n instanceof ReturnNode ret ) { + if( ret1 != null ) { ret1=null; break; } + ret1 = ret; + } + } if( ret1!=null ) return ret1._print0(sb,visited); + sb.append("Stop[ "); - for( Node ret : _inputs ) - if( ret!=null ) { - String name = ((ReturnNode)ret).fun()._name; + for( Node n : _inputs ) + if( n instanceof ReturnNode ret ) { + String name = ret.fun()._name; if( name== null || !name.startsWith("sys.") ) ret._print0(sb, visited).append(" "); } @@ -41,25 +52,36 @@ public class StopNode extends CFGNode { // If a single Return, return it. // Otherwise, null because ambiguous. public ReturnNode 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; + 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; + } } - return ret1==this ? null : (ReturnNode)ret1; + return ret1; } @Override - public Type compute() { - return Type.BOTTOM; + 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; } - @Override - public Node idealize() { + @Override public Node idealize() { int len = nIns(); for( int i=0; i<nIns(); i++ ) - if( addDep(((ReturnNode)in(i)).fun()).isDead() ) + if( in(i).nIns()==0 ) delDef(i--); if( len != nIns() ) return this; return null; @@ -69,7 +91,10 @@ public class StopNode extends CFGNode { if( _idepth!=0 ) return _idepth; int d=0; for( Node ret : _inputs ) - d = Math.max(d,((ReturnNode)ret).idepth()+1); + // 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); return _idepth=d; } } diff --git a/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/src/main/java/com/seaofnodes/simple/node/StoreNode.java index 8e33512a..c0006511 100644 --- a/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -1,8 +1,14 @@ package com.seaofnodes.simple.node; 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.BitSet; +import java.util.HashMap; +import java.util.IdentityHashMap; /** * Store represents setting a value to a memory based object, in chapter 10 @@ -10,7 +16,10 @@ import java.util.BitSet; */ public class StoreNode extends MemOpNode { - private final boolean _init; // Initializing writes are allowed to write null + 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; /** * @param name The struct field we are assigning to @@ -19,9 +28,25 @@ 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 glb, Node mem, Node ptr, Node off, Node value, boolean init) { - super(loc, name, alias, false, glb, mem, ptr, off, value); + public StoreNode(Parser.Lexer loc, String name, int alias, Type glb, Node ctrl, Node mem, Node ptr, Node off, Node value, boolean init) { + this(loc,name,alias,glb,ctrl,mem,ptr,off,value,init,(byte)0); + } + private StoreNode(Parser.Lexer loc, String name, int alias, Type glb, Node ctrl, Node mem, Node ptr, Node off, Node value, boolean init, byte size) { + super(loc, name, alias, false, glb, ctrl, 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); } // GraphVis DOT code and debugger labels @@ -30,7 +55,10 @@ public class StoreNode extends MemOpNode { @Override public String glabel() { return "." +_name+"="; } @Override public boolean isMem() { return true; } + public Node nnptr() { return ptr() instanceof GuardNode cast && cast._nonZero ? cast.in(1) : ptr(); } public Node val() { return in(4); } + public int storeSize() { assert _size==1 || _size==2 || _size==4 || _size==8; return _size; } + @Override public int log_size() { return Integer.numberOfTrailingZeros(storeSize()); } @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { @@ -39,28 +67,108 @@ public class StoreNode extends MemOpNode { @Override public Type compute() { - Type val = val()._type; - Type mem0 = mem()._type; - if( mem0 == Type.TOP ) return TypeMem.TOP; - TypeMem mem = (TypeMem)mem0; // Invariant - if( mem == TypeMem.TOP ) return TypeMem.TOP; - Type t = Type.BOTTOM; // No idea on field contents - // Same alias, lift val to the declared type and then meet into other fields - if( mem._alias == _alias ) { - assert !_declaredType.isFRef(); - // Sharpen memory value; required for narrowing stores where the parser inserts - // zero/sign masking and somebody reads the TypeMem type. - val = val.join(_declaredType); - t = val.meet(mem._t); - } - return TypeMem.make(_alias,t); + Type val = val()._type; + Type mem0 = aliasMem()._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 && decl != Type.TOP ) + val = val.join(decl); + //if( err()!=null ) + // val = Type.BOTTOM; + // 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. + Field fld = ptr._obj.field(_name); + boolean xfinal = fld != null && fld._final; + return TypeMem.make(_alias,t,mem._one,mem._clz,xfinal,mem._escFs,mem._escAs).escapesFrom(val); } @Override public Node idealize() { + assert !(mem() instanceof CheckCastNode); - if( mem() instanceof CastNode cast ) { - setDef(1,cast.in(1)); + // 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. + Type storage = storageType(); + if( _size==0 && storage!=Type.BOTTOM && storage!=Type.TOP ) { + unlock(); // Width participates in GVN identity. + _size = storeSize(storage); + return init(); + } + + // A precise Store can confirm its alias from a precise memory input. + // Do not sharpen an alias-#1 (whole-memory) Store in place: users of + // that Store already treat it as the complete memory state. The + // pointer/field path below replaces it with a precise Store wrapped in + // a MemMerge, preserving any other precise aliases in its input. + if( mem()._type instanceof TypeMem mem ) { + assert mem._alias > 0; + if( _alias != 1 && mem._alias != 1 ) + assert _alias == mem._alias; + } + + // Forward-ref loads eventually sharpen to a declared type + Field fld; + if( _alias==1 && ptr()._type instanceof TypeMemPtr tmp && (fld=tmp._obj.field(_name)) != null ) { + // 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 stores 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. + Type decl = storageType(); + byte size = _size==0 && decl!=Type.BOTTOM && decl!=Type.TOP ? storeSize(decl) : _size; + 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; + 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(false,null,mem()); + if( !(mem() instanceof BulkMemPhiNode) ) + mmm.alias(fld._alias,st); + return mmm.init(); + } + // Expose the same effective memory input already observed by compute. + Node aliasMem = aliasMem(); + if( aliasMem != mem() ) { + setDef(1,aliasMem); + CodeGen.CODE.add(aliasMem); return this; } @@ -73,20 +181,26 @@ public class StoreNode extends MemOpNode { // Must have exactly one use of "this" or you get weird // non-serializable memory effects in the worse case. checkOnlyUse(st) ) { - assert _name.equals(st._name); // Equiv class aliasing is perfect + assert _name==st._name; // Equiv class aliasing is perfect setDef(1,st.mem()); return this; } // Simple store-after-MemMerge to a known alias can bypass. Happens when inlining. - if( mem() instanceof MemMergeNode mem ) { + if( _alias != 1 && mem() instanceof MemMergeNode mem ) { setDef(1,mem.alias(_alias)); return this; } + // Resolve the semantic Store variant exactly once, after structural + // memory/alias peeps. This avoids publishing a half-sharpened Store + // while BulkMemPhi is constructing precise slices. + if( storage != Type.BOTTOM && storage != Type.TOP ) + assert _size==storeSize(storage) : "Store width changed for '"+_name+"': "+_size+" from "+storage; + // Value is automatically truncated by narrow store - if( val() instanceof AndNode and && and.in(2)._type.isConstant() ) { - int log = _declaredType.log_size(); + if( _size!=0 && val() instanceof AndNode and && and.in(2)._type.isConstant() ) { + int log = log_size(); if( log<3 ) { // And-mask vs narrow store long mask = ((TypeInteger)and.in(2)._type).value(); long bits = (1L<<(8<<log))-1; @@ -98,7 +212,7 @@ public class StoreNode extends MemOpNode { } // Store will chop high order bits off; math to change those bits can be dropped. - if( val() instanceof SarNode shr && + if( _size!=0 && val() instanceof SarNode shr && shr.in(1) instanceof ShlNode shl && shr.in(2)._type.isConstant() && shl.in(2)._type.isConstant() ) { @@ -114,9 +228,68 @@ public class StoreNode extends MemOpNode { return this; } } + return null; } + private static byte storeSize(Type decl) { + // `decl` is the target field declaration, not the stored value. In + // particular a constant zero stored into u32 is still a 4-byte Store; + // taking the constant's GLB here would incorrectly select i64. + byte size = (byte)(1 << decl.log_size()); + assert size==1 || size==2 || size==4 || size==8; + return size; + } + + // 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 Type storageType() { + // Freshly parsed field stores retain the declaring structure on their + // symbolic offset even after the pointer's flow type sharpens. + if( off() instanceof ConFldOffNode coff ) { + Field fld = coff._ts.field(_name); + if( fld != null && fld._t != null ) return fld._t; + } + if( !(ptr()._type instanceof TypeMemPtr tmp) ) return Type.BOTTOM; + Type base = Parser.TYPES.get(tmp._obj._name); + TypeStruct obj = base instanceof TypeStruct ts ? ts : tmp._obj; + Field fld = obj.field(_name); + // Imported optimized structs can expose the current constant field + // value instead of its declaration. A constant has no storage width; + // an imported Store already carries its serialized, frozen `_size`. + return fld == null || fld._t == null || fld._t.isConstant() ? Type.BOTTOM : fld._t; + } + + // 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 -> esc.pub()==this ? esc.fld()._alias : 0; + default -> 0; + }; + } + + // Select a precise alias from a whole-memory partition. Alias #1 is the + // bulk remainder itself. + private static Node memSlice( Node mem, int alias ) { + return alias != 1 && mem instanceof MemMergeNode merge + ? merge.alias(alias) + : mem; + } + + // Semantic memory input for this Store. Compute defines behavior from + // this view; ideal exposes the same edge in the graph. + private Node aliasMem() { + Node mem = mem(); + if( _alias != 1 && mem instanceof MemMergeNode merge ) + mem = merge.alias(_alias); + return mem; + } + // Check that "mem" has no uses except "this" private boolean checkOnlyUse(Node mem) { if( mem.nOuts()==1 ) return true; @@ -132,11 +305,17 @@ public class StoreNode extends MemOpNode { public Parser.ParseException err() { Parser.ParseException err = super.err(); if( err != null ) return err; + if( _size==0 && CodeGen.CODE._phase.ordinal() > CodeGen.Phase.Opto.ordinal() ) + throw Utils.TODO("Failed to decide Store size"); if( ptr()._type == Type.TOP ) return null; // This means we have an error input, report elsewhere TypeMemPtr tmp = (TypeMemPtr)ptr()._type; - if( tmp._obj.field(_name)._final && !_init ) + Field f = tmp._obj.field(_name); + if( f!=null && f._final && !_init ) return Parser.error("Cannot modify final field '"+_name+"'",_loc); return null; } + + @Override public boolean eq(Node n) { return _size==((StoreNode)n)._size && super.eq(n); } + @Override int hash() { return super.hash() ^ _size; } } diff --git a/src/main/java/com/seaofnodes/simple/node/StructNode.java b/src/main/java/com/seaofnodes/simple/node/StructNode.java index b5b59b81..4dcc695f 100644 --- a/src/main/java/com/seaofnodes/simple/node/StructNode.java +++ b/src/main/java/com/seaofnodes/simple/node/StructNode.java @@ -1,8 +1,11 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.util.BAOS; +import com.seaofnodes.simple.util.Utils; import java.util.BitSet; +import java.util.HashMap; +import java.util.IdentityHashMap; /** * Build a compound object @@ -11,13 +14,23 @@ public class StructNode extends Node { public final TypeStruct _ts; public StructNode(TypeStruct ts) { _ts=ts; assert !ts._open; } - - @Override public String label() { return _ts==null ? "STRUCT?" : _ts.str(); } + @Override public Tag serialTag() { return Tag.Struct; } + @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)); + //baos.packed2(types.get(_type)); // Write _type not _minType, which can be higher + throw Utils.TODO(); + } + static Node make( BAOS bais, Type[] types) { + //Node[] ins = new Node[bais.packed1()]; + //return new StructNode(types[bais.packed2()], ins); + throw Utils.TODO(); + } @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { if( _ts==null ) return sb.append("STRUCT?"); - sb.append(_ts._name).append(" {"); + sb.append(_ts._name).append(" { "); for( int i=0; i<nIns(); i++ ) { sb.append(_ts._fields[i]._fname).append(":"); sb.append(in(i)==null ? Type.BOTTOM : in(i)._type); diff --git a/src/main/java/com/seaofnodes/simple/node/SubFNode.java b/src/main/java/com/seaofnodes/simple/node/SubFNode.java deleted file mode 100644 index 3fc4f7ca..00000000 --- a/src/main/java/com/seaofnodes/simple/node/SubFNode.java +++ /dev/null @@ -1,27 +0,0 @@ -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); } - - @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/src/main/java/com/seaofnodes/simple/node/SubNode.java b/src/main/java/com/seaofnodes/simple/node/SubNode.java index 8e4f6f79..aba1df90 100644 --- a/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -6,12 +6,15 @@ import com.seaofnodes.simple.type.TypeInteger; import java.util.BitSet; 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 label() { return "Sub"; } @Override public String op() { return "-"; } - @Override long doOp( long x, long y ) { return x - y; } + @Override long doOp( long x, long y ) { return x - y; } + @Override double doOp(double x, double y ) { return x - y; } @Override TypeInteger doOp(TypeInteger x, TypeInteger y) { // Sub of same is 0 if( in(1)==in(2) ) @@ -38,6 +41,5 @@ public class SubNode extends ArithNode { return super.idealize(); } - @Override Node copy(Node lhs, Node rhs) { return new SubNode(lhs,rhs); } - @Override Node copyF() { return new SubFNode(null,null); } + @Override Node copy(Node lhs, Node rhs) { return new SubNode(lhs,rhs,_mode); } } diff --git a/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java b/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java index e02183a0..2d8cc788 100644 --- a/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java @@ -8,8 +8,7 @@ import java.util.BitSet; public class ToFloatNode extends Node { public ToFloatNode(Node lhs) { super(null, lhs); } - - @Override public String label() { return "ToFloat"; } + @Override public Tag serialTag() { return Tag.ToFloat; } @Override public String glabel() { return "(flt)"; } diff --git a/src/main/java/com/seaofnodes/simple/node/TypeNode.java b/src/main/java/com/seaofnodes/simple/node/TypeNode.java new file mode 100644 index 00000000..3ab2d6f9 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/TypeNode.java @@ -0,0 +1,33 @@ +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/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java b/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java index 8c620a04..a8c06b37 100644 --- a/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java +++ b/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java @@ -7,7 +7,7 @@ import java.util.BitSet; public class XCtrlNode extends CFGNode { public XCtrlNode() { super(new Node[]{CodeGen.CODE._start}); } - @Override public String label() { return "Xctrl"; } + @Override public Tag serialTag() { return Tag.XCtrl; } @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append("Xctrl"); } @Override public boolean isConst() { return true; } @Override public Type compute() { return Type.XCONTROL; } diff --git a/src/main/java/com/seaofnodes/simple/node/XorNode.java b/src/main/java/com/seaofnodes/simple/node/XorNode.java index fdb4863e..95d4461e 100644 --- a/src/main/java/com/seaofnodes/simple/node/XorNode.java +++ b/src/main/java/com/seaofnodes/simple/node/XorNode.java @@ -5,9 +5,9 @@ import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; public class XorNode extends ArithNode { - public XorNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); } + 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 label() { return "Xor"; } @Override public String op() { return "^"; } @Override public String glabel() { return "^"; } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java index b9ede544..5cf8a89c 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java @@ -8,9 +8,11 @@ import com.seaofnodes.simple.util.Utils; // Jump on flags, uses flags public class BranchARM extends IfNode implements MachNode, RIPRelSize { String _bop; - BranchARM(IfNode iff, String bop ) { + final boolean _fp; + BranchARM(IfNode iff, String bop, boolean fp ) { super(iff); _bop = bop; + _fp = fp; } @Override public String op() { return "j"+_bop; } @Override public String label() { return op(); } @@ -35,7 +37,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( arm.b_cond(arm.OP_BRANCH, 0, arm.make_condition(_bop)) ); + enc.add4( arm.b_cond(arm.OP_BRANCH, 0, arm.make_condition(_bop, _fp)) ); } else { if( _bop=="!=" ) return; // Inverted, no code enc.add4(arm.b(arm.OP_UJMP, 0)); @@ -55,7 +57,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,arm.b_cond(arm.OP_BRANCH, delta, arm.make_condition(_bop))); + enc.patch4(opStart,arm.b_cond(arm.OP_BRANCH, delta, arm.make_condition(_bop, _fp))); } else { throw Utils.TODO(); } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java index 427cf2e2..8f070525 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java @@ -13,8 +13,7 @@ public class CallARM extends CallNode implements MachNode, RIPRelSize { _inputs.pop(); // Pop constant target assert tfp.isConstant(); _tfp = tfp; - FunNode fun = CodeGen.CODE.link(tfp); - _name = fun==null ? ((ExternNode)call.fptr())._extern : fun._name; // Can be null for extern calls + _name = CodeGen.CODE.funcName(tfp.fidx()); } @Override public String op() { return "call"; } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/FltARM.java index b47b65ad..42553868 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/FltARM.java @@ -3,21 +3,22 @@ package com.seaofnodes.simple.node.cpus.arm; 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 FloatARM extends ConstantNode implements MachNode, RIPRelSize { - FloatARM(ConstantNode con) { super(con); } +public class FltARM extends ConstantNode implements MachNode, RIPRelSize { + final String _ext; + FltARM( ConstantNode con, String ext ) { super(con); _ext = ext; } @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 FloatARM copy() { return new FloatARM(this); } + @Override public FltARM copy() { return new FltARM(this,_ext); } @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(arm.load_pc(arm.OPF_ARM, 0, dst)); diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java index ee74b95e..dbed9487 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java @@ -6,19 +6,22 @@ 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 { - IntARM( ConstantNode con ) { super(con); } + final String _ext; + IntARM( ConstantNode con, String ext ) { super(con); _ext = ext; } @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); } + @Override public IntARM copy() { return new IntARM(this,_ext); } @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(); int nb0 = 0; diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java index 1e5d2094..f8c9bfef 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java @@ -21,16 +21,16 @@ public class LoadARM extends MemOpARM { private static final int[] OP_LOADS = new int[]{ arm.OP_LOAD_IMM_8, arm.OP_LOAD_IMM_16, arm.OP_LOAD_IMM_32, arm.OP_LOAD_IMM_64, }; private int imm_op() { - return _declaredType == TypeFloat.F32 ? arm.OPF_LOAD_IMM_32 - : _declaredType == TypeFloat.F64 ? arm.OPF_LOAD_IMM_64 - : OP_LOADS[_declaredType.log_size()]; + return _con == TypeFloat.F32 ? arm.OPF_LOAD_IMM_32 + : _con == TypeFloat.F64 ? arm.OPF_LOAD_IMM_64 + : OP_LOADS[_con.log_size()]; } private static final int[] OP_LOAD_RS = new int[]{ arm.OP_LOAD_R_8 , arm.OP_LOAD_R_16 , arm.OP_LOAD_R_32 , arm.OP_LOAD_R_64, }; // ldr(immediate - unsigned offset) | ldr(register) @Override public void encoding( Encoding enc ) { - ldst_encode(enc, imm_op(), OP_LOAD_RS[_declaredType.log_size()], this, size()); + ldst_encode(enc, imm_op(), OP_LOAD_RS[_con.log_size()], this, size()); } @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)).p(","); diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java index e877931d..08bdf974 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java @@ -11,7 +11,8 @@ import java.util.BitSet; 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 char _sz = (char)('0'+(1<<_declaredType.log_size())); + final int _bytes; + final char _sz; MemOpARM(MemOpNode mop, Node ptr, Node idx, int off, int imm) { super(mop,mop); assert ptr._type instanceof TypeMemPtr && !(ptr instanceof AddNode); @@ -20,6 +21,8 @@ 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 @@ -36,7 +39,7 @@ public abstract class MemOpARM extends MemOpNode implements MachNode { @Override public Type compute() { throw Utils.TODO(); } @Override public Node idealize() { throw Utils.TODO(); } - int size() { return 1<<_declaredType.log_size(); } + int size() { return _bytes; } // Wider mask to store both GPRs and FPRs @Override public RegMask regmap(int i) { diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java index c1aad15f..df2a6bc9 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java @@ -31,7 +31,8 @@ public class NewARM extends NewNode implements MachNode, RIPRelSize { } // General form: "alloc #bytes PC" @Override public void asm(CodeGen code, SB sb) { - sb.p("ldi x0=#1\n"); + sb.p("\n"); // Multi-op trigger + sb.p("ldi ").p(code._mach.regs()[_arg2Reg]).p(" = #1\n"); sb.p("call #calloc"); } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java index e1b3b3b6..c597b9dd 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java @@ -8,12 +8,14 @@ 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 <,<=,== - SetARM(Node cmp, String bop) { + final boolean _fp; // FP compare (unsigned encoding) + SetARM(Node cmp, String bop, boolean fp) { 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; } @@ -21,7 +23,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 = arm.cset(arm.OP_CSET,0b11111, arm.make_condition(_bop), 0b011111, enc.reg(this)); + int body = arm.cset(arm.OP_CSET,0b11111, arm.make_condition(_bop, _fp), 0b011111, enc.reg(this)); enc.add4(body); } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java index 5af45892..be975fb6 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java @@ -8,7 +8,7 @@ import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; public class SplitARM extends SplitNode { - SplitARM(String kind, byte round) { super(kind,round,new Node[2]);} + SplitARM(LRG lrg, String kind, byte round) { super(lrg,kind,round,new Node[2]);} @Override public RegMask regmap(int i) { return arm.SPLIT_MASK; } @Override public RegMask outregmap() { return arm.SPLIT_MASK; } @@ -24,7 +24,7 @@ public class SplitARM extends SplitNode { if(src >= arm.MAX_REG) { throw Utils.TODO(); } - int off = enc._fun.computeStackOffset(enc._code,dst); + int off = fun(enc).computeStackOffset(enc._code,dst); if( srcX ) src -= arm.D_OFFSET; enc.add4(arm.load_str_imm(arm.OP_STORE_IMM_64, off, arm.RSP, src, 8)); return; @@ -32,7 +32,7 @@ public class SplitARM extends SplitNode { if(src >= arm.MAX_REG) { // Load from SP - int off = enc._fun.computeStackOffset(enc._code,src); + int off = fun(enc).computeStackOffset(enc._code,src); if( dstX ) dst -= arm.D_OFFSET; enc.add4(arm.load_str_imm(arm.OP_LOAD_IMM_64, off, arm.RSP, dst, 8)); return; @@ -56,10 +56,4 @@ public class SplitARM extends SplitNode { enc.add4(arm.f_mov_general(arm.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/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java index 91385ac9..b11a2c89 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java @@ -25,15 +25,15 @@ public class StoreARM extends MemOpARM { private static final int[] OP_STORES = new int[]{ arm.OP_STORE_IMM_8, arm.OP_STORE_IMM_16, arm.OP_STORE_IMM_32, arm.OP_STORE_IMM_64, }; private int imm_op() { - return _declaredType == TypeFloat.F32 ? arm.OPF_STORE_IMM_32 - : _declaredType == TypeFloat.F64 ? arm.OPF_STORE_IMM_64 - : OP_STORES[_declaredType.log_size()]; + return val()!=null && val()._type instanceof TypeFloat + ? (_bytes==4 ? arm.OPF_STORE_IMM_32 : arm.OPF_STORE_IMM_64) + : OP_STORES[Integer.numberOfTrailingZeros(_bytes)]; } private static final int[] OP_STORE_RS = new int[]{ arm.OP_STORE_R_8, arm.OP_STORE_R_16, arm.OP_STORE_R_32, arm.OP_STORE_R_64, }; @Override public void encoding( Encoding enc ) { - ldst_encode(enc, imm_op(), OP_STORE_RS[_declaredType.log_size()], val(), size()); + ldst_encode(enc, imm_op(), OP_STORE_RS[Integer.numberOfTrailingZeros(_bytes)], val(), size()); } @Override public void asm(CodeGen code, SB sb) { asm_address(code,sb).p(","); diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java index 29d490ef..9d490c8b 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java @@ -1,28 +1,30 @@ package com.seaofnodes.simple.node.cpus.arm; import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.ConstantNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeFunPtr; import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; -public class TFPARM extends ConstantNode implements MachNode, RIPRelSize { - TFPARM( ConstantNode con ) { super(con); } +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; } @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(this); } + @Override public TFPARM copy() { return new TFPARM((TypeFunPtr)_con,_ext); } @Override public void encoding( Encoding enc ) { - enc.relo(this); - short self = enc.reg(this); + if( _ext==null ) enc.relo(this); // Internal patch + else enc.external(this,_ext); // ELF-file external patch + short dst = enc.reg(this); // adrp x0, 0 - int adrp = arm.adrp(1,0, arm.OP_ADRP, 0,self); + enc.add4(arm.adrp(1,0, arm.OP_ADRP, 0,dst)); // add x0, x0, 0 - enc.add4(adrp); - arm.imm_inst(enc,arm.OPI_ADD,0, 0); + arm.imm_inst(enc,arm.OPI_ADD,0, dst); } @Override public byte encSize(int delta) { @@ -33,15 +35,7 @@ public class TFPARM extends ConstantNode implements MachNode, RIPRelSize { @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { short rpc = enc.reg(this); if(opLen == 8 ) { - // 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, arm.adrp(1, adrp_delta & 0b11, 0b10000, adrp_delta >> 2, rpc)); - // low 12 bits via add - enc.patch4(opStart+4, arm.imm_inst_l(arm.OPI_ADD, delta & 0xfff, rpc)); + arm.patch_adrp_add(enc, opStart, delta, rpc); } else { // should not happen as one instruction is 4 byte, and TFP arm encodes 2. throw Utils.TODO(); @@ -50,7 +44,7 @@ public class TFPARM extends ConstantNode implements MachNode, RIPRelSize { @Override public void asm(CodeGen code, SB sb) { String reg = code.reg(this); - _con.print(sb.p(reg).p(" #")); + _type.print(sb.p(reg).p(" #")); } @Override public boolean eq(Node n) { return this==n; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java index cdcaa433..f86d85dc 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java @@ -9,13 +9,15 @@ import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; public class TMPARM extends ConstantNode implements MachNode, RIPRelSize { - TMPARM( ConstantNode con ) { super(con); } + final String _ext; + TMPARM( ConstantNode con, String ext ) { super(con); _ext = ext; } @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); } + @Override public TMPARM copy() { return new TMPARM(this,_ext); } @Override public void encoding( Encoding enc ) { + if( _ext!=null ) throw Utils.TODO(); enc.largeConstant(this,((TypeMemPtr)_con)._obj,0,-1); short dst = enc.reg(this); // adrp x0, 0 @@ -32,15 +34,7 @@ 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 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, arm.adrp(1, adrp_delta & 0b11, 0b10000, adrp_delta >> 2, dst)); - // low 12 bits via add - enc.patch4(opStart+4, arm.imm_inst_l(arm.OPI_ADD, delta & 0xfff, dst)); + arm.patch_adrp_add(enc, opStart, delta, dst); } else { throw Utils.TODO(); } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java index a5dac751..852fbdb1 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java @@ -466,9 +466,19 @@ public class arm extends Machine { // int l public static int adrp(int op, int imlo,int opcode, int imhi, int rd) { assert 0 <= rd && rd < 32; + assert 0 <= imlo && imlo <= 0x3; // 2 bits + assert 0 <= imhi && imhi <= 0x7FFFF; // 19 bits return (op << 31) | (imlo << 29) |(opcode << 24) | (imhi << 5) | rd; } + 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 , adrp(1, imm21 & 3, OP_ADRP, imm21 >>> 2, rd)); + enc.patch4(opStart+4, imm_inst_l(OPI_ADD, target & 0xFFF, rd)); + } + // [Rptr+Roff] public static int indr_adr(int opcode, int off, STORE_LOAD_OPTION option, int s, int ptr, int rt) { assert 0 <= ptr && ptr < 32; @@ -514,19 +524,31 @@ public class arm extends Machine { 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); - int body = f_cmp(0b00011110, 3, reg1, reg2); + int body = f_cmp(0b00011110, 3, reg2, reg1); enc.add4(body); } - public static COND make_condition(String bop) { - return switch (bop) { - case "==" -> COND.EQ; - case "!=" -> COND.NE; - case "<" -> COND.LT; - case "<=" -> COND.LE; - case ">=" -> COND.GE; - case ">" -> COND.GT; - default -> throw Utils.TODO(); + public static COND make_condition(String bop, boolean fp) { + // One of {N,Z,C} and !V + // OR { !N, !Z, C, V } + return fp + ? switch (bop) { // FP uses unsigned encodings + case "==" -> COND.EQ; // Z + case "!=" -> COND.NE; // !Z + case "<" -> COND.MI; // N + case "<=" -> COND.LS; // Z || !C + case ">=" -> COND.PL; // !N + case ">" -> COND.HI; // !Z && C + default -> throw Utils.TODO(); + } + : switch (bop) { + case "==" -> COND.EQ; // Z + case "!=" -> COND.NE; // !Z + case "<" -> COND.LT; // N != V + case "<=" -> COND.LE; // Z || N != V + case ">=" -> COND.GE; // N == V + case ">" -> COND.GT; // !Z && N == V + default -> throw Utils.TODO(); }; } @@ -594,10 +616,16 @@ 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=2; i<idx; i++ ) - if( tfp.arg(i-2) instanceof TypeFloat) + for( int i=hidden ? 1 : 0; i<idx-2; i++ ) + if( tfp.arg(i) instanceof TypeFloat) fcnt++; // Floats up to XMMS in XMM registers if( tfp.arg(idx-2) instanceof TypeFloat ) { @@ -605,24 +633,27 @@ public class arm extends Machine { return XMMS[fcnt]; } else { RegMask[] cargs = CALLINMASK; - if( idx-2-fcnt < cargs.length ) - return cargs[idx-2-fcnt]; + if( sigidx-fcnt < cargs.length ) + return cargs[sigidx-fcnt]; } // Pass on stack slot (8 and higher) if( maxArgSlot>0 ) throw Utils.TODO(); - return new RegMask(MAX_REG + 1 + (idx - 2)); + return new RegMask(MAX_REG + 1 + sigidx); } // 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=0; i<tfp.nargs(); i++ ) { + for( int i=hiddenSelf(tfp) ? 1 : 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 | @@ -640,7 +671,7 @@ 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(String kind, byte round, LRG lrg) { return new SplitARM(kind,round); } + @Override public SplitNode split(LRG lrg, String kind, byte round) { return new SplitARM(lrg,kind,round); } // Return a MachNode unconditional branch @Override public CFGNode jump() { @@ -650,40 +681,47 @@ 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 CastNode cast -> new CastMach(cast); + case CheckCastNode cast -> new CheckCastMach(cast); + case PtrToIntNode ptr -> new PtrToIntMach(ptr); + case GuardNode guard -> new GuardMach(guard); case CallEndNode cend-> new CallEndMach(cend); case CProjNode c -> new CProjNode(c); case ConstantNode con-> con(con); - case DivFNode divf -> new DivFARM(divf); - case DivNode div -> new DivARM(div); + case DivNode div -> div.mode()==2 ? new DivFARM(div) : new DivARM(div); + case EscapeNode esc -> new EscapeNode(esc); 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 -> new NegARM(neg); - case MulFNode mulf -> new MulFARM(mulf); - case MulNode mul -> new MulARM(mul); + 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 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 PhiNode phi -> new PhiNode(phi); - case ProjNode prj -> new ProjARM(prj); + case ProjNode prj -> prj(prj); case ReadOnlyNode read -> new ReadOnlyMach(read); case ReturnNode ret -> new RetARM(ret,ret.fun()); 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 SubFNode subf -> new SubFARM(subf); - case SubNode sub -> sub(sub); + case SubNode sub -> sub.mode()==2 ? new SubFARM(sub) : sub(sub); case ToFloatNode tfn -> new I2F8ARM(tfn); case XorNode xor -> xor(xor); @@ -695,7 +733,7 @@ public class arm extends Machine { private Node cmp(BoolNode bool){ Node cmp = _cmp(bool); - return new SetARM(cmp, bool.op()); + return new SetARM(cmp, bool.op(), cmp instanceof CmpFARM); } private Node _cmp(BoolNode bool) { if( bool.isFloat() ) @@ -714,13 +752,18 @@ 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 = "!="; - if( iff.in(1) instanceof BoolNode bool ) op = bool.op(); + boolean fp = false; + if( iff.in(1) instanceof BoolNode bool ) { op = bool.op(); fp = bool.isFloat(); } else if( iff.in(1)==null ) op = "=="; // Never-node cutout - else iff.setDef(1, new BoolNode.NE(iff.in(1), new ConstantNode(TypeInteger.ZERO))); - return new BranchARM(iff, op); + 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); } 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); @@ -733,22 +776,30 @@ public class arm extends Machine { } private Node con( ConstantNode con ) { - if( !con._con.isConstant() ) return new ConstantNode( con ); // Default unknown caller inputs + if( !con._con.isConstant() ) + return ConstantNode.raw(con); // Default unknown caller inputs + String ext = con instanceof ExternNode ext0 ? ext0._extern : null; return switch( con._con ) { - 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 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 TypeNil tn -> throw Utils.TODO(); // TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code. - case Type t -> t==Type.NIL ? new IntARM(con) : new ConstantNode(con); + case Type t -> t==Type.NIL ? new IntARM(con,ext) : ConstantNode.raw(con); }; } + private Node fptr( FunPtrNode con ) { + return new TFPARM(con); + } + private Node call(CallNode call){ - return call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp - ? new CallARM(call, tfp) - : new CallRRARM(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); } private Node or(OrNode or) { @@ -790,6 +841,10 @@ public class arm extends Machine { : new LsrARM(lsr); } + private Node prj(ProjNode prj) { + return prj.in(0) instanceof StartNode ? new ProjNode(prj) : new ProjARM(prj); + } + private static int off; private static Node idx; diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java index 2cd00845..1c157bc1 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java @@ -1,19 +1,19 @@ package com.seaofnodes.simple.node.cpus.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 ConstantNode implements MachNode, RIPRelSize { - AUIPC( Type tfp ) { super(tfp); } +public class AUIPC extends FunPtrNode implements MachNode, RIPRelSize { + AUIPC( FunPtrNode fptr ) { super(fptr); } @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(_con); } + @Override public AUIPC copy() { return new AUIPC(this); } @Override public String op() { return "auipc"; } @Override public void encoding( Encoding enc ) { enc.relo(this); @@ -36,7 +36,6 @@ public class AUIPC extends ConstantNode implements MachNode, RIPRelSize { @Override public void asm(CodeGen code, SB sb) { String reg = code.reg(this); sb.p(reg).p(" = PC+#"); - if( _con == null ) sb.p("---"); - else _con.print(sb); + _type.print(sb); } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java index bd5dabe5..8e1b4a48 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java @@ -14,8 +14,7 @@ public class CallRISC extends CallNode implements MachNode, RIPRelSize { assert tfp.isConstant(); _inputs.pop(); // Pop constant target _tfp = tfp; - FunNode fun = CodeGen.CODE.link(tfp); - _name = fun==null ? ((ExternNode)call.fptr())._extern : fun._name; // Can be null for extern calls + _name = CodeGen.CODE.funcName(tfp.fidx()); } @Override public String op() { return "call"; } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java index 298789e8..7369f9f5 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java @@ -13,7 +13,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 null; } + @Override public RegMask outregmap() { return riscv.RPC_MASK; } @Override public void encoding( Encoding enc ) { short rpc = enc.reg(this); diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java index 81726200..c1a9b018 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java @@ -8,12 +8,13 @@ import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; public class FltRISC extends ConstantNode implements MachNode, RIPRelSize { - FltRISC(ConstantNode con) { super(con); } + final String _ext; + FltRISC( ConstantNode con, String ext ) { super(con); _ext = ext; } @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); } + @Override public FltRISC copy() { return new FltRISC(this,_ext); } @Override public void encoding( Encoding enc ) { enc.largeConstant(this,_con, 0, -1); diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java index edd1850f..e6b4c4ed 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java @@ -4,21 +4,23 @@ 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 { - public Int8RISC(ConstantNode con) { super(con); } - + final String _ext; + Int8RISC( ConstantNode con, String ext ) { super(con); _ext = ext; } @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); } + @Override public Int8RISC copy() { return new Int8RISC(this,_ext); } @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/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java index f8af913a..b795ba96 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java @@ -10,7 +10,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 { - public IntRISC(ConstantNode con) { super(con); } + 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/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java index 7e08a01d..592cdb84 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java @@ -2,7 +2,9 @@ package com.seaofnodes.simple.node.cpus.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 @@ -21,6 +23,39 @@ 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/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java index b9e6e5ba..79f22fb8 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java @@ -10,6 +10,7 @@ import java.util.BitSet; 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; @@ -17,7 +18,8 @@ public abstract class MemOpRISC extends MemOpNode implements MachNode { _inputs.setX(3, null); // Never an index _inputs.setX(4, val ); _off = off; - _sz = (char)('0'+(1<<_declaredType.log_size())); + _bytes = mop instanceof StoreNode st ? st.storeSize() : 1<<_con.log_size(); + _sz = (char)('0'+_bytes); } @Override public String label() { return op(); } @@ -29,38 +31,7 @@ public abstract class MemOpRISC extends MemOpNode implements MachNode { @Override public Node idealize() { throw Utils.TODO(); } // func3 is based on load/store size and extend - 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; - } + abstract int func3(); // Register mask allowed on input i. @Override public RegMask regmap(int i) { diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java index 6c03bda6..4a6583ce 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java @@ -40,8 +40,9 @@ public class NewRISC extends NewNode implements MachNode, RIPRelSize { // General form: "alloc #bytes PC" @Override public void asm(CodeGen code, SB sb) { - sb.p("ldi a1=#1\n"); + sb.p("\n"); // Multi-op trigger + sb.p("ldi ").p(code._mach.regs()[_arg2Reg]).p(" = #1\n"); sb.p("auipc a2=#calloc\n"); - sb.p("call a2+#calloc, a0"); + sb.p("call a2+#calloc"); } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java index 6082cbbf..58fa8a4b 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java @@ -8,7 +8,7 @@ import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; public class SplitRISC extends SplitNode { - SplitRISC( String kind, byte round ) { super(kind,round, new Node[2]); } + SplitRISC( LRG lrg, String kind, byte round ) { super(lrg, 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 ) { @@ -24,7 +24,7 @@ public class SplitRISC extends SplitNode { if( src >= riscv.MAX_REG ) { throw Utils.TODO(); // Very rare stack-stack move } - int off = enc._fun.computeStackOffset(enc._code,dst); + int off = fun(enc).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)); @@ -32,7 +32,7 @@ public class SplitRISC extends SplitNode { } if( src >= riscv.MAX_REG ) { // Load from SP - int off = enc._fun.computeStackOffset(enc._code,src); + int off = fun(enc).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)); @@ -57,10 +57,4 @@ 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/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java index d06c0c35..ebd6a5ae 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java @@ -3,6 +3,7 @@ package com.seaofnodes.simple.node.cpus.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; @@ -39,6 +40,11 @@ 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/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java index 7e057507..1f269152 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java @@ -1,46 +1,46 @@ package com.seaofnodes.simple.node.cpus.riscv; import com.seaofnodes.simple.codegen.*; -import com.seaofnodes.simple.node.ConstantNode; -import com.seaofnodes.simple.node.MachNode; -import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeFunPtr; import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; -public class TFPRISC extends ConstantNode implements MachNode, RIPRelSize { - TFPRISC(ConstantNode con) { super(con); } +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; } @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(this); } + @Override public TFPRISC copy() { return new TFPRISC((TypeFunPtr)_con,_ext); } @Override public void encoding( Encoding enc ) { - enc.relo(this); + if( _ext==null ) enc.relo(this); // Internal patch + else enc.external(this,_ext); // ELF-file external patch // 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 enc.add4(riscv.i_type(riscv.OP_IMM, dst, 0, dst, 0)); } - @Override public byte encSize(int delta) { - if( -(1L<<11) <= delta && delta < (1L<<11) ) return 4; - throw Utils.TODO(); - } + @Override public byte encSize(int delta) { return 8; } // Delta is from opcode start @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 - int next = opStart + opLen; - enc.patch4(opStart,riscv.u_type(riscv.OP_AUIPC, rpc, delta>>12)); + enc.patch4(opStart ,riscv.u_type(riscv.OP_AUIPC, rpc, hi20)); // addi(low 12 bits) - enc.patch4(next,riscv.i_type(riscv.OP_IMM, rpc, 0, rpc, delta & 0xFFF)); + enc.patch4(opStart+4,riscv.i_type(riscv.OP_IMM, rpc, 0, rpc, lo12 & 0xFFF)); // addi } else { // should not happen as one instruction is 4 byte, and TFP arm encodes 2. @@ -49,7 +49,7 @@ public class TFPRISC extends ConstantNode implements MachNode, RIPRelSize { } @Override public void asm(CodeGen code, SB sb) { - _con.print(sb.p(code.reg(this)).p(" #")); + _type.print(sb.p(code.reg(this)).p(" #")); } @Override public boolean eq(Node n) { return this==n; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java index 44e73d1d..aa001e1e 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java @@ -6,15 +6,18 @@ import com.seaofnodes.simple.node.MachNode; import com.seaofnodes.simple.node.Node; import com.seaofnodes.simple.type.TypeMemPtr; import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.util.Utils; public class TMPRISC extends ConstantNode implements MachNode, RIPRelSize { - TMPRISC(ConstantNode con) { super(con); } + final String _ext; + TMPRISC( ConstantNode con, String ext ) { super(con); _ext = ext; } @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); } + @Override public TMPRISC copy() { return new TMPRISC(this,_ext); } @Override public void encoding( Encoding enc ) { + if( _ext!=null ) throw Utils.TODO(); enc.largeConstant(this,((TypeMemPtr)_con)._obj,0,-1); short dst = enc.reg(this); // AUIPC dst,#hi20_constant_pool diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java index 7a6361bc..078a7693 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java @@ -13,7 +13,6 @@ 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; @@ -32,24 +31,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 +90,7 @@ public class riscv extends Machine { public static RegMask FA6_MASK = new RegMask(FA6); public static RegMask FA7_MASK = new RegMask(FA7); - // Int arguments calling conv + // Int/ptr arguments calling convention static RegMask[] CALLINMASK = new RegMask[] { A0_MASK, A1_MASK, @@ -257,10 +256,16 @@ 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=2; i<idx; i++ ) - if( tfp.arg(i-2) instanceof TypeFloat ) + for( int i=hidden ? 1 : 0; i<idx-2; i++ ) + if( tfp.arg(i) instanceof TypeFloat ) fcnt++; // Floats up to XMMS in XMM registers if( tfp.arg(idx-2) instanceof TypeFloat ) { @@ -268,23 +273,26 @@ public class riscv extends Machine { return XMMS[fcnt]; } else { RegMask[] cargs = CALLINMASK; - if( idx-2-fcnt < cargs.length ) - return cargs[idx-2-fcnt]; + if( sigidx-fcnt < cargs.length ) + return cargs[sigidx-fcnt]; } // Pass on stack slot(8 and higher) if( maxArgSlot>0 ) throw Utils.TODO(); - return new RegMask(MAX_REG + 1 + (idx - 2)); + return new RegMask(MAX_REG + 1 + sigidx); } @Override public short maxArgSlot( TypeFunPtr tfp ) { int icnt=0, fcnt=0; // Count of ints, floats - for( int i=0; i<tfp.nargs(); i++ ) { + for( int i=hiddenSelf(tfp) ? 1 : 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) | @@ -306,7 +314,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(String kind, byte round, LRG lrg) { return new SplitRISC(kind,round); } + @Override public SplitNode split(LRG lrg, String kind, byte round ) { return new SplitRISC(lrg, kind,round); } // True if signed 12-bit immediate public static boolean imm12(TypeInteger ti) { @@ -323,28 +331,34 @@ 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 CastNode cast -> new CastMach(cast); + case CheckCastNode cast -> new CheckCastMach(cast); + case PtrToIntNode ptr -> new PtrToIntMach(ptr); + case GuardNode guard -> new GuardMach(guard); case CallEndNode cend -> new CallEndMach(cend); case CProjNode c -> new CProjNode(c); case ConstantNode con -> con(con); - case DivFNode divf -> new DivFRISC(divf); - case DivNode div -> new DivRISC(div); + case DivNode div -> div.mode()==2 ? new DivFRISC(div) : new DivRISC(div); + case EscapeNode esc -> new EscapeNode(esc); 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 -> new NegRISC(neg); - case MulFNode mulf -> new MulFRISC(mulf); - case MulNode mul -> new MulRISC(mul); + 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 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 PhiNode phi -> new PhiNode(phi); case ProjNode prj -> prj(prj); case ReadOnlyNode read-> new ReadOnlyMach(read); @@ -352,11 +366,12 @@ 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 SubFNode subf -> new SubFRISC(subf); - case SubNode sub -> sub(sub); + case SubNode sub -> sub.mode()==2 ? new SubFRISC(sub) : sub(sub); case ToFloatNode tfn -> i2f8(tfn); case XorNode xor -> xor(xor); @@ -366,11 +381,9 @@ 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); @@ -388,8 +401,10 @@ public class riscv extends Machine { } private Node call(CallNode call) { - return call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp - ? new CallRISC(call, tfp) + return call.fptr() instanceof FunPtrNode fptr + ? new CallRISC(call, (TypeFunPtr)fptr._type) + : call.fptr() instanceof ConstantNode con + ? new CallRISC(call, (TypeFunPtr)con._type) : new CallRRISC(call); } private Node nnn(NewNode nnn) { @@ -428,9 +443,13 @@ public class riscv extends Machine { } private Node con( ConstantNode con ) { - if( !con._con.isConstant() ) return new ConstantNode( con ); // Default unknown caller inputs + if( !con._con.isConstant() ) + return ConstantNode.raw(con); // Default unknown caller inputs + String ext = con instanceof ExternNode ext0 ? ext0._extern : null; 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); @@ -443,18 +462,22 @@ 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); + yield new Int8RISC(con,null); } // Load from constant pool - case TypeFloat tf -> new FltRISC(con); - case TypeFunPtr tfp -> new TFPRISC(con); - case TypeMemPtr tmp -> new TMPRISC(con); + case TypeFloat tf -> new FltRISC(con,ext); + case TypeMemPtr tmp -> new TMPRISC(con,ext); + case TypeFunPtr tfp -> new TFPRISC(tfp,ext); case TypeNil tn -> throw Utils.TODO(); // TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code. - case Type t -> t==Type.NIL ? new IntRISC(con) : new ConstantNode(con); + case Type t -> t==Type.NIL ? new IntRISC(con) : ConstantNode.raw(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 @@ -509,7 +532,7 @@ public class riscv extends Machine { } private Node prj(ProjNode prj) { - return new ProjRISC(prj); + return prj.in(0) instanceof StartNode ? new ProjNode(prj) : new ProjRISC(prj); } private Node ld(LoadNode ld) { diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java index 2513de37..093e2bd2 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java @@ -6,7 +6,7 @@ import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; public class AddFMemX86 extends MemOpX86 { - AddFMemX86( AddFNode add, LoadNode ld , Node base, Node idx, int off, int scale, Node val ) { + AddFMemX86( AddNode 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/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java index 460f568b..14b8e4da 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java @@ -20,7 +20,7 @@ public class CallRX86 extends CallNode implements MachNode { // calls the function in the register short src = enc.reg(fptr()); enc.add1(0xFF); - enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.INDIRECT,2,src)); + enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT,2,src)); } @Override public void asm(CodeGen code, SB sb) { diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java index d74e3833..989895b2 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java @@ -8,21 +8,27 @@ 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; - FunNode fun = CodeGen.CODE.link(tfp); - _name = fun==null ? ((ExternNode)call.fptr())._extern : fun._name; // Can be null for extern calls + _name = CodeGen.CODE.funcName(tfp.fidx()); + _extern = CodeGen.CODE.externFunc(tfp.fidx()) != null; } @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 x86_64_v2.callInMask(_tfp,i,fun()._maxArgSlot); } + @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 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/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java index 11f900ed..24812fea 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java @@ -25,7 +25,7 @@ public class CmpMemX86 extends MemOpX86 { if(src == -1) { assert !_swap; - int log = _declaredType.log_size(); + int log = _con.log_size(); x86_64_v2.rexF(-1, ptr, idx, log==3, enc); switch(log) { case 0: enc.add1(0x80); break; @@ -41,7 +41,7 @@ public class CmpMemX86 extends MemOpX86 { case 3: enc.add8(_imm); break; } } else { - encVal(enc, _declaredType, ptr, idx, src, _off, _scale, _swap); + encVal(enc, _con, ptr, idx, src, _off, _scale, _swap); } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java index 9bcbac8f..15e95374 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java @@ -3,17 +3,20 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; 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; - FltX86(ConstantNode con ) { super(con); } + final String _ext; + FltX86( ConstantNode con, String ext ) { super(con); _ext = ext; } @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); } + @Override public Node copy() { return new FltX86(this,_ext); } @Override public void encoding( Encoding enc ) { + if( _ext!=null ) throw Utils.TODO(); // Load the constant into a FPR // movsd xmm, [rip + 0] // F2 0F 10 /r MOVSD xmm1, m64 diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java index cbd5fdbf..1724109a 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java @@ -10,6 +10,7 @@ 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/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java index 3f2f696c..1973ea4e 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java @@ -5,15 +5,17 @@ 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 { - IntX86( ConstantNode con ) { super(con); } + final String _ext; + IntX86( ConstantNode con, String ext ) { super(con); _ext = ext; } @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); } + @Override public Node copy() { return new IntX86(this,_ext); } @Override public RegMask regmap(int i) { return null; } @Override public RegMask outregmap() { return x86_64_v2.WMASK; } // Zero-set uses XOR kills flags @@ -33,6 +35,7 @@ public class IntX86 extends ConstantNode implements MachNode { return; } + if( _ext!=null ) throw Utils.TODO(); // Simply move the constant into a GPR // Conditional encoding based on 64 or 32 bits //REX.W + C7 /0 id MOV r/m64, imm32 diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java index 441cf693..c55002c1 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java @@ -12,7 +12,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 x86_64_v2.MEM_MASK; } + @Override public RegMask outregmap() { return _con instanceof TypeFloat ? x86_64_v2.XMASK : x86_64_v2.WMASK; } @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 @@ -20,7 +20,7 @@ public class LoadX86 extends MemOpX86 { short dst = enc.reg(this ); short ptr = enc.reg(ptr()); short idx = enc.reg(idx()); - enc(enc, _declaredType, dst, ptr, idx, _off, _scale); + enc(enc, _con, 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/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java index 5f090717..6069c07b 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java @@ -41,7 +41,8 @@ public abstract class MemOpX86 extends MemOpNode implements MachNode { _off = off; _scale = scale; _imm = imm; - _sz = (char)('0'+(1<<_declaredType.log_size())); + int size = mop instanceof StoreNode st ? st.storeSize() : 1<<_con.log_size(); + _sz = (char)('0'+size); } // Store-based flavors have a value edge diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java index 280b8185..1e9e708f 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java @@ -19,7 +19,7 @@ public class MulIX86 extends MachConcreteNode implements MachNode { @Override public final void encoding( Encoding enc ) { short dst = enc.reg(this ); // Also src1 short src = enc.reg(in(1)); - enc.add1(x86_64_v2.rex(src, dst, 0)); + enc.add1(x86_64_v2.rex(dst, src, 0)); // opcode; 0x69 or 0x6B enc.add1( 0x69 + (x86_64_v2.imm8(_imm) ? 2 : 0) ); enc.add1( x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src) ); diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java index fc896dce..40a38809 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java @@ -18,7 +18,8 @@ public class NewX86 extends NewNode implements MachNode { } // General form: "alloc #bytes PC" @Override public void asm(CodeGen code, SB sb) { - sb.p("ldi rcx = #1\n"); + sb.p("\n"); // Multi-op trigger + sb.p("ldi ").p(code._mach.regs()[_arg2Reg]).p(" = #1\n"); sb.p("call #calloc"); } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java index 4a14240a..bc3d421d 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java @@ -11,8 +11,7 @@ import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; public class SplitX86 extends SplitNode { - SplitX86( String kind, byte round ) { super(kind,round, new Node[2]); } - @Override public String op() { return "mov"; } + SplitX86( LRG lrg, String kind, byte round ) { super(lrg,kind,round, new Node[2]); } @Override public RegMask regmap(int i) { return x86_64_v2.SPLIT_MASK; } @Override public RegMask outregmap() { return x86_64_v2.SPLIT_MASK; } @@ -49,10 +48,10 @@ public class SplitX86 extends SplitNode { // Stack spills if( dst >= x86_64_v2.MAX_REG ) { - int off = enc._fun.computeStackOffset(enc._code,dst); + int off = fun(enc).computeStackOffset(enc._code,dst); if( src >= x86_64_v2.MAX_REG ) { // Rare stack-stack move. push [RSP+soff]; pop [RSP+doff] - int soff = enc._fun.computeStackOffset(enc._code,src); + int soff = fun(enc).computeStackOffset(enc._code,src); enc.add1(0xFF); x86_64_v2.indirectAdr(0, (short)-1/*index*/, (short)x86_64_v2.RSP, soff, 6, enc); enc.add1(0x8F); @@ -63,7 +62,7 @@ public class SplitX86 extends SplitNode { return; } if( src >= x86_64_v2.MAX_REG ) { - int off = enc._fun.computeStackOffset(enc._code,src); + int off = fun(enc).computeStackOffset(enc._code,src); LoadX86.enc(enc, dstX ? TypeFloat.F64 : TypeInteger.BOT, dst, (short)x86_64_v2.RSP, (short)-1, off, 0); return; } @@ -98,10 +97,4 @@ public class SplitX86 extends SplitNode { enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, 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/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java index 4403b02b..faedeb8b 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java @@ -31,7 +31,7 @@ public class StoreX86 extends MemOpX86 { short src = enc.reg(val()); if( src == -1 ) { - int log = _declaredType.log_size(); + int log = Integer.numberOfTrailingZeros(_sz-'0'); x86_64_v2.rexF(-1, ptr, idx, log==3, enc); switch( log ) { case 0: enc .add1(0xC6); break; @@ -47,7 +47,7 @@ public class StoreX86 extends MemOpX86 { case 3: enc.add4(_imm); break; // Limit of a 4 byte immediate } } else { - encVal(enc,_declaredType,ptr,idx,src,_off,_scale); + encVal(enc,_con,ptr,idx,src,_off,_scale); } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java index 359b6d83..8d8d3950 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java @@ -2,54 +2,51 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; 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 ConstantNode implements MachNode, RIPRelSize { - TFPX86( ConstantNode con ) { super(con); } - @Override public String op() { - return _con == Type.NIL ? "xor" : "ldx"; - } +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(); } + // The ideal FunPtrNode is pinned because its Return input is a lifetime + // hook. After selection this is an ordinary cloneable load-address + // instruction and must be scheduled down near its actual use. + @Override public boolean isPinned() { return false; } @Override public boolean isClone() { return true; } - @Override public Node copy() { return new TFPX86(this); } + @Override public TFPX86 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 ) { - short dst = enc.reg(this); - // Short form for zero - if( _con==Type.NIL ) { - // XOR dst,dst. Can skip REX is dst is low 8, makes this a 32b - // xor, which will also zero the high bits. - if( dst >= 8 ) enc.add1(x86_64_v2.rex(dst, dst, 0)); - enc.add1(0x33); // opcode - enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, dst)); - return; - } - + if( _ext==null ) enc.relo(this); // Internal patch + else enc.external(this,_ext); // ELF-file external patch // lea to load function pointer address // lea rax, [rip+disp32] - enc.relo(this); - enc.add1(x86_64_v2.rex(dst, 0, 0)); + short dst = enc.reg(this); + // 0 or 1 for REX depending on the dst. + // Zero/sign extend should be fine, so not wide. + _opLen = (byte)(6+ x86_64_v2.rexF(dst, 0, 0, true, enc)); enc.add1(0x8D); // opcode enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.INDIRECT, dst, 0b101)); enc.add4(0); } - @Override public byte encSize(int delta) { - return 7; - } + @Override public byte encSize(int delta) { return _opLen; } - // Delta is from opcode start + // Delta is from opcode start, X86 expects it from opcode end @Override public void patch( Encoding enc, int opStart, int opLen, int delta ) { - enc.patch4(opStart + 3, delta - 7); + enc.patch4(opStart + opLen - 4, delta - opLen); } // Human-readable form appended to the SB. Things like the encoding, @@ -57,11 +54,9 @@ public class TFPX86 extends ConstantNode 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) { - String reg = code.reg(this); - if( _con == Type.NIL ) - sb.p(reg).p(",").p(reg); - else - _con.print(sb.p(reg).p(" = #")); + sb.p(code.reg(this)); + if( _ext == null ) _type.print(sb.p(" #")); + else sb.p(_ext); } @Override public boolean eq(Node n) { return this==n; } } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java index c23f6c5f..cc8741b3 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java @@ -4,32 +4,33 @@ import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeMemPtr; import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.util.Utils; public class TMPX86 extends ConstantNode implements MachNode, RIPRelSize{ - TMPX86(ConstantNode con ) { super(con); } + private byte _opLen; + final String _ext; + TMPX86( ConstantNode con, String ext ) { super(con); _ext = ext; } @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); } + @Override public Node copy() { return new TMPX86(this,_ext); } @Override public void encoding( Encoding enc ) { + if( _ext!=null ) throw Utils.TODO(); short dst = enc.reg(this); - x86_64_v2.rexF(dst, 0, 0, true, enc); + // 0 or 1 for REX depending on the dst. + _opLen = (byte)(6+ x86_64_v2.rexF(dst, 0, 0, true, enc)); enc.add1(0x8D).add1(x86_64_v2.modrm(x86_64_v2.MOD.INDIRECT, dst, 0b101)).add4(0); - 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*/); + enc.largeConstant(this,((TypeMemPtr)_con)._obj, _opLen-4, 2/*ELF encoding PC32*/); } // Delta is from opcode start - @Override public byte encSize(int delta) { return (byte)7; } + @Override public byte encSize(int delta) { return _opLen; } // 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/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java index 026e12aa..a1ad17dc 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java @@ -70,7 +70,7 @@ public class x86_64_v2 extends Machine { public static int REX_WB = 0x49; public enum MOD { - INDIRECT, // [mem] + INDIRECT, // [mem] INDIRECT_disp8, // [mem + 0x12] INDIRECT_disp32, // [mem + 0x12345678] DIRECT, // mem @@ -188,22 +188,29 @@ 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( CodeGen.CODE._callingConv ) { + return switch( conv ) { 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 switch( CodeGen.CODE._callingConv ) { + return maxArgSlot(tfp,CodeGen.CODE._callingConv); + } + @Override public short maxArgSlot( TypeFunPtr tfp, String conv ) { + return switch( conv ) { case "SystemV" -> maxArgSlotSys5 (tfp); - case "Win64" -> maxArgSlotWin64(tfp); + case "win64" -> maxArgSlotWin64(tfp); default -> throw Utils.TODO(); }; } @@ -236,14 +243,22 @@ 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). - if( idx >= 6 ) - return new RegMask(MAX_REG+maxArgSlot+(idx-2)); + int sigidx = idx-2; + if( hiddenSelf(tfp) ) { + if( sigidx==0 ) return null; + sigidx--; + } + if( sigidx >= 4 ) + return new RegMask(MAX_REG+maxArgSlot+sigidx); return tfp.arg(idx-2) instanceof TypeFloat - ? XMMS8 [idx-2] - : WIN64_CALL[idx-2]; + ? XMMS8 [sigidx] + : WIN64_CALL[sigidx]; } static short maxArgSlotWin64(TypeFunPtr tfp) { - return (short)tfp.nargs(); + 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); } // Sys5: max 6 GPRs and 8 FPRS filled first. Extra args land in increasing @@ -277,9 +292,14 @@ 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=2; i<idx; i++ ) { - if( tfp.arg(i-2) instanceof TypeFloat ) fcnt++; + for( int i=hiddenSelf(tfp) ? 1 : 0; i<idx-2; i++ ) { + if( tfp.arg(i) instanceof TypeFloat ) fcnt++; else icnt++; } int nstk = Math.max(icnt-6,0)+Math.max(fcnt-8,0); @@ -289,13 +309,16 @@ public class x86_64_v2 extends Machine { } static short maxArgSlotSys5(TypeFunPtr tfp) { int icnt=0, fcnt=0; // Count of ints, floats - for( int i=0; i<tfp.nargs(); i++ ) { + for( int i=hiddenSelf(tfp) ? 1 : 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) | @@ -314,12 +337,18 @@ public class x86_64_v2 extends Machine { (1L<<XMM4) | (1L<<XMM5) ; @Override public long callerSave() { - return switch (CodeGen.CODE._callingConv) { + return callerSave(CodeGen.CODE._callingConv); + } + @Override public long callerSave(String conv) { + return switch (conv) { case "SystemV" -> SYSTEM5_CALLER_SAVE; - case "Win64" -> WIN64_CALLER_SAVE; - default -> throw new IllegalArgumentException("Unknown calling convention: " + CodeGen.CODE._callingConv); + case "win64" -> WIN64_CALLER_SAVE; + default -> throw new IllegalArgumentException("Unknown calling convention: " + conv); }; } + @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; @@ -328,8 +357,8 @@ public class x86_64_v2 extends Machine { // Create a split op; any register to any register, including stack slots - @Override public SplitNode split(String kind, byte round, LRG lrg) { - return new SplitX86(kind, round); + @Override public SplitNode split(LRG lrg, String kind, byte round ) { + return new SplitX86(lrg,kind, round); } // Return a MachNode unconditional branch @@ -341,28 +370,34 @@ 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 CProjNode c -> new CProjNode(c); case CallEndNode cend -> new CallEndMach(cend); case CallNode call -> call(call); - case CastNode cast -> new CastMach(cast); + case CheckCastNode cast -> new CheckCastMach(cast); + case PtrToIntNode ptr -> new PtrToIntMach(ptr); + case GuardNode guard -> new GuardMach(guard); case ConstantNode con -> con(con); - case DivFNode divf -> new DivFX86(divf); - case DivNode div -> new DivX86(div); + case DivNode div -> div.mode()==2 ? new DivFX86(div) : new DivX86(div); + case EscapeNode esc -> new EscapeNode(esc); 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 -> new NegX86(neg); - case MulFNode mulf -> new MulFX86(mulf); - case MulNode mul -> mul(mul); + 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 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 PhiNode phi -> new PhiNode(phi); case ProjNode prj -> prj(prj); case ReadOnlyNode read-> new ReadOnlyMach(read); @@ -370,13 +405,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 SubFNode subf -> new SubFX86(subf); - case SubNode sub -> sub(sub); + case SubNode sub -> sub.mode()==2 ? new SubFX86(sub) : sub(sub); case ToFloatNode tfn -> i2f8(tfn); - case XCtrlNode x -> new ConstantNode(Type.XCONTROL); + case XCtrlNode x -> ConstantNode.raw(Type.XCONTROL); case XorNode xor -> xor(xor); case LoopNode loop -> new LoopNode(loop); @@ -391,15 +427,12 @@ 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) { -// throw Utils.TODO(); // Swap load sides -// } - // 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 ) { @@ -422,17 +455,6 @@ 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) ) @@ -456,8 +478,10 @@ public class x86_64_v2 extends Machine { } private Node call(CallNode call) { - return call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp - ? new CallX86(call, tfp) + 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) : new CallRX86(call); } @@ -482,11 +506,11 @@ 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) && + 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 ); @@ -504,18 +528,23 @@ public class x86_64_v2 extends Machine { private Node con( ConstantNode con ) { if( !con._con.isConstant() ) - return new ConstantNode(con); // Default unknown caller inputs + return ConstantNode.raw(con); // Default unknown caller inputs + String ext = con instanceof ExternNode ext0 ? ext0._extern : null; return switch (con._con) { - 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 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 TypeNil tn -> throw Utils.TODO(); // TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code. - case Type t -> t == Type.NIL ? new IntX86(con) : new ConstantNode(con); + case Type t -> t == Type.NIL ? new IntX86(con,null) : ConstantNode.raw(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); @@ -528,7 +557,9 @@ 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), new ConstantNode(TypeInteger.ZERO))); + 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 JmpX86(iff, op); } @@ -549,7 +580,7 @@ public class x86_64_v2 extends Machine { } private Node prj( ProjNode prj ) { - return new ProjX86(prj); + return prj.in(0) instanceof StartNode ? new ProjNode(prj) : new ProjX86(prj); } private Node sar(SarNode sar) { diff --git a/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java b/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java index a9f86ce8..206983a0 100644 --- a/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java +++ b/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java @@ -1,130 +1,126 @@ package com.seaofnodes.simple.print; +import com.seaofnodes.simple.codegen.CompUnit; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.codegen.Encoding.Relo; import com.seaofnodes.simple.codegen.Encoding; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; +import com.seaofnodes.simple.util.*; +import java.util.HashMap; import java.util.HashSet; public abstract class ASMPrinter { - public static SB print(SB sb, CodeGen code) { - if( code._cfg==null ) - return sb.p("Need _cfg set, run after GCM"); + public static SB print(SB sb, CodeGen code ) { + Encoding enc = code._encoding; + if( enc == null ) return sb.p("No encoding for ").p(code._srcName); // instruction address int iadr = 0; - for( int i=0; i<code._cfg._len; i++ ) - if( code._cfg.at(i) instanceof FunNode fun ) - iadr = print(iadr,sb,code,fun,i); + // Print all functions in order + for( int i=0; i<enc._cfg._len; i++ ) + if( enc._cfg.at(i) instanceof FunNode fun ) + iadr = print(iadr,sb,code,enc,fun,i); - // constant pool + // constant pools iadr = (iadr+15)&-16; // pad to 16 - Encoding enc = code._encoding; - if( enc!=null && !enc._bigCons.isEmpty() && iadr < enc._bits.size() ) { - // radix sort the big constants by alignment - Ary<Relo>[] raligns = new Ary[5]; - for( Node op : enc._bigCons.keySet() ) { - Relo relo = enc._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); - } + if( enc._cpool.size()>0 ) + iadr = printConstantPool(iadr, sb, enc._cpool,enc._bigCons,true ,"Constant Pool"); - HashSet<Type> targets = new HashSet<>(); - sb.p("--- Constant Pool ------").nl(); - - // By alignment - for( int align = 4; align >= 0; align-- ) { - Ary<Relo> relos = raligns[align]; - if( relos == null ) continue; - for( Relo relo : relos ) { - if( targets.contains(relo._t) ) continue; - targets.add(relo._t); - sb.hex2(iadr).p(" "); - switch( relo._t ) { - case TypeTuple tt -> { - for( Type tx : tt._types ) { - pN(enc,sb,iadr,align).p(" "); - iadr += (1<<align); - } - } - - case TypeStruct ts -> { - int sz = ts.size(); - int log = 1<<align; - sz = (sz + (log -1)) & -log; // Round up final padding - for( int i=0; i<sz; i++ ) - sb.hex1(enc.read1(iadr++)); - } + iadr = (iadr+15)&-16; // pad to 16 + if( enc._sdata.size()>0 ) + iadr = printConstantPool(iadr, sb,enc._sdata,enc._bigCons,false, "Static Data" ); + return sb; + } + + + static int printConstantPool( int iadr, SB sb, BAOS bits, HashMap<Node,Relo> bigCons, boolean ro, String msg ) { + sb.p("--- ").p(msg).p(" ------").nl(); + // radix sort the big constants by alignment + Ary<Relo>[] raligns = new Ary[5]; + for( Node op : bigCons.keySet() ) { + Relo relo = bigCons.get(op); + // non-constant structs in the r/w data, everything else in r/o data + if( (relo._t instanceof TypeStruct ts && !ts.isConstant()) == ro ) + continue; + int align = relo._t.alignment(); + Ary<Relo> relos = raligns[align]==null ? (raligns[align]=new Ary<>( Relo.class)) : raligns[align]; + relos.add(relo); + } + + HashSet<Type> targets = new HashSet<>(); + + // By alignment + int dadr=0; + for( int align = 4; align >= 0; align-- ) { + Ary<Relo> relos = raligns[align]; + if( relos == null ) continue; + for( Relo relo : relos ) { + if( targets.contains(relo._t) ) continue; + targets.add(relo._t); + sb.hex2(iadr+dadr).p(" "); + if( relo._t instanceof TypeStruct ts ) { + int sz = ts.size(); + int log = 1<<align; + sz = (sz + (log -1)) & -log; // Round up final padding + for( int i=0; i<sz; i++ ) + sb.hex1(bits.read1(dadr++)); + } else { // Simple primitive (e.g. larger int, float) - default -> { - pN(enc,sb,iadr,align).fix(9-(1<<align),""); - iadr += (1<<align); - } - } - relo._t.print(sb).nl(); + pN(bits,sb,dadr,align).fix(9-(1<<align),""); + dadr += (1<<align); } + relo._t.print(sb.p('\t')).nl(); } } - - return sb; + return iadr+dadr; } - private static SB pN( Encoding enc, SB sb, int iadr, int log ) { + + private static SB pN( BAOS bits, SB sb, int dadr, int log ) { switch( log ) { - case 0: sb.hex1(enc.read1(iadr)); break; - case 1: sb.hex2(enc.read2(iadr)); break; - case 2: sb.hex4(enc.read4(iadr)); break; - case 3: sb.hex8(enc.read8(iadr)); break; + case 0: sb.hex1(bits.read1(dadr)); break; + case 1: sb.hex2(bits.read2(dadr)); break; + case 2: sb.hex4(bits.read4(dadr)); break; + case 3: sb.hex8(bits.read8(dadr)); break; } return sb; } - private static int print(int iadr, SB sb, CodeGen code, FunNode fun, int cfgidx) { - FunNode old=null; - if( code._encoding!=null ) { - old = code._encoding._fun; - code._encoding._fun = fun; // Useful printing after RA - } + private static int print(int iadr, SB sb, CodeGen code, Encoding enc, FunNode fun, int cfgidx) { // Function header sb.nl().p("---"); if( fun._name != null ) sb.p(fun._name).p(" "); fun.sig().print(sb); sb.p("---------------------------").nl(); - if( code._encoding!=null && code._encoding._padFunHeads ) - iadr = (iadr + 15)&-16; // All function entries padded to 16 align + iadr = (iadr + 15)&-16; // All function entries padded to 16 align if( fun._frameAdjust != 0 ) - iadr = doInst(iadr,sb,code,fun,cfgidx,fun,true,true); - while( !(code._cfg.at(cfgidx) instanceof ReturnNode) ) - iadr = doBlock(iadr,sb,code,fun,cfgidx++); + iadr = doInst(iadr,sb,code,enc, cfgidx,fun,true,enc!=null); + while( !(enc._cfg.at(cfgidx) instanceof ReturnNode) ) + iadr = doBlock(iadr,sb,code,enc,fun,cfgidx++); // Function separator sb.p("---"); fun.sig().print(sb); sb.p("---------------------------").nl(); - if( code._encoding != null ) - code._encoding._fun = old; return iadr; } static private final int opWidth = 5; static private final int argWidth = 30; - static int doBlock(int iadr, SB sb, CodeGen code, FunNode fun, int cfgidx) { + static int doBlock(int iadr, SB sb, CodeGen code, Encoding enc, FunNode fun, int cfgidx) { final int encWidth = code._mach==null ? 2 : code._mach.defaultOpSize()*2; - CFGNode bb = code._cfg.at(cfgidx); + CFGNode bb = enc._cfg.at(cfgidx); if( bb != fun && !(bb instanceof IfNode) && !(bb instanceof CallEndNode) && !(bb instanceof CallNode) && !(bb instanceof CProjNode && bb.in(0) instanceof CallEndNode )) sb.p(label(bb)).p(":").nl(); if( bb instanceof CallNode ) return iadr; - final boolean postAlloc = code._phase.ordinal() > CodeGen.Phase.RegAlloc.ordinal(); final boolean postEncode= code._phase.ordinal() >=CodeGen.Phase.Encoding.ordinal(); + final boolean postAlloc = code._phase.ordinal() > CodeGen.Phase.RegAlloc.ordinal() || + (code._phase.ordinal() == CodeGen.Phase.RegAlloc.ordinal() && code._regAlloc.done()); boolean once=false; for( Node n : bb.outs() ) { @@ -133,10 +129,10 @@ public abstract class ASMPrinter { // Post-RegAlloc phi prints all on one line if( postAlloc ) { if( !once ) { once=true; sb.fix(4," ").p(" ").fix(encWidth,"").p(" "); } - sb.p(phi._label).p(':').p(code.reg(phi,fun)).p(','); + sb.p(phi._label).p(':').p(code.reg(phi)).p(','); } else { // Pre-RegAlloc phi prints one line per - sb.fix(4," ").p(" ").fix(encWidth,"").p(" ").fix(opWidth,phi._label).p(" ").p(code.reg(phi,fun)); + sb.fix(4," ").p(" ").fix(encWidth,"").p(" ").fix(opWidth,phi._label).p(" ").p(code.reg(phi)); if( phi.getClass() == PhiNode.class ) { sb.p(" = phi( "); for( int i=1; i<phi.nIns(); i++ ) @@ -151,16 +147,17 @@ public abstract class ASMPrinter { // All the non-phis for( int i=0; i<bb.nOuts(); i++ ) if( !(bb.out(i) instanceof PhiNode) ) - iadr = doInst(iadr, sb,code, fun, cfgidx, bb.out(i),postAlloc, postEncode ); + iadr = doInst(iadr, sb,code, enc, cfgidx, bb.out(i),postAlloc, postEncode ); return iadr; } - static int doInst( int iadr, SB sb, CodeGen code, FunNode fun, int cfgidx, Node n, boolean postAlloc, boolean postEncode ) { + static int doInst( int iadr, SB sb, CodeGen code, Encoding enc, int cfgidx, Node n, boolean postAlloc, boolean postEncode ) { if( n==null || n instanceof CProjNode ) return iadr; if( postAlloc && n instanceof CalleeSaveNode ) return iadr; if( postEncode && n instanceof ProjNode ) return iadr; if( n instanceof MemMergeNode ) return iadr; + if( n instanceof EscapeNode ) return iadr; if( n.getClass() == ConstantNode.class ) return iadr; // Default placeholders final int dopz = code._mach==null ? 2 : code._mach.defaultOpSize(); final int encWidth = dopz*2; @@ -170,8 +167,8 @@ public abstract class ASMPrinter { // need to assume a jump. There's no real hardware op here, yet. if( n instanceof RegionNode cfg && !(n instanceof FunNode) ) { if( postEncode ) return iadr; // All jumps inserted already - while( cfgidx < code._cfg._len-1 ) { - CFGNode next = code._cfg.at(++cfgidx); + while( cfgidx < enc._cfg._len-1 ) { + CFGNode next = enc._cfg.at(++cfgidx); if( next == n ) return iadr; // Fall-through, no branch if( next.nOuts()>1 ) break; // Has code in the block, need to jump around @@ -185,7 +182,9 @@ public abstract class ASMPrinter { // get indent slightly and just print their index & node# if( n instanceof ProjNode proj ) { if( proj._type instanceof TypeMem ) return iadr; // Nothing for the hidden ones - sb.fix(4," ").p(" ").fix(encWidth,"").p(" ").fix(opWidth,proj._label==null ? "---" : proj._label).p(" ").p(code.reg(n,fun)).nl(); + sb.fix(4," ").p(" ").fix(encWidth,"").p(" ").fix(opWidth,code.reg(n)).p(" // "); + if( proj._label != null ) sb.p(proj._label); + sb.nl(); return iadr; } @@ -195,15 +194,15 @@ public abstract class ASMPrinter { // Encoding int fatEncoding = 0; - if( code._encoding != null && code._encoding._opLen!=null ) { - int size = code._encoding._opLen[n._nid]; + if( enc != null ) { + int size = enc.opLen(n); if( code._asmLittle ) for( int i=0; i<Math.min(size,dopz); i++ ) - sb.hex1(code._encoding._bits.buf()[iadr++]); + sb.hex1(enc._bits.buf()[iadr++]); else { iadr += Math.min(size,dopz); for( int i=0; i<Math.min(size,dopz); i++ ) - sb.hex1(code._encoding._bits.buf()[iadr-i-1]); + sb.hex1(enc._bits.buf()[iadr-i-1]); } for( int i=size*2; i<encWidth; i++ ) sb.p(" "); @@ -244,16 +243,16 @@ public abstract class ASMPrinter { sb.nl(); // Printing more op bits than fit - if( isMultiOp != null && code._encoding != null && code._encoding._opLen!=null ) { + if( isMultiOp != null ) { // Multiple ops, template style, no RA, no scheduling. Print out // one-line-per-newline, with encoding bits up front. - int size = code._encoding._opLen[n._nid]; + int size = enc != null ? enc.opLen(n) : 0; int off = Math.min(size,dopz); while( isMultiOp!=null ) { sb.hex2(iadr).p(" "); int len = Math.min(size-off,dopz); for( int i=0; i<len; i++ ) - sb.hex1(code._encoding._bits.buf()[iadr++]); + sb.hex1(enc._bits.buf()[iadr++]); off += len; for( int i=len; i<dopz; i++ ) sb.p(" "); sb.p(" "); @@ -273,7 +272,7 @@ public abstract class ASMPrinter { // themselves. X86 special for super long encodings sb.hex2(iadr).p(" "); for( int i=0; i<fatEncoding; i++ ) - sb.hex1(code._encoding._bits.buf()[iadr++]); + sb.hex1(enc._bits.buf()[iadr++]); sb.nl(); } @@ -282,7 +281,7 @@ public abstract class ASMPrinter { if( !(n instanceof CFGNode) && n instanceof MultiNode ) { for( Node proj : n._outputs ) { if( proj instanceof ProjNode ) // it could also be an ante-dep - doInst(iadr,sb,code,fun, cfgidx, proj,postAlloc,postEncode); + doInst(iadr,sb,code,enc, cfgidx, proj,postAlloc,postEncode); } } @@ -294,13 +293,10 @@ public abstract class ASMPrinter { } private static String isMultiOp(SB sb, int old, int len) { - for( int i=old; i<len; i++ ) - if( sb.at(i)=='\n' ) { - String s = sb.subString(i+1,len); - sb.setLen(i); - return s; - } - return null; + if( old==len || sb.at(old)!='\n' ) return null; + String rest = sb.subString(old+1,len); + sb.setLen(old); + return rest; } } diff --git a/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java b/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java index 20c8439a..ad2df8b6 100644 --- a/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java +++ b/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java @@ -1,6 +1,7 @@ package com.seaofnodes.simple.print; import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.codegen.CompUnit; import com.seaofnodes.simple.node.*; import java.util.*; import static com.seaofnodes.simple.util.Utils.TODO; @@ -21,16 +22,16 @@ public class GraphVisualizer { public GraphVisualizer(boolean separateControlCluster) { this._separateControlCluster = separateControlCluster; } public GraphVisualizer() { this(false); } - public String generateDotOutput(Parser parse) { return generateDotOutput(parse._code._stop,parse._scope,parse._xScopes); } - public String generateDotOutput(StopNode stop, Node scope, Stack<ScopeNode> xScopes) { + public String generateDotOutput(Parser parse) { return generateDotOutput(parse._ref,parse._scope,parse._xScopes); } + public String generateDotOutput(CompUnit cu, Node scope, Stack<ScopeNode> xScopes) { // Since the graph has cycles, we need to create a flat list of all the // nodes in the graph. - Collection<Node> all = findAll(xScopes, stop, scope); + Collection<Node> all = findAll(xScopes, cu._stop, scope); StringBuilder sb = new StringBuilder(); sb.append("digraph chapter18 {\n"); sb.append("/*\n"); - sb.append(stop._src); + sb.append(cu._src); sb.append("\n*/\n"); // To keep the Scopes below the graph and pointing up into the graph we @@ -80,7 +81,7 @@ public class GraphVisualizer { // Just the Nodes first, in a cluster no edges sb.append(doCtrl ? "\tsubgraph cluster_Controls {\n" : "\tsubgraph cluster_Nodes {\n"); // Magic "cluster_" in the subgraph name for (Node n : all) { - if (n instanceof ProjNode || n instanceof CProjNode || n instanceof MemMergeNode || n==Parser.XCTRL) + if( n instanceof ProjNode || n instanceof CProjNode || n instanceof MemMergeNode || n instanceof XCtrlNode ) continue; // Do not emit, rolled into MultiNode or Scope cluster already if( _separateControlCluster && doCtrl && !(n instanceof CFGNode) ) continue; if( _separateControlCluster && !doCtrl && (n instanceof CFGNode) ) continue; @@ -162,7 +163,7 @@ public class GraphVisualizer { sb.append("\tsubgraph cluster_").append(scopeName).append(" {\n"); // Magic "cluster_" in the subgraph name // Special for memory ScopeMinNode if( level==0 ) { - MemMergeNode n = scope.mem(); + Node n = scope.mem(); sb.append("\t\t").append(n.uniqueName()).append(" [label=\"").append(n.glabel()).append("\"];\n"); } sb.append("\t\t").append(scopeName).append(" [label=<\n"); @@ -195,7 +196,7 @@ public class GraphVisualizer { // Do not display the Constant->Start edge; // ProjNodes handled by Multi; // ScopeNodes are done separately - if( n instanceof ConstantNode || n instanceof ProjNode || n instanceof CProjNode || n instanceof ScopeNode || n==Parser.XCTRL ) + if( n instanceof ConstantNode || n instanceof ProjNode || n instanceof CProjNode || n instanceof ScopeNode || n instanceof XCtrlNode ) continue; for( int i=0; i<n.nIns(); i++ ) { Node def = n.in(i); diff --git a/src/main/java/com/seaofnodes/simple/print/IRPrinter.java b/src/main/java/com/seaofnodes/simple/print/IRPrinter.java index d1265bef..7c0f84f3 100644 --- a/src/main/java/com/seaofnodes/simple/print/IRPrinter.java +++ b/src/main/java/com/seaofnodes/simple/print/IRPrinter.java @@ -1,14 +1,12 @@ package com.seaofnodes.simple.print; import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.Serialize; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeFunPtr; 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; + +import java.util.*; public abstract class IRPrinter { @@ -37,295 +35,138 @@ public abstract class IRPrinter { return sb.p("\n"); } - - public static String prettyPrint(Node node, int depth) { - return CodeGen.CODE._phase.ordinal() > CodeGen.Phase.Schedule.ordinal() - ? _prettyPrintScheduled( node, depth ) - : _prettyPrint( node, depth ); - } - - // Another bulk pretty-printer. Makes more effort at basic-block grouping. - private static String _prettyPrint( Node node, int depth ) { - // First, a Breadth First Search at a fixed depth. - BFS bfs = new BFS(node,depth); - // Convert just that set to a post-order - ArrayList<Node> rpos = new ArrayList<>(); - BitSet visit = new BitSet(); - for( int i=bfs._lim; i< bfs._bfs.size(); i++ ) - postOrd( bfs._bfs.get(i), null, rpos, visit, bfs._bs); - // Reverse the post-order walk + // Bulk whole program pretty print + public static String prettyPrint( CodeGen code ) { + // Loop-tree for nodeOrder + CFGNode.LoopTree old = code._start._ltree; + if( old==null ) + code._start.buildLoopTree( code._linker, code._stop); + Ary<Node> nodes = Serialize.nodeOrder(code); + if( old == null ) + code._start._ltree = null; SB sb = new SB(); - boolean gap=false; - for( int i=rpos.size()-1; i>=0; i-- ) { - Node n = rpos.get(i); - if( n instanceof CFGNode || n instanceof MultiNode ) { - if( !gap ) sb.p("\n"); // Blank before multihead - if( n instanceof FunNode fun ) - fun.sig().print(sb.p("--- ").p(fun._name==null ? "" : fun._name).p(" ")).p("----------------------\n"); - printLine( n, sb ); // Print head - while( --i >= 0 ) { - Node t = rpos.get(i); - if( !(t.in(0) instanceof MultiNode) ) { i++; break; } - printLine( t, sb ); - } - if( n instanceof ReturnNode ret ) { - FunNode fun = ret.fun(); - sb.p("--- "); - if( fun != null ) - fun.sig().print(sb.p(fun._name==null ? "" : fun._name).p(" ")); - sb.p("----------------------\n"); - } - if( !(n instanceof CallNode) ) { - sb.p("\n"); // Blank after multitail - gap = true; - } - } else { - printLine( n, sb ); - gap = false; + Node prior=null; + for( Node n : nodes ) { + if( n instanceof FunNode fun ) { + sb.nl().p("--- "); + fun.sig().print(sb.p(fun._name==null ? "" : fun._name).p(" ")); + sb.p("----------------------\n"); } + if( n instanceof MultiNode || n instanceof RegionNode || n instanceof CallNode || + (isMultiChild(prior) && !isMultiChild(n)) ) + sb.nl(); + printLine(n,sb); + if( n instanceof ReturnNode ret ) + sb.p("--- ").p(ret._fun._name==null ? "" : ret._fun._name).p(" ----------------------\n"); + prior = n; } return sb.toString(); } - private static void postOrd(Node n, Node prior, ArrayList<Node> rpos, BitSet visit, BitSet bfs) { - if( !bfs.get(n._nid) ) - return; // Not in the BFS visit - if( n instanceof FunNode && !(prior instanceof StartNode) ) - return; // Only visit Fun from Start - if( visit.get(n._nid) ) return; // Already post-order walked - visit.set(n._nid); - // First walk the CFG, then everything - if( n instanceof CFGNode ) { - for( Node use : n._outputs ) - // Follow CFG, not across call/function borders, and not around backedges - if( use instanceof CFGNode && !(n instanceof CallNode && use instanceof FunNode) && - use.nOuts() >= 1 && !(use._outputs.get(0) instanceof LoopNode) ) - postOrd(use, n, rpos,visit,bfs); - for( Node use : n._outputs ) - // Follow CFG, not across call/function borders - if( use instanceof CFGNode && !(n instanceof CallNode && use instanceof FunNode) ) - postOrd(use,n,rpos,visit,bfs); - } - // Follow all outputs - for( Node use : n._outputs ) - if( use != null && - !(n instanceof CallNode && use instanceof FunNode) && - (n instanceof FunNode || !(use instanceof ParmNode)) ) - postOrd(use, n, rpos,visit,bfs); - // Post-order - rpos.add(n); + private static boolean isMultiHead(Node n) { + return n instanceof RegionNode || n instanceof MultiNode; } - // Breadth-first search, broken out in a class to keep in more independent. - // Maintains a root-set of Nodes at the limit (or past by 1 if MultiHead). - public static class BFS { - // A breadth first search, plus MultiHeads for any MultiTails - public final ArrayList<Node> _bfs; - public final BitSet _bs; // Visited members by node id - public final int _depth; // Depth limit - public final int _lim; // From here to _bfs._len can be roots for a reverse search - public BFS( Node base, int d ) { - _depth = d; - _bfs = new ArrayList<>(); - _bs = new BitSet(); - - add(base); // Prime the pump - int idx=0, lim=1; // Limit is where depth counter changes - while( idx < _bfs.size() ) { // Ran out of nodes below depth - Node n = _bfs.get(idx++); - for( Node def : n._inputs ) - if( def!=null && !_bs.get(def._nid) ) - add(def); - if( idx==lim ) { // Depth counter changes at limit - if( --d < 0 ) - break; // Ran out of depth - lim = _bfs.size(); // New depth limit - } - } - // Toss things past the limit except multi-heads - while( idx < _bfs.size() ) { - Node n = _bfs.get(idx); - if( n instanceof MultiNode ) idx++; - else del(idx); - } - // Root set is any node with no inputs in the visited set - lim = _bfs.size(); - for( int i=_bfs.size()-1; i>=0; i-- ) - if( !any_visited(_bfs.get(i)) ) - swap( i,--lim); - _lim = lim; - } - void swap( int x, int y ) { - if( x==y ) return; - Node tx = _bfs.get(x); - Node ty = _bfs.get(y); - _bfs.set(x,ty); - _bfs.set(y,tx); - } - void add(Node n) { - _bfs.add(n); - _bs.set(n._nid); - } - void del(int idx) { - _bs.clear(_bfs.get(idx)._nid); - Utils.del(_bfs, idx); - } - boolean any_visited( Node n ) { - for( Node def : n._inputs ) - if( def!=null && _bs.get(def._nid) ) - return true; - return false; - } + private static boolean isMultiChild(Node n) { + return n!=null && n.nIns() > 0 && n.in(0) != null && isMultiHead(n.in(0)); } - public static String _prettyPrint( CodeGen code ) { - SB sb = new SB(); - // Print the Start "block" - printLine(code._start,sb); - for( Node n : code._start._outputs ) - printLine(n,sb); - sb.nl(); - // Skip start, stop - for( int i=1; i<code._cfg._len-1; i++ ) { - CFGNode blk = code._cfg.at(i); - if( blk.blockHead() && (!(blk instanceof CProjNode) || (blk.cfg0() instanceof IfNode )) ) { - if( blk instanceof FunNode fun ) - sb.p("--- ").p(fun.label()).p(" ---------------------------").nl(); - // Print block header - sb.p("%-13.13s".formatted(label(blk)+":")); - sb.p( " ".repeat(4) ).p(" [[ "); - if( blk instanceof RegionNode ) - for( int j=1; j<blk.nIns(); j++ ) - label(sb,blk.cfg(j)); - else - label(sb,blk.cfg(0)); - sb.p(" ]] \n"); - } - printLine(blk,sb); - if( blk instanceof ReturnNode ret ) - sb.p("--- ").p(ret.fun().label()).p(" ---------------------------").nl(); + // ---------------------------------------- + // Another bulk pretty-printer. Makes more effort at basic-block grouping. + public static String prettyPrint(Node node, int depth) { + // Convert just that set to a post-order + Ary<Node> post = new Ary<>(Node.class); + Ary<Node> rets = new Ary<>(Node.class); + var visit = new IdentityHashMap<Node,Integer>(); + assert depth < 5000; + postOrd( node, 0, depth, visit, post, rets); + // Function calls all print independent + while( rets.size()>0 ) + postOrd( rets.pop(), 0, depth, visit, post, rets ); - // Block contents - for( Node n : blk._outputs ) { - if( n instanceof CFGNode cfg ) continue; - printLine(n,sb); - if( !(n instanceof CFGNode) && n instanceof MultiNode ) - for( Node use : n._outputs ) - if( use instanceof ProjNode ) - printLine(use,sb); + + // Reverse the post-order walk + SB sb = new SB(); + for( int i=0; i<post._len; i++ ) { + Node n = post.at(i); + // Split ahead of a Region/MultiNode + if( n instanceof RegionNode || n instanceof MultiNode || + (i>0 && isMultiChild(post.at(i-1)) && !isMultiChild(n)) ) + sb.nl(); + if( n instanceof FunNode fun ) + fun.sig().print(sb.p("--- ").p(fun._name==null ? "" : fun._name).p(" ")).p("----------------------\n"); + printLine( n, sb ); // Print head + if( n instanceof ReturnNode ret ) { + FunNode fun = ret.fun(); + sb.p("--- ").p(fun==null ? "" : fun._name).p("----------------------\n"); } } - - printLine(code._stop,sb); return sb.toString(); } - // Bulk pretty printer, knowing scheduling information is available - private static String _prettyPrintScheduled( Node node, int depth ) { - // Backwards DFS walk to depth. - HashMap<Integer,Integer> ds = new HashMap<>(); - Ary<Node> ns = new Ary<>(Node.class); - _walk(ds,ns,node,depth); - // Remove data projections, these are force-printed behind their multinode head - for( int i=0; i<ns.size(); i++ ) { - if( ns.get(i) instanceof ProjNode proj && !(proj.in(0) instanceof CFGNode) ) { - ns.del(i--); - ds.remove(proj._nid); - } - } - // Print by block with least idepth - SB sb = new SB(); - Ary<Node> bns = new Ary<>(Node.class); - while( !ds.isEmpty() ) { - CFGNode blk = null; - for( Node n : ns ) { - CFGNode cfg = n instanceof CFGNode cfg0 && cfg0.blockHead() ? cfg0 : n.cfg0(); - if( blk==null || cfg.idepth() < blk.idepth() ) - blk = cfg; - } - Integer d = ds.remove(blk._nid); - ns.del(ns.find(blk)); - // Print block header - sb.p("%-13.13s".formatted(label(blk)+":")); - sb.p( " ".repeat(4) ).p(" [[ "); - if( blk instanceof StartNode ) ; - else if( blk instanceof RegionNode || blk instanceof StopNode ) - for( int i=(blk instanceof StopNode ? 3 : 1); i<blk.nIns(); i++ ) - label(sb,blk.cfg(i)); - else - label(sb,blk.cfg(0)); - sb.p(" ]] \n"); - printLine(blk,sb); + // keep the largest PO depth?; probably want *shortest* + private static void postOrd( Node n, int d, int cutoff, IdentityHashMap<Node,Integer> visit, Ary<Node> post, Ary<Node> rets ) { + if( n==null ) return; + if( d >= cutoff ) return; // Too deep + Integer depth = visit.get(n); + if( depth!=null ) return; // Been there, done that + + // Not a multi-child (Phi of a Region or Proj of a MultiNode) + if( !isMultiHead(n) && !isMultiChild(n) ) { + visit.put(n,d); // Visit node N and depth D + for( Node def : n._inputs ) // Children before visit + postOrd(def, d+1, cutoff, visit, post, rets); + post.add(n); // Post-order add + return; + } - // Collect block contents that are in the depth limit - bns.clear(); - int xd = Integer.MAX_VALUE; - for( Node use : blk._outputs ) { - Integer i = ds.get(use._nid); - if( i!=null && !(use instanceof CFGNode cfg && cfg.blockHead()) ) { - if( bns.find(use)==-1 ) - bns.add(use); - xd = Math.min(xd,i); + // Multi-child; print the parent and all children together + Node multi = isMultiHead(n) ? n : n.in(0); + // Multi + children are visited all at once + visit.put(multi,d+1); + for( Node out : multi.outs() ) + visit.put(out,d); + + // Order to visit all the children + Node[] outs = multi.outs().asAry(); + Arrays.sort(outs, Comparator.comparingInt( IRPrinter::sortOrder ) ); + + // Do not walk out of Functions, this makes functions stand-alone + // but otherwise unordered + if( !(multi instanceof FunNode fun && fun._folding) ) { + // Visit all children inputs all at once + for( Node out : outs ) { + // A FunNode is both a multi-head AND a multi-child from Start + if( !isMultiHead(out) && out != null ) { + for( Node def : out._inputs ) { + postOrd(def, d+1, cutoff, visit, post, rets); + } } } - // Print Phis up front, if any - for( int i=0; i<bns.size(); i++ ) - if( bns.get(i) instanceof PhiNode phi ) - printLine( phi, sb,bns,i--,ds,ns); - - // Print block contents in depth order, bumping depth until whole block printed - for( ; !bns.isEmpty(); xd++ ) - for( int i=0; i<bns.size(); i++ ) { - Node n = bns.get(i); - if( ds.get(n._nid)==xd ) { - printLine( n, sb, bns, i--, ds,ns ); - if( n instanceof MultiNode && !(n instanceof CFGNode) ) { - for( Node use : n._outputs ) { - if( use instanceof ProjNode ) - printLine(use,sb,bns,bns.indexOf(use),ds,ns); - } - } - } + // Visit multi inputs + for( Node def : multi._inputs ) { + if( multi instanceof CallEndNode && def instanceof ReturnNode ret && !ret._fun._folding ) { + rets.add(ret); // But save the Return for a separate function print + } else { + postOrd(def, d+2, cutoff, visit, post, rets); } - sb.p("\n"); + } } - return sb.toString(); - } - private static void _walk( HashMap<Integer,Integer> ds, Ary<Node> ns, Node node, int d ) { - Integer nd = ds.get(node._nid); - if( nd!=null && d <= nd ) return; // Been there, done that - Integer old = ds.put(node._nid,d) ; - if( old == null ) - ns.add(node); - if( d == 0 ) return; // Depth cutoff - for( Node def : node._inputs ) - if( def != null && - !(node instanceof LoopNode loop && loop.back()==def) && - // Don't walk into or out of functions - !(node instanceof CallEndNode && def instanceof ReturnNode) && - !(node instanceof FunNode && def instanceof CallNode) && - !(node instanceof ParmNode && !(def instanceof FunNode)) - ) - _walk(ds,ns,def,d-1); + // Post visit the multi and all children + post.add(multi); + for( Node out : outs ) + if( !isMultiHead(out) ) + post.add(out); } - static String label( CFGNode blk ) { - if( blk instanceof StartNode ) return "START"; - return (blk instanceof LoopNode ? "LOOP" : "L")+blk._nid; - } - static void label( SB sb, CFGNode blk ) { - if( !blk.blockHead() ) blk = blk.cfg(0); - sb.p( "%-9.9s ".formatted( label( blk ) ) ); - } - static void printLine( Node n, SB sb, Ary<Node> bns, int i, HashMap<Integer,Integer> ds, Ary<Node> ns ) { - printLine( n, sb ); - if( i != -1 ) bns.del(i); - ds.remove(n._nid); - int idx = ns.find(n); - if( idx!=-1 ) ns.del(idx); + static int sortOrder( Node n ) { + if( n instanceof ProjNode proj ) return proj._idx; + if( n instanceof PhiNode phi ) return phi._nid; + if( n == null ) return 0; + return n._nid+1000000; } } diff --git a/src/main/java/com/seaofnodes/simple/print/JSViewer.java b/src/main/java/com/seaofnodes/simple/print/JSViewer.java index 62ff0001..0f34af2e 100644 --- a/src/main/java/com/seaofnodes/simple/print/JSViewer.java +++ b/src/main/java/com/seaofnodes/simple/print/JSViewer.java @@ -202,11 +202,11 @@ public class JSViewer implements AutoCloseable { int lexStart = level==0 ? 0 : scope._kinds.at(level-1)._lexSize; // Special for memory ScopeMinNode - MemMergeNode n = scope.nIns()>1 ? scope.mem() : null; - if( level==0 && n!=null && n.nIns()>2 ) { + Node n = scope.nIns()>1 ? scope.mem() : null; + if( level==0 && n instanceof MemMergeNode merge && n.nIns()>2 ) { sb.i().p("<TR>"); for( int m=2; m<n.nIns(); m++ ) - cell(sb,"#"+m,n.alias(m),"m"+m); + cell(sb,"#"+m,merge.alias(m),"m"+m); sb.p("</TR>\n"); // End scope level } @@ -346,7 +346,7 @@ public class JSViewer implements AutoCloseable { sb.i().p(scopeName).p(" -> ").p(n.uniqueName()).p(" [ style=dashed color=grey];\n"); } // Force functions above return - if( n instanceof ReturnNode ret && ret.inProgress() && !ret.fun().isDead() ) + if( n instanceof ReturnNode ret && !ret.fun().isDead() ) sb.i().p(ret.ctrl().uniqueName()).p(" -> ").p(ret.fun().uniqueName()).p("[style=invis]\n"); } } @@ -376,8 +376,7 @@ public class JSViewer implements AutoCloseable { } // Memory - if( scope.nIns()>1 ) { - MemMergeNode n = scope.mem(); + if( scope.nIns()>1 && scope.mem() instanceof MemMergeNode n ) { for( int i=2; i<n.nIns(); i++ ) { Node def = n.in(i); while( def instanceof ScopeNode lazy ) diff --git a/src/main/java/com/seaofnodes/simple/type/Field.java b/src/main/java/com/seaofnodes/simple/type/Field.java index b2b2deca..b6ea5843 100644 --- a/src/main/java/com/seaofnodes/simple/type/Field.java +++ b/src/main/java/com/seaofnodes/simple/type/Field.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple.type; import com.seaofnodes.simple.util.Ary; +import com.seaofnodes.simple.util.AryInt; +import com.seaofnodes.simple.util.BAOS; import com.seaofnodes.simple.util.SB; import java.util.*; @@ -8,6 +10,7 @@ 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. @@ -17,29 +20,25 @@ 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 must be written to exactly once, no more, no less + // Field is only written in the constructor public boolean _final; - // 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 one ) { super(TFLD); init(fname,type,alias,xfinal,one); } - private Field init(String fname, Type type, int alias, boolean xfinal, boolean one ) { + private Field(String fname, Type type, int alias, boolean xfinal ) { super(TFLD); init(fname,type,alias,xfinal); } + private Field init(String fname, Type type, int alias, boolean xfinal ) { _fname = fname; _t = type; _alias = alias; _final = xfinal; - _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 one ) { - return FREE.isEmpty() ? new Field(fname,type,alias,xfinal,one) : FREE.pop().init(fname,type,alias,xfinal,one); + private static Field malloc(String fname, Type type, int alias, boolean xfinal ) { + return FREE.isEmpty() ? new Field(fname,type,alias,xfinal) : FREE.pop().init(fname,type,alias,xfinal); } // Malloc-from - public Field malloc( ) { return malloc(_fname,null,_alias,_final,_one); } + public Field malloc( ) { return malloc(_fname,null,_alias,_final); } @Override Field free(Type t) { Field f = (Field)t; assert !f.isFree() && !f._terned; @@ -52,26 +51,26 @@ 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, boolean one ) { - Field f = malloc(fname,type,alias,xfinal,one); + public static Field make( String fname, Type type, int alias, boolean xfinal ) { + Field f = malloc(fname,type,alias,xfinal); 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,_one); + return type == _t ? this : make(_fname,type,_alias,_final); } public Field makeFrom( boolean xfinal ) { - return xfinal == _final ? this : make(_fname,_t,_alias,xfinal,_one); + return xfinal == _final ? this : make(_fname,_t,_alias,xfinal); } // Cycle-making-breaking void setType(Type t) { assert _t ==null; _t = t; } - 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 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 void gather(ArrayList<Type> ts) { ts.add(TEST); ts.add(TEST2); ts.add(FLT); } @Override Field xmeet( Type that ) { @@ -81,17 +80,16 @@ 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; - assert _one ==fld._one ; - return make(_fname, _t.meet(fld._t ),_alias,_final | fld._final, _one); + return make(_fname, _t.meet(fld._t ), _alias, _final | fld._final); } @Override - Field xdual() { return malloc(_fname, _t.dual(),_alias,!_final,_one); } + Field xdual() { return malloc(_fname, _t.dual(),_alias,!_final); } @Override Field rdual() { if( _dual!=null ) return dual(); assert !_terned; - Field d = malloc(_fname,null,_alias,!_final,_one); + Field d = malloc(_fname,null,_alias,!_final); (_dual = d)._dual = this; // Cross link duals d._t = _t._terned ? _t.dual() : _t.rdual(); return d; @@ -99,38 +97,52 @@ 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,_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 _makeRO() { return _final ? this : make(_fname, _t._makeRO(),_alias,true); } + @Override boolean _isGLB(boolean mem) { return _t._isGLB(true); } + @Override public Type _glb(boolean mem) { return make(_fname,_t._glb(true),_alias,true); } + @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 _close() { return makeFrom( _t._close()); } // Override in subclasses - int hash() { return _fname.hashCode() ^ _t.hashCode() ^ _alias ^ (_final ? 1024 : 0) ^ (_one ? 2048 : 0); } + int hash() { return _fname.hashCode() ^ _t.hashCode() ^ _alias ^ (_final ? 1024 : 0); } private boolean static_eq( Field f ) { - return _fname.equals(f._fname) && _alias==f._alias && _final==f._final && _one==f._one && _t !=null && f._t !=null; + return _fname.equals(f._fname) && _alias==f._alias && _final==f._final && _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 int nkids() { return 1; } - @Override Type at( int idx ) { return _t; } - @Override void set( int idx, Type t ) { _t = 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 + @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)); + } + 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); + } @Override public SB _print( SB sb, BitSet visit, boolean html ) { - sb.p(_final?"":"!").p(_one?"$":"").p(_fname).p(":").p(_alias).p(" : "); + sb.p(_final?"":"!").p(_fname).p(":").p(_alias).p(" : "); return _t ==null ? sb.p("---") : _t.print(sb,visit,html); } - @Override public String str() { return (_final?"":"!")+(_one?"$":"")+_fname; } + @Override public String str() { return (_final?"":"!")+_fname; } } diff --git a/src/main/java/com/seaofnodes/simple/type/Type.java b/src/main/java/com/seaofnodes/simple/type/Type.java index 2d5a6d04..fd3a7c59 100644 --- a/src/main/java/com/seaofnodes/simple/type/Type.java +++ b/src/main/java/com/seaofnodes/simple/type/Type.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple.type; +import com.seaofnodes.simple.codegen.GlobalBits; import com.seaofnodes.simple.util.*; import java.util.*; @@ -56,34 +57,38 @@ 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 = 6; // End of the Simple Types + static final byte TSIMPLE = 5; // End of the Simple Types - static final byte TPTR = 7; // All nil-able scalar values + 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 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 =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 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 TSTRUCT =16; // Structs; tuples with named fields - static final byte TFLD =17; // Named fields in structs + static final byte TTUPLE =17; // Tuples; finite collections of unrelated Types, kept in parallel + + static final byte TMAX =18; // 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_simple() { return _type <= TSIMPLE; } + public boolean is_nokids() { return _type < TCYCLIC; } 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 @@ -97,6 +102,7 @@ public class Type /*implements Cloneable*/ { ts.add(BOTTOM); ts.add(CONTROL); ts.add(NIL); + TypeScalar.gather(ts); TypeNil.gather(ts); TypePtr.gather(ts); TypeInteger.gather(ts); @@ -135,7 +141,7 @@ public class Type /*implements Cloneable*/ { // Factory method which interns "this" @SuppressWarnings("unchecked") - <T extends Type> T intern() { + public <T extends Type> T intern() { //assert check(); assert !_terned; // Do not ask for already-interned T t2 = (T)INTERN.get(this); @@ -216,16 +222,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 _type < TCYCLIC; return eq(t); } + boolean cycle_eq(Type t) { assert is_nokids(); 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 - 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 + public Type at( int idx ) { throw Utils.TODO(); } + public void set( int idx, Type t ) { throw Utils.TODO(); } + public int nkids() { return 0; } // Number of kids // Clear and re-insert the basic Type INTERN table @@ -254,6 +260,7 @@ 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 @@ -264,7 +271,14 @@ 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); - return Type.BOTTOM; // Mixing 2 unrelated types + // TypeConAry meet its element type is OK + if( this instanceof TypeConAry tcon && tcon.elemT() == t._type ) return tcon.ymeet(t); + if( t instanceof TypeConAry tcon && tcon.elemT() == _type ) return tcon.ymeet(this); + // 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; } // Compute meet right now. Overridden in subclasses. @@ -277,8 +291,14 @@ public class Type /*implements Cloneable*/ { if( _type==TTOP || t._type==TBOT ) return t; // RHS TypeNil vs NIL/XNIL - if( _type== TNIL ) return t instanceof TypeNil ptr ? ptr.meet0() : (t._type==TXNIL ? TypePtr.PTR : BOTTOM); - if( _type== TXNIL ) return t instanceof TypeNil ptr ? ptr.meetX() : (t._type== TNIL ? TypePtr.PTR : BOTTOM); + if( _type==TNIL || _type==TXNIL ) { + if( t instanceof TypeNil ptr ) return _type==TNIL ? ptr.meet0() : ptr.meetX(); + if( t instanceof TypeScalar scalar ) { + if( t._type==TSCALAR ) return t.isHigh() ? this : t; + return TypeScalar.BOT; + } + return t._type==(_type==TNIL ? TXNIL : TNIL) ? TypePtr.PTR : BOTTOM; + } // 'this' is only {TCTRL,TXCTRL} // Other non-simple RHS things bottom out @@ -316,19 +336,15 @@ public class Type /*implements Cloneable*/ { } // True if this "isa" t; e.g. 17 isa TypeInteger.BOT - // Applies for pessimistic case - public boolean isa( Type t ) { return meet(t)==t; } + 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 TypePtr.NPTR; } + public Type nonZero() { return this==NIL ? TypePtr.XNPTR : TypePtr.NPTR; } // Make a zero version of this type, 0 for integers and null for pointers. public Type makeZero() { return Type.NIL; } - // Is forward-reference - public boolean isFRef() { return false; } + //// Is forward-reference + //public boolean isFRef() { return false; } // Cap at limits public Type oob() { return isHigh() ? TOP : BOTTOM; } @@ -337,7 +353,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 accumilate types without + // recursive descent visit of all types; this will accumulate 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 @@ -350,7 +366,8 @@ public class Type /*implements Cloneable*/ { // "C0<->D0" cycle. Type recurOpen() { assert VISIT.isEmpty(); VISIT.put(0L,BOTTOM); return this; } - Type recurClose() { + Type recurClose() { return recurClose(null); } + Type recurClose(Type[] types) { VISIT.remove(0L); // Just ignore the sentinel Ary<Type> ts = new Ary<>(Type.class); ts.addAll(VISIT.values()); @@ -374,6 +391,9 @@ 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 ) @@ -415,10 +435,12 @@ public class Type /*implements Cloneable*/ { free(this); } - Type free(Type free) { return this; } + Type free(Type free) { + return this; + } boolean isFree() { return false; } - <T extends Type> T delayFree(Type free) { + final <T extends Type> T delayFree(Type free) { assert !free._terned; FREES.push(free); return (T)this; @@ -427,7 +449,7 @@ public class Type /*implements Cloneable*/ { final Type install() { if( this instanceof TypeStruct ) { Type x = _intern(); // Stop the recursion - assert x==this; + if( x!=this ) return x.delayFree(this); } int nkids = nkids(); for( int i=0; i<nkids; i++ ) { @@ -440,7 +462,7 @@ public class Type /*implements Cloneable*/ { } } } - return this instanceof TypeStruct ? this : _intern(); + return this instanceof TypeStruct || _terned ? this : _intern(); } // Strict constant values, things on the lattice centerline. @@ -450,7 +472,7 @@ public class Type /*implements Cloneable*/ { // Are all reachable struct Fields are final? public final boolean isFinal() { return recurClose(recurOpen()._isFinal()); } - boolean _isFinal() { assert _type < TCYCLIC; return true; } + boolean _isFinal() { assert is_nokids(); return true; } public final Type makeRO() { if( isFinal() ) return this; @@ -464,6 +486,7 @@ public class Type /*implements Cloneable*/ { boolean _isGLB(boolean mem) { return switch(_type) { case TBOT -> false; + case TSCALAR -> this==TypeScalar.BOT; case TNIL -> false; case TCTRL -> true; case TXCTRL -> false; @@ -480,9 +503,149 @@ public class Type /*implements Cloneable*/ { } Type _glb(boolean mem) { assert is_simple(); return Type.BOTTOM; } - Type _close() { return this; } + // 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(); + }; + } + + // 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(); + }; + } + + // 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; + } + + // ---------------------------------------------------------- // Size in bits to hold an instance of this type. diff --git a/src/main/java/com/seaofnodes/simple/type/TypeConAry.java b/src/main/java/com/seaofnodes/simple/type/TypeConAry.java index c5543030..26fa4a67 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeConAry.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeConAry.java @@ -1,54 +1,51 @@ package com.seaofnodes.simple.type; -import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.util.AryInt; +import com.seaofnodes.simple.util.BAOS; import com.seaofnodes.simple.util.Utils; -import java.io.ByteArrayOutputStream; import java.util.ArrayList; +import java.util.HashMap; /** * Represents a constant array of primitives */ -public class TypeConAry<A> extends Type { +public abstract class TypeConAry<A> extends TypeScalar { boolean _any; // One of byte,short,int,long,float,double array public final A _ary; - TypeConAry( boolean any, byte type, A ary ) { super(type); _any = any; _ary = ary; } - public static final TypeConAry BOT = new TypeConAry(false, TINT, null).intern(); + TypeConAry( boolean any, A ary ) { super(TCONARY); _any = any; _ary = ary; } public static void gather(ArrayList<Type> ts) { - ts.add(BOT); TypeConAryB.gather(ts); TypeConAryI.gather(ts); } @Override public String str() { return (_any?"~":"") + "[]"; } - @Override TypeConAry xdual() { - if( _ary==null ) - return new TypeConAry(!_any,_type,null); - return this; - } + @Override TypeConAry xdual() { return this; } @Override Type xmeet(Type t) { - if( t instanceof TypeInteger ti ) return imeet(ti); TypeConAry ary = (TypeConAry)t; // Invariant - if( this==BOT ) return BOT; - if( ary ==BOT ) return BOT; - if( this==BOT.dual() ) return ary ; - if( ary ==BOT.dual() ) return this; assert _ary!=ary._ary; // Already interned and this!=t + // Unrelated constant arrays return elem().meet(ary.elem()); } - Type imeet( TypeInteger ti ) { - if( this==BOT.dual() ) return ti; - if( this==BOT ) return BOT; - Type elem = elem(); - if( !(elem instanceof TypeInteger) ) return BOTTOM; - if( ti.isHigh() && elem.isa(ti.dual()) ) + + Type ymeet( Type t ) { + if( t instanceof TypeInteger ti ) { + // if i can isa *each* element, then maybe can keep. + // no good to i.isa(elem()) because fails the dual + boolean fail = false; + for( int j=0; j<len(); j++ ) + if( !ti.isa(TypeInteger.constant(at8(j))) ) + { fail=true; break; } + Type e = elem(); + if( fail ) + return ti.meet(e); return this; - return elem.meet(ti); + } + throw Utils.TODO(); } - @Override public boolean isHigh() { return this==TOP; } @Override boolean _isConstant() { return true; } @Override Type _glb(boolean mem) { return this; } @@ -67,10 +64,24 @@ public class TypeConAry<A> extends Type { } return TypeInteger.make(min,max); } + // Generic element _type + public int elemT() { return TBOT; } public long at8(int idx) { throw Utils.TODO(); } public int len() { throw Utils.TODO(); } @Override public int log_size() { throw Utils.TODO(); } - public void write( ByteArrayOutputStream baos ) { throw Utils.TODO(); } + public void write( BAOS baos ) { throw Utils.TODO(); } + + // 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()])); + } @Override boolean eq(Type t) { return t instanceof TypeConAry ary && _any==ary._any && ary._ary==null; diff --git a/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java b/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java index 9e5f0a2d..bd2827ed 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java @@ -1,21 +1,22 @@ package com.seaofnodes.simple.type; +import com.seaofnodes.simple.util.BAOS; import java.util.ArrayList; import java.util.Arrays; -import java.io.ByteArrayOutputStream; public class TypeConAryB extends TypeConAry<byte[]> { - private TypeConAryB(byte[] bs) { super(false,TINT,bs); } + private TypeConAryB(byte[] bs) { super(false,bs); } public static TypeConAryB make( byte[] bs ) { return new TypeConAryB(bs).intern(); } public static TypeConAryB make( String s ) { return make(s.getBytes()); } static final TypeConAryB ABC = make("abc"); static final TypeConAryB ABCD = make("abcd"); public static void gather(ArrayList<Type> ts) { ts.add(ABC); ts.add(ABCD); } + @Override public int elemT() { return TINT; } @Override public long at8(int idx) { return _ary[idx]; } @Override public int len() { return _ary.length; } @Override public int log_size() { return 0; } @Override public String str() { return "[\""+new String(_ary)+"\"]"; } @Override boolean eq(Type t) { return t instanceof TypeConAryB ary && Arrays.equals(_ary,ary._ary); } @Override int hash() { return Arrays.hashCode(_ary); } - @Override public void write( ByteArrayOutputStream baos ) { baos.write(_ary,0,_ary.length); } + @Override public void write( BAOS baos ) { baos.write(_ary); } } diff --git a/src/main/java/com/seaofnodes/simple/type/TypeConAryI.java b/src/main/java/com/seaofnodes/simple/type/TypeConAryI.java index 8228887e..bb2ab83c 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeConAryI.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeConAryI.java @@ -4,10 +4,11 @@ import java.util.ArrayList; import java.util.Arrays; public class TypeConAryI extends TypeConAry<int[]> { - private TypeConAryI(int[] bs) { super(false,TINT,bs); } + private TypeConAryI(int[] bs) { super(false,bs); } public static TypeConAryI make( int[] bs ) { return new TypeConAryI(bs).intern(); } private static final TypeConAryI I123 = make(new int[]{1,2,3}); public static void gather(ArrayList<Type> ts) { ts.add(I123); } + @Override public int elemT() { return TINT; } @Override public long at8(int idx) { return _ary[idx]; } @Override public int len() { return _ary.length; } @Override public int log_size() { return 2; } diff --git a/src/main/java/com/seaofnodes/simple/type/TypeFloat.java b/src/main/java/com/seaofnodes/simple/type/TypeFloat.java index f3de581c..f4968f28 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeFloat.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeFloat.java @@ -1,14 +1,16 @@ package com.seaofnodes.simple.type; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; import com.seaofnodes.simple.util.Ary; +import com.seaofnodes.simple.util.AryInt; +import com.seaofnodes.simple.util.BAOS; +import com.seaofnodes.simple.util.Utils; import java.util.ArrayList; +import java.util.HashMap; /** * Float Type */ -public class TypeFloat extends Type { +public class TypeFloat extends TypeScalar { // - high -64, high -32, con 0, low +32, low +64 public byte _sz; @@ -98,6 +100,28 @@ public class TypeFloat extends Type { @Override boolean _isConstant() { return _sz==0; } @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(); + }; + } + @Override int hash() { return (int)(Double.hashCode(_con) ^ _sz ^ (1<<17)); } @Override diff --git a/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java b/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java index ddef9651..e2ec6263 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java @@ -1,8 +1,6 @@ package com.seaofnodes.simple.type; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; -import com.seaofnodes.simple.util.Ary; +import com.seaofnodes.simple.util.*; import java.util.*; /** @@ -21,27 +19,25 @@ public class TypeFunPtr extends TypeNil { // A TypeFunPtr is Signature and a set of functions. public Type[] _sig; public Type _ret; - // 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 + 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; private static final Ary<TypeFunPtr> FREE = new Ary<>(TypeFunPtr.class); - 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 ) { + private TypeFunPtr(byte nil) { super(TFUNPTR,nil); } + private TypeFunPtr init(byte nil, boolean open, Type[] sig, Type ret, int[] fidxs ) { assert sig != null; _nil = nil; _open = open; _sig = sig; _ret = ret; - _fidxs= fidxs; + assert _fidxs == null; + _fidxs= XInt.intern(fidxs); return this; } - 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); + 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); } @Override TypeFunPtr free(Type t) { TypeFunPtr fun = (TypeFunPtr)t; @@ -50,55 +46,75 @@ 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, long fidxs ) { + 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); } - public static TypeFunPtr make( boolean nil, boolean open, Type[] sig, Type ret ) { return make((byte)(nil ? 3 : 2),open,sig,ret,-1); } + public static TypeFunPtr make( boolean nil, Type[] sig, Type ret ) { return make((byte)(nil ? 3 : 2),true,sig,ret, XInt.FULL); } + public static TypeFunPtr make1( byte nil, boolean open, Type[] sig, Type ret, int fidx ) { + return make(nil,open,sig,ret,XInt.make(fidx)); + } + @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 make((byte)2, _open, _sig,_ret,1L<<fidx ); } - + public TypeFunPtr makeFrom( int fidx ) { return make1((byte)2,_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 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,-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 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 void gather(ArrayList<Type> ts) { ts.add(TEST); ts.add(TEST0); ts.add(BOT); ts.add(MAIN);ts.add(CALLOC); } + private static final Type[] ARG_EMPTY = new Type[0]; @Override Type xmeet(Type t) { TypeFunPtr that = (TypeFunPtr) t; - // 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); + 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)); } + static Type[] xmeet( Type[] ts0, Type[] ts1) { if( ts0==ts1 ) return ts0; assert ts0.length==ts1.length; @@ -108,7 +124,7 @@ public class TypeFunPtr extends TypeNil { return ts; } - @Override TypeFunPtr xdual() { return malloc(dual0(), !_open, xdual(_sig), _ret.dual(), ~_fidxs); } + @Override TypeFunPtr xdual() { return malloc(dual0(), !_open, xdual(_sig), _ret.dual(), XInt.dual(_fidxs)); } static Type[] xdual(Type[] ts) { Type[] dual = new Type[ts.length]; for( int i=0; i<ts.length; i++ ) @@ -120,7 +136,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,~_fidxs); + TypeFunPtr d = malloc(dual0(),!_open,sigs,null, XInt.dual(_fidxs)); (_dual = d)._dual = this; // Cross link duals // Return rdual d._ret = _ret._terned ? _ret.dual() : _ret.rdual(); @@ -130,53 +146,97 @@ public class TypeFunPtr extends TypeNil { return d; } - // 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); } + // 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); + + return make((byte)3,_open,_sig,_ret,_fidxs); + } - @Override public boolean isHigh() { return _nil <= 1 || (_nil==2 && _fidxs==0); } + @Override public boolean isHigh() { return _nil <= 1 || (_nil==2 && _fidxs== XInt.EMPTY); } - @Override boolean _isConstant() { return (_nil==2 && Long.bitCount(_fidxs)==1) || (_nil==3 && _fidxs==0); } + @Override boolean _isConstant() { return (_nil==2 && XInt.isConstant(_fidxs)) || (_nil==3 && _fidxs== XInt.EMPTY); } @Override boolean _isFinal() { return true; } @Override boolean _isGLB(boolean mem) { return true; } @Override TypeFunPtr _glb(boolean mem) { return this; } - @Override TypeFunPtr _close() { + + @Override TypeFunPtr _close( String name, HashMap<String, Type> TYPES ) { Type[] sig = new Type[_sig.length]; - TypeFunPtr fun = malloc(_nil,false,sig,null,_fidxs); + TypeFunPtr fun = malloc(_nil,true,sig,null,_fidxs); // Now start the recursion - fun._ret = _ret._close(); + fun._ret = _ret._close(name, TYPES ); for( int i=0; i<sig.length; i++ ) - sig[i] = _sig[i]._close(); + sig[i] = _sig[i]._close(name, TYPES ); + return fun; + } + // 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 long fidxs() { return _fidxs; } + public int[] fidxs() { return _fidxs; } public Type ret() { return _ret; } public int nargs() { return _sig.length; } - public int fidx() { assert Long.bitCount(_fidxs)==1; return Long.numberOfTrailingZeros(_fidxs); } + public int fidx() { return XInt.onlyBit(_fidxs); } public int nfcns() { - if( _fidxs < 0 ) return Integer.MAX_VALUE; // Infinite choices - return Long.bitCount(_fidxs); + if( XInt.isHigh(_fidxs) ) return Integer.MAX_VALUE; // Infinite choices + return XInt.bitCount(_fidxs); } // Used to walk over type structures - @Override int nkids() { return _sig.length+1; } - @Override Type at( int idx ) { + @Override public int nkids() { return _sig.length+1; } + @Override public Type at( int idx ) { return idx == _sig.length ? _ret : _sig[idx]; } - @Override void set( int idx, Type t ) { + @Override public 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 ) { + 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() ^ _fidxs ^ super.hash(); + long hash = _ret.hashCode() ^ XInt.hash(_fidxs) ^ super.hash(); for( Type arg : _sig ) hash = hash*19 ^ arg.hashCode(); return Utils.fold(hash); @@ -221,26 +281,24 @@ public class TypeFunPtr extends TypeNil { @Override public String str() { return "{"+printFIDX()+"}"; } + @Override SB _print(SB sb, BitSet visit, boolean html ) { sb.p(x()).p("{ "); if( _sig!=null ) for( Type t : _sig ) - sb.p(t.str()).p(" "); // Short form in signature + sb.p(t==null ? "---" : t.str()).p(" "); // Short form in signature + if( !_open ) sb.p("^ "); _ret.print(sb.p(html ? "→ " : "-> "),visit,html).p(" #"); // Print fidxs return sb.p(printFIDX()).p("}").p(q()); } String printFIDX() { String tilde = isHigh() ? "~" : ""; - 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 + String fidx = XInt.str(_fidxs); 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); } } diff --git a/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/src/main/java/com/seaofnodes/simple/type/TypeInteger.java index 97c17a34..25c3fa6f 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeInteger.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeInteger.java @@ -1,14 +1,16 @@ package com.seaofnodes.simple.type; import com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.util.SB; +import com.seaofnodes.simple.util.AryInt; +import com.seaofnodes.simple.util.BAOS; import com.seaofnodes.simple.util.Utils; import java.util.ArrayList; +import java.util.HashMap; /** * Integer Type */ -public class TypeInteger extends Type { +public class TypeInteger extends TypeScalar { /** * Describes an integer *range* - everything from min to max; both min and * max are inclusive. If min==max, this is a constant. @@ -116,14 +118,17 @@ public class TypeInteger extends Type { 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() ) { // 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(); + 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; } public long value() { assert isConstant(); return _min; } @@ -147,7 +152,6 @@ public class TypeInteger extends Type { @Override public Type xmeet(Type other) { - if( other instanceof TypeConAry ary ) return ary.imeet(this); // Invariant from caller: 'this' != 'other' and same class (TypeInteger) TypeInteger i = (TypeInteger)other; // Contract return make(Math.min(_min,i._min), Math.max(_max,i._max), (byte)Math.max(_widen,i._widen)); @@ -160,8 +164,8 @@ public class TypeInteger extends Type { @Override public TypeInteger nonZero() { if( isHigh() ) return this; if( this==ZERO ) return null; // No sane answer - if( _min==0 ) return make(1,Math.max(_max,1)); // specifically good on BOOL - if( _max==0 ) return make(_min,-1); + if( _min==0 ) return make(1,Math.max(_max,1),_widen); // specifically good on BOOL + if( _max==0 ) return make(_min,-1,_widen); return this; } @Override public Type makeZero() { return ZERO; } @@ -174,6 +178,39 @@ public class TypeInteger extends Type { 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(); + }; + } + @Override int hash() { return Utils.fold(_min) * Utils.fold(_max) + _widen; } @Override public boolean eq( Type t ) { return t instanceof TypeInteger i && _min==i._min && _max==i._max && _widen==i._widen; diff --git a/src/main/java/com/seaofnodes/simple/type/TypeMem.java b/src/main/java/com/seaofnodes/simple/type/TypeMem.java index 99b19c75..3bbdf1df 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeMem.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeMem.java @@ -1,10 +1,9 @@ package com.seaofnodes.simple.type; -import com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; +import com.seaofnodes.simple.util.*; import java.util.ArrayList; import java.util.BitSet; +import java.util.HashMap; /** @@ -18,13 +17,35 @@ 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(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); + + private TypeMem() { super(TMEM); CNT++; } + private TypeMem init(int alias, Type t, boolean one, boolean clz, boolean xfinal, int[] fidxs, int[] aliases) { _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) { @@ -32,25 +53,59 @@ 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) { - return FREE.isEmpty() ? new TypeMem(alias,t) : FREE.pop().init(alias,t); + 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 make(int alias, Type t) { - TypeMem f = malloc(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); 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(Type t) { return make(_alias,t,_one,_clz,_final,_escFs,_escAs); } + 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)); + } + // Existing escapes plus `t` escapes + public TypeMem escapesFrom(int alias, 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); } + 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); } @Override TypeMem xmeet(Type t) { @@ -59,50 +114,139 @@ public class TypeMem extends Type { if( that==TOP ) return this; if( this==BOT ) return BOT; if( that==BOT ) return BOT; - int alias = _alias==that._alias ? _alias : 1; + // 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; Type mt = _t.meet(that._t); - return make(alias,mt); + int[] fidxs = XInt.meet( _escFs, that._escFs ); + int[] aliases= XInt.meet( _escAs, that._escAs ); + // Singleton & class facts are kept only when both sides agree. + + // Final is kept if either side is final, merging a final & non-final + // memory can be argued either way here: + + // - Keep it: merging a final & non-final memory means a later Store + // update will be flagged as an error. + // - Lose it: merging a final & non-final memory means a later Load + // cannot be promised the memory is unchanging. + return make(alias, mt, _one & that._one, _clz & that._clz, _final | that._final, fidxs, aliases); } @Override - Type xdual() { return _t._dual==_t ? this : malloc(_alias,_t.dual()); } + Type xdual() { return malloc(_alias,_t.dual(),!_one,!_clz,!_final, XInt.dual(_escFs), XInt.dual(_escAs)); } @Override TypeMem rdual() { if( _dual!=null ) return dual(); assert !_terned; - TypeMem d = malloc(_alias,null); + TypeMem d = malloc(_alias,null,!_one,!_clz,!_final, XInt.dual(_escFs), XInt.dual(_escAs)); (_dual = d)._dual = this; // Cross link duals d._t = _t._terned ? _t.dual() : _t.rdual(); + if( d._alias==1 && !d._one && !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(); } @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 public Type _glb(boolean mem) { return make(_alias,_t._glb(true),_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 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(); } + @Override int hash() { return 9876543 + _alias + _t.hashCode() + (_one ? 8196 : 0) + (_clz ? 4098 : 0) + (_final ? 16392 : 0) + XInt.hash(_escFs) + XInt.hash(_escAs); } @Override boolean eq(Type t) { TypeMem that = (TypeMem) t; // Invariant - return _alias == that._alias && _t == that._t; + return _alias == that._alias && _t == that._t && _one == that._one && _clz == that._clz && _final == that._final && _escFs == that._escFs && _escAs == that._escAs; } @Override boolean cycle_eq(Type t) { TypeMem that = (TypeMem) t; // Invariant - return _alias == that._alias && _t.cycle_eq(that._t); + return _alias == that._alias && _one == that._one && _clz == that._clz && _final == that._final && _escFs == that._escFs && _escAs == that._escAs && _t.cycle_eq(that._t); } - @Override int nkids() { return 1; } - @Override Type at( int idx ) { return _t; } - @Override void set( int idx, Type t ) { _t = 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 public SB _print(SB sb, BitSet visit, boolean html) { - sb.p("#"); + 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"); - return _t.print(sb.p(_alias).p(":"),visit,html); + sb.p(_alias).p(":["); + _t.print(sb,visit,html).p(","); + XInt.str(sb,_escFs).p(","); + return XInt.str(sb,_escAs).p("]"); + } + + @Override public String str() { + 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(); } - @Override public String str() { return 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 + ? 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; + }; + } + + private static int[] escapeAlias( int[] xs, Type t ) { + return switch(t) { + case TypeMemPtr tmp -> escapeAlias(xs,tmp._obj); + case TypeStruct ts -> ts._open + ? 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; + }; + } } diff --git a/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java b/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java index 689b73ce..67d80591 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java @@ -1,8 +1,9 @@ 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.Utils; +import com.seaofnodes.simple.util.BAOS; import java.util.*; /** @@ -26,19 +27,23 @@ public class TypeMemPtr extends TypeNil { // (TOP ,true ) - a nil public TypeStruct _obj; - public boolean _one; // Singleton instance + public boolean _one; // Singleton pointer value; the pointer itself is constant. + public boolean _pub; // Escaped/public pointer. False means constructor-private. private static final Ary<TypeMemPtr> FREE = new Ary<>(TypeMemPtr.class); - 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; _one=one; return this; } + 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; _one=one; _pub=pub; return this; } // Return a filled-in TypeMemPtr; either from free list or alloc new. - private static TypeMemPtr malloc(byte nil, TypeStruct obj, boolean one) { - return FREE.isEmpty() ? new TypeMemPtr(nil,obj,one) : FREE.pop().init(nil,obj,one); + 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); } // All fields directly listed public static TypeMemPtr make(byte nil, TypeStruct obj, boolean one) { - TypeMemPtr tmp = malloc(nil, obj, one); + return make(nil,obj,one,true); + } + public static TypeMemPtr make(byte nil, TypeStruct obj, boolean one, boolean pub) { + TypeMemPtr tmp = malloc(nil, obj, one, pub); TypeMemPtr t2 = tmp.intern(); if( t2==tmp ) return tmp; return VISIT.isEmpty() ? t2.free(tmp) : t2.delayFree(tmp); @@ -47,6 +52,8 @@ 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); @@ -57,11 +64,13 @@ 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); } + public TypeMemPtr makeFrom(TypeStruct obj) { return obj==_obj ? this : make(_nil, obj, _one, _pub); } public TypeMemPtr makeNullable() { return makeFrom((byte)3); } - @Override TypeMemPtr makeFrom(byte nil) { return nil==_nil ? this : make(nil, _obj, _one); } - public TypeMemPtr makeHigh() { return make((byte)0,_obj.makeHigh(),false); } + @Override TypeMemPtr makeFrom(byte nil) { return nil==_nil ? this : make(nil, _obj, _one, _pub); } + public TypeMemPtr makeHigh(byte nil) { return make(nil,_obj.makeHigh(),false); } + @Override public Type nonZero() { return makeFrom((byte)(_nil <= 1 ? 1 : 2)); } // An abstract pointer, pointing to either a Struct or an Array. // Can also be null or not, so 4 choices {TOP,BOT} x {nil,not} @@ -76,16 +85,19 @@ public class TypeMemPtr extends TypeNil { public TypeNil xmeet(Type t) { TypeMemPtr that = (TypeMemPtr) t; assert !isFree() && !that.isFree(); - return make(xmeet0(that), (TypeStruct)_obj.meet(that._obj), _one & that._one); + // 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); } @Override - TypeMemPtr xdual() { return malloc( dual0(), _obj.dual(), _one); } + TypeMemPtr xdual() { return malloc( dual0(), _obj.dual(), !_one, !_pub); } @Override TypeMemPtr rdual() { if( _dual!=null ) return dual(); assert !_terned; - TypeMemPtr d = malloc(dual0(), null, _one); + TypeMemPtr d = malloc(dual0(), null, !_one, !_pub); (_dual = d)._dual = this; // Cross link duals d._obj = _obj._terned ? _obj.dual() : _obj.rdual(); return d; @@ -94,50 +106,52 @@ public class TypeMemPtr extends TypeNil { // RHS is NIL; do not deep-dual when crossing the center line @Override Type meet0() { return _nil==3 ? this : make((byte)3,_obj); } - @Override boolean _isConstant() { return _one && _obj._isConstant(); } + @Override boolean _isConstant() { return _one; } @Override boolean _isFinal() { return _obj._isFinal(); } @Override TypeMemPtr _makeRO() { return makeFrom(_obj._makeRO()); } - @Override boolean _isGLB(boolean mem) { return _obj.isGLB2(); } + @Override boolean _isGLB(boolean mem) { return _obj._isGLB(true); } @Override TypeMemPtr _glb(boolean mem) { return make((byte)3,_obj.glb2()); } - @Override TypeMemPtr _close() { return makeFrom(_obj._close()); } - - - // 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 ) 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); - } + @Override TypeMemPtr _close( String name, HashMap<String, Type> TYPES ) { return malloc(_nil,_obj._close(name, TYPES ),_one,_pub); } - // Is forward-reference - @Override public boolean isFRef() { return _obj.isFRef(); } + @Override Type _upgradeType(HashMap<String,Type> TYPES) { + return makeFrom((TypeStruct)_obj._upgradeType(TYPES)); + } @Override public int log_size() { return 3; } // (1<<3)==8-byte pointers - @Override int hash() { return _obj.hashCode() ^ super.hash() ^ (_one ? 2048 : 0); } + @Override int hash() { return _obj.hashCode() ^ super.hash() ^ (_one ? 2048 : 0) ^ (_pub ? 4096 : 0); } @Override boolean eq(Type t) { TypeMemPtr ptr = (TypeMemPtr)t; // Invariant - return super.eq(ptr) && _one == ptr._one && _obj == ptr._obj; + return super.eq(ptr) && _one == ptr._one && _pub == ptr._pub && _obj == ptr._obj; } @Override boolean cycle_eq(Type t) { if( t._type != TMEMPTR ) return false; TypeMemPtr ptr = (TypeMemPtr)t; // Invariant - return super.eq(ptr) && _one == ptr._one && _obj.cycle_eq(ptr._obj); + return super.eq(ptr) && _one == ptr._one && _pub == ptr._pub && _obj.cycle_eq(ptr._obj); } - @Override int nkids() { return 1; } - @Override Type at( int idx ) { return _obj; } - @Override void set( int idx, Type t ) { _obj = (TypeStruct)t; } + @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 and public/private + @Override int TAGOFF() { return 8; } + @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) ); + } + static TypeMemPtr packed( int tag, BAOS bais ) { + return malloc((byte)((tag&1)+2),null,(tag&2)==2,(tag&4)==4); + } @Override public String str() { if( this== NOTBOT) return "*void"; if( this== BOT) return "*void?"; - return x()+"*"+_obj.str()+q(); + return x()+"*"+(_obj==null?"---":_obj.str())+q(); } @Override SB _print(SB sb, BitSet visit, boolean html ) { diff --git a/src/main/java/com/seaofnodes/simple/type/TypeNil.java b/src/main/java/com/seaofnodes/simple/type/TypeNil.java index 56d73b00..32505aa2 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeNil.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeNil.java @@ -7,7 +7,7 @@ import java.util.ArrayList; /** * Nil-able Scalar types */ -public abstract class TypeNil extends Type { +public abstract class TypeNil extends TypeScalar { // 0 = high-subclass choice nil // 1 = high-subclass no nil // 2 = low -subclass no nil diff --git a/src/main/java/com/seaofnodes/simple/type/TypePtr.java b/src/main/java/com/seaofnodes/simple/type/TypePtr.java index 75478119..3ff9765b 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypePtr.java +++ b/src/main/java/com/seaofnodes/simple/type/TypePtr.java @@ -1,9 +1,12 @@ 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. @@ -50,6 +53,14 @@ public class TypePtr extends TypeNil { boolean _isGLB(boolean mem) { return this==PTR; } @Override TypePtr _glb(boolean mem) { 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; } + @Override public String str() { return STRS[_nil]; } @Override public SB _print(SB sb, BitSet visit, boolean html) { return sb.p(str()); } } diff --git a/src/main/java/com/seaofnodes/simple/type/TypeRPC.java b/src/main/java/com/seaofnodes/simple/type/TypeRPC.java index a1389540..a39ebc04 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeRPC.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeRPC.java @@ -1,32 +1,26 @@ package com.seaofnodes.simple.type; 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.HashSet; +import com.seaofnodes.simple.util.BAOS; +import java.util.*; /** * Return Program Control or Return PC or RPC */ public class TypeRPC extends Type { - // 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; + // A set of CallEndNode IDs (or StopNode); commonly just one. High-bit + // XInt sets represent complements/infinite sets. + public int[] _rpcs; private static final Ary<TypeRPC> FREE = new Ary<>(TypeRPC.class); - private TypeRPC init(boolean any, HashSet<Integer> rpcs) { _any=any; _rpcs=rpcs; return this; } + private TypeRPC init(int[] rpcs) { _rpcs = XInt.intern(rpcs); return this; } private TypeRPC() { super(TRPC); } - private static TypeRPC malloc(boolean any, HashSet<Integer> rpcs) { - return (FREE.isEmpty() ? new TypeRPC() : FREE.pop()).init(any,rpcs); + private static TypeRPC malloc(int[] rpcs) { + return (FREE.isEmpty() ? new TypeRPC() : FREE.pop()).init(rpcs); } - private static TypeRPC make(boolean any, HashSet<Integer> rpcs) { - TypeRPC rpc = (FREE.isEmpty() ? new TypeRPC() : FREE.pop()).init(any,rpcs); + private static TypeRPC make(int[] rpcs) { + TypeRPC rpc = malloc(rpcs); TypeRPC t2 = rpc.intern(); return t2==rpc ? rpc : t2.free(rpc); } @@ -41,71 +35,72 @@ public class TypeRPC extends Type { @Override boolean isFree() { return _rpcs==null; } public static TypeRPC constant(int cend) { - HashSet<Integer> rpcs = new HashSet<>(); - rpcs.add(cend); - return make(false,rpcs); + return make(XInt.make(cend)); + } + + public int rpc() { + return XInt.onlyBit(_rpcs); } - public static final TypeRPC BOT = make(true,new HashSet<>()); + + public static final TypeRPC BOT = make(XInt.FULL); 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.isEmpty() ) - return _any ? "$[ALL]" : "$[]"; - if( _rpcs.size()==1 ) - for( Integer rpc : _rpcs ) - return _any ? "$[-"+rpc+"]" : "$["+rpc+"]"; - return "$["+(_any ? "-" : "")+_rpcs+"]"; + if( _rpcs == XInt.FULL ) return "$[ALL]"; + if( _rpcs == XInt.EMPTY ) return "$[]"; + return "$"+XInt.str(_rpcs); } @Override public TypeRPC xmeet(Type other) { TypeRPC rpc = (TypeRPC)other; - // 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); + return make(XInt.meet(_rpcs,rpc._rpcs)); } - @Override Type xdual() { return malloc(!_any,_rpcs); } + @Override TypeRPC xdual() { return malloc(XInt.dual(_rpcs)); } + @Override Type rdual() { + TypeRPC d = xdual(); + (_dual = d)._dual = this; + return d; + } - @Override boolean _isConstant() { return !_any && _rpcs.size()==1; } + @Override boolean _isConstant() { return XInt.isConstant(_rpcs); } @Override boolean _isGLB(boolean mem) { return true; } + // 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 - int hash() { return _rpcs.hashCode() ^ (_any ? -1 : 0) ; } + int hash() { return XInt.hash(_rpcs); } @Override public boolean eq( Type t ) { TypeRPC rpc = (TypeRPC)t; // Contract - return _any==rpc._any && _rpcs.equals(rpc._rpcs); + return _rpcs==rpc._rpcs; } } diff --git a/src/main/java/com/seaofnodes/simple/type/TypeScalar.java b/src/main/java/com/seaofnodes/simple/type/TypeScalar.java new file mode 100644 index 00000000..7b5d6317 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/type/TypeScalar.java @@ -0,0 +1,58 @@ +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. + */ +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; + return BOT; + } + + @Override Type xmeet(Type t) { + TypeScalar scalar = (TypeScalar)t; + return isHigh() ? scalar : this; + } + + @Override TypeScalar xdual() { return new TypeScalar(); } + @Override public boolean isHigh() { return this == TOP; } + @Override boolean _isGLB(boolean mem) { return this == BOT; } + @Override Type _glb(boolean mem) { 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 String str() { return isHigh() ? "~scalar" : "scalar"; } + @Override SB _print(SB sb, BitSet visit, boolean html) { return sb.p(str()); } +} diff --git a/src/main/java/com/seaofnodes/simple/type/TypeStruct.java b/src/main/java/com/seaofnodes/simple/type/TypeStruct.java index 1cabbe32..f51ab859 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeStruct.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeStruct.java @@ -10,42 +10,65 @@ 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 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. + // 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. + */ public String _name; // Struct name - public boolean _open; // infinite fields are all true:BOTTOM, false:TOP + public boolean _open; + public boolean _fref; public Field[] _fields; - private TypeStruct(String name, boolean open, Field[] fields) { - super(TSTRUCT); + 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()); _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, 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; + private static TypeStruct malloc(String name, boolean open, boolean fref, Field[] fields) { + return (FREE.isEmpty() ? new TypeStruct() : FREE.pop()).init(name,open,fref,fields); } // 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; } @@ -54,73 +77,202 @@ public class TypeStruct extends Type { // All fields directly listed public static TypeStruct make(String name, boolean open, Field... fields) { - TypeStruct ts = malloc(name, open, 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 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 + // Array, variant body 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 ,false), - Field.make("[]",body,bodyAlias,efinal,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)); } + public TypeStruct makeHigh() { Field[] fs = new Field[_fields.length]; for( int i=0; i<_fields.length; i++ ) fs[i] = _fields[i].makeFrom(Type.TOP); - return make(_name,false,fs); + return make(_name,_open,_fref,fs); + } + + public TypeStruct makeInit() { + Field[] fs = new Field[_fields.length]; + for( int i=0; i<_fields.length; i++ ) { + Type zero = _fields[i]._t.makeZero(); + fs[i] = _fields[i].makeFrom(_fields[i]._t != Type.BOTTOM && _fields[i]._t != Type.TOP && zero.isa(_fields[i]._t) ? zero : Type.TOP); + } + return make(_name,_open,_fref,fs); } public TypeStruct add( Field f ) { - assert _open && find(f._fname)==-1; // No double field names + assert find(f._fname)==-1; // No double field names Field[] flds = Arrays.copyOf(_fields,_fields.length+1); flds[_fields.length] = f; - return make(_name,true,flds); + return make(_name,_open,_fref,flds); } public TypeStruct replace( Field f ) { - assert !_open; Field[] flds = Arrays.copyOf(_fields,_fields.length); flds[find(f._fname)] = f; - return make(_name,false,flds); + 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); } + 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().recurClose(); + return (TypeStruct)recurOpen()._close(_name, null).recurClose(); } - @Override TypeStruct _close() { - TypeStruct ts = (TypeStruct)VISIT.get(_name); + @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; - ts = recurPre(_name,false); + TypeStruct base = name==null ? (TypeStruct)TYPES.get(_name) : this; + if( base == null ) base = this; + 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(_fields[i]._t._close()); + 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; + } + + // 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); + if( ts!=null ) return ts; + // Main work: replace open with closed + if( _open ) + return base; + // Build recursive version + ts = recurPre(_uid,_name,false,_fref); + Field[] flds = ts._fields; + // Now start the recursion + for( int i=0; i<flds.length; i++ ) + flds[i].setType(_fields[i]._t._upgradeType(TYPES)); return ts; } static final AryInt CEQUALS = new AryInt(); - public static final TypeStruct BOT = open("$STRUCT"); - public static final TypeStruct TOP = BOT.dual(); + 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 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",TypeInteger.constant(3),-1,TypeConAryB.ABC,-4,true); + 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)); // A pair of self-cyclic types - 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(); + 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)); + @Override public boolean isHigh() { return _name==TOPNAME; } public static void gather(ArrayList<Type> ts) { ts.add(BOT); @@ -128,14 +280,15 @@ 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,true); + Field fcalloc = Field.make("calloc",TypeFunPtr.CALLOC,-2,true); TypeStruct scalloc = make("calloc",false,fcalloc); ts.add(scalloc); @@ -144,7 +297,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.equals(fname) ) + if( _fields[i]._fname==fname ) return i; return -1; } @@ -170,28 +323,27 @@ 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._one != f1._one ) + if( !f0._fname.equals(f1._fname) || f0._alias != f1._alias ) return BOT; } @@ -205,7 +357,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,_open | that._open); + ts = recurPre(pid,name,_open | that._open,_fref & that._fref); // Recurse all common fields Field[] flds = ts._fields; @@ -226,12 +378,10 @@ 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(_name,!_open,flds); + return malloc(sdual(),!_open,!_fref,flds); } // Recursive dual @@ -239,19 +389,26 @@ public class TypeStruct extends Type { if( _dual!=null ) return dual(); assert !_terned; Field[] flds = new Field[_fields.length]; - TypeStruct d = malloc(_name,!_open,flds); + TypeStruct d = malloc(sdual(),!_open,!_fref,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; } - // Is forward-reference - @Override public boolean isFRef() { return _open; } + private String sdual() { + return + _name==TOPNAME ? BOTNAME : + _name==BOTNAME ? TOPNAME : + _name; + } @Override boolean _isConstant() { if( VISIT.containsKey(_uid) ) return true; // Cycles assume constant VISIT.put(_uid,this); + if( _open ) return false; // Infinite BOT fields + // Special case for constant arrays + // Check all fields for being constant for( Field field : _fields ) if( !field._isConstant() ) @@ -261,69 +418,63 @@ public class TypeStruct extends Type { // All fields are final @Override boolean _isFinal() { - if( _open ) return false; // May have more more non-final fields + if( _open ) return false; // May have more non-final fields if( VISIT.containsKey(_uid) ) // Test: been here before? return true; // Cycles assume final - VISIT.put(_uid,this); // Set: dont do this again + VISIT.put(_uid,this); // Set: don't do this again for( Field fld : _fields ) if( !fld._isFinal() ) return false; return true; } - private TypeStruct recurPre(Object key, boolean open) { + private TypeStruct recurPre(Object key, String name, boolean open, boolean fref) { // 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, flds ); + TypeStruct ts = malloc(name, open, fref, flds ); + ts._aliases = aliases(); VISIT.put(key,ts); return ts; } - // Make a read-only version - @Override TypeStruct _makeRO() { - // Check for already visited - TypeStruct ts = (TypeStruct)VISIT.get(_name); + 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); if( ts!=null ) return ts; // Already visited - ts = recurPre(_name,_open); // Make a new type with blank fields + ts = recurPre(key,name,open,fref); Field[] flds = ts._fields; - for( Field fld : flds ) fld._final = true; + for( Field fld : flds ) + fld._final |= xfinal; // Now start the recursion for( int i=0; i<flds.length; i++ ) - flds[i].setType(_fields[i]._t._makeRO()); + flds[i].setType(transform.apply(_fields[i]._t)); return ts; } - - // 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; + // Make a read-only version + @Override TypeStruct _makeRO() { + return recurTransform(_name,_name,_open,_fref,true,Type::_makeRO); } // 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 recurTransform(_uid,_name,_open,false,false,t -> t._glb(true)); + } - return ts; + @Override boolean _isGLB(boolean mem) { + if( VISIT.containsKey(_uid) ) return true; // Cycles assume GLB + VISIT.put(_uid,this); + for( Field f : _fields ) + if( !f._t._isGLB(mem) ) + return false; + return true; } // log_size for a struct is not defined, unless its exactly some power of @@ -340,10 +491,13 @@ public class TypeStruct extends Type { // If false, always false. // If true , maybe true, need to check recursive fields. private boolean static_eq( TypeStruct ts ) { - return _name.equals(ts._name) && _open==ts._open && _fields.length==ts._fields.length; + 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; } @Override boolean eq(Type t) { + TypeStruct ts = (TypeStruct)t; // Invariant + // Recursive; so use cyclic equals if( !VISIT.isEmpty() ) { assert CEQUALS.isEmpty(); @@ -352,7 +506,6 @@ 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++ ) @@ -380,15 +533,35 @@ public class TypeStruct extends Type { @Override int hash() { - long hash = _name.hashCode() ^ (_open ? 4 : 0); + long hash = _name.hashCode() ^ (_open ? 4 : 0) ^ (_fref ? 8 : 0); for( Field f : _fields ) hash = Utils.rot(hash,13) ^ ((long)f._fname.hashCode() * f._alias); return Utils.fold(hash); } - @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 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 SB _print(SB sb, BitSet visit, boolean html ) { @@ -401,14 +574,17 @@ public class TypeStruct extends Type { 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 ) sb.p("... "); + if( _open && !_fref ) sb.p("... "); return sb.p("}"); } @Override public String str() { return (isFree() ? "FREE:":"")+_name; } - public boolean isAry() { return _fields.length>=2 && _fields[_fields.length-1]._fname=="[]"; } + public boolean isAry() { return find("[]")!=-1; } + public boolean isConAry() { + return isAry() && _fields[1]._t instanceof TypeConAry; + } public int aryBase() { assert isAry(); @@ -446,8 +622,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]._one ) - cnts[_fields[i]._t.log_size()]++; // Log size is 0(byte), 1(i16/u16), 2(i32/f32), 3(i64/dbl) + cnts[_fields[i]._t.log_size()]++; // Log size is 0(byte), 1(i16/u16), 2(i32/f32), 3(i64/dbl) int off = 0, idx = 0; // Base common struct fields go here, e.g. Mark/Klass // Compute offsets to the start of each power-of-2 aligned fields. int[] offs = new int[4]; @@ -458,7 +633,6 @@ 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]._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 diff --git a/src/main/java/com/seaofnodes/simple/type/TypeTuple.java b/src/main/java/com/seaofnodes/simple/type/TypeTuple.java index df9aba31..86f8aef1 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeTuple.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeTuple.java @@ -1,8 +1,6 @@ package com.seaofnodes.simple.type; -import com.seaofnodes.simple.util.Ary; -import com.seaofnodes.simple.util.SB; -import com.seaofnodes.simple.util.Utils; +import com.seaofnodes.simple.util.*; import java.util.*; public class TypeTuple extends Type { @@ -31,20 +29,32 @@ 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 START= make(Type.CONTROL,TypeMem.TOP,TypeInteger.BOT); - public static final TypeTuple RET = make(Type.CONTROL,TypeMem.BOT,Type.BOTTOM); + public static final TypeTuple DEAD_NEW = make(TypeMemPtr.TOP,TypeMem.TOP); 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(START); ts.add(IF_TRUE); } + public static void gather(ArrayList<Type> ts) { ts.add(BOT); ts.add(TEST); ts.add(STATE); ts.add(IF_TRUE); } @Override Type xmeet(Type other) { @@ -58,7 +68,24 @@ public class TypeTuple extends Type { @Override TypeTuple xdual() { if( _types.length==0 ) return malloc(null); - return malloc(TypeFunPtr.xdual(_types)); + boolean isCon=true; + for( Type t : _types ) + if( t._dual!=t ) + // Not a constant + return malloc(TypeFunPtr.xdual(_types)); + return this; + } + @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 + for( int i=0; i<_types.length; i++ ) + ts[i] = _types[i]._terned ? _types[i].dual() : _types[i].rdual(); + return d; } @Override boolean _isConstant() { @@ -71,6 +98,13 @@ public class TypeTuple extends Type { @Override TypeMemPtr _makeRO() { 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 public int log_size() { throw Utils.TODO(); } @Override public int alignment() { assert isConstant(); @@ -80,7 +114,30 @@ public class TypeTuple extends Type { return align; } - public Type ret() { assert _types.length==3; return _types[2]; } + 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()]); + } @Override SB _print(SB sb, BitSet visit, boolean html ) { if( this==TOP ) return sb.p("[TOP]"); @@ -112,8 +169,4 @@ public class TypeTuple extends Type { return true; } - @Override int nkids() { return _types.length; } - @Override Type at( int idx ) { return _types[idx]; } - @Override void set( int idx, Type t ) { _types[idx] = t; } - } diff --git a/src/main/java/com/seaofnodes/simple/type/XInt.java b/src/main/java/com/seaofnodes/simple/type/XInt.java new file mode 100644 index 00000000..e3054a73 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/type/XInt.java @@ -0,0 +1,266 @@ +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); + for( int i=1; i<min; i++ ) { + if( (xs[i] | ys[i])!=xs[i] ) isX=false; + if( (xs[i] | ys[i])!=ys[i] ) isY=false; + } + if( !isHigh(xs) && min < ys.length ) + isX = false; // YS has *some* more bits set, and XS does not + if( !isHigh(ys) && min < xs.length ) + isY = false; // XS has *some* more bits set, and YS does not + if( isX ) return xs; + if( isY ) return ys; + // Get a free one + int[] ms = free(Math.max(xs.length,ys.length)); + for( int i=1; i<min; i++ ) + ms[i] = xs[i] | ys[i]; + if( min < xs.length ) System.arraycopy(xs,min,ms,min,xs.length-min); + if( min < ys.length ) System.arraycopy(ys,min,ms,min,ys.length-min); + 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/src/main/java/com/seaofnodes/simple/util/BAOS.java b/src/main/java/com/seaofnodes/simple/util/BAOS.java new file mode 100644 index 00000000..61ce7387 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/util/BAOS.java @@ -0,0 +1,162 @@ +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/src/main/java/com/seaofnodes/simple/util/IntHashMap.java b/src/main/java/com/seaofnodes/simple/util/IntHashMap.java new file mode 100644 index 00000000..55aaa9f9 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/util/IntHashMap.java @@ -0,0 +1,65 @@ +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 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 + 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]; + } + + 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 ) + put(old[i],old[i+1]); + } +} diff --git a/src/main/java/com/seaofnodes/simple/util/SB.java b/src/main/java/com/seaofnodes/simple/util/SB.java index d7afd6eb..cc59f2a6 100644 --- a/src/main/java/com/seaofnodes/simple/util/SB.java +++ b/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 ) { |
