From 63fe16fa2ea123b8ce2f976662b0a6d09f352868 Mon Sep 17 00:00:00 2001 From: Cliff Click Date: Sat, 12 Oct 2024 14:02:06 -0700 Subject: Chapter 16: Constructors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Tamás Hadházy <85063808+Hels15@users.noreply.github.com> Co-authored-by: Hsiang-Ying Fu <66062782+fuhsnn@users.noreply.github.com> Co-authored-by: tannal Co-authored-by: hadha Co-authored-by: fuhsnn <66062782+fuhsnn@users.noreply.github.com> Co-authored-by: Thalia Archibald Co-authored-by: Symious <14933944+symious@users.noreply.github.com> Co-authored-by: RobertObkircher Co-authored-by: Claude Opus 5 --- src/main/java/com/seaofnodes/simple/Ary.java | 217 +++++++++++ .../com/seaofnodes/simple/GlobalCodeMotion.java | 12 +- src/main/java/com/seaofnodes/simple/IterPeeps.java | 4 +- src/main/java/com/seaofnodes/simple/Parser.java | 422 +++++++++++++-------- .../java/com/seaofnodes/simple/node/AndNode.java | 7 +- .../java/com/seaofnodes/simple/node/IfNode.java | 4 +- .../java/com/seaofnodes/simple/node/LoadNode.java | 15 +- .../com/seaofnodes/simple/node/MemMergeNode.java | 5 +- .../java/com/seaofnodes/simple/node/MulNode.java | 2 + .../java/com/seaofnodes/simple/node/NewNode.java | 24 +- src/main/java/com/seaofnodes/simple/node/Node.java | 52 +-- .../java/com/seaofnodes/simple/node/NotNode.java | 2 +- .../java/com/seaofnodes/simple/node/OrNode.java | 7 +- .../java/com/seaofnodes/simple/node/OutNode.java | 4 +- .../java/com/seaofnodes/simple/node/PhiNode.java | 2 +- .../com/seaofnodes/simple/node/RegionNode.java | 5 +- .../com/seaofnodes/simple/node/ReturnNode.java | 2 +- .../java/com/seaofnodes/simple/node/SarNode.java | 9 +- .../java/com/seaofnodes/simple/node/ScopeNode.java | 237 ++++++------ .../java/com/seaofnodes/simple/node/ShlNode.java | 11 +- .../java/com/seaofnodes/simple/node/ShrNode.java | 9 +- .../java/com/seaofnodes/simple/node/StartNode.java | 16 +- .../java/com/seaofnodes/simple/node/StoreNode.java | 39 +- .../com/seaofnodes/simple/node/StructNode.java | 45 +++ .../java/com/seaofnodes/simple/node/XorNode.java | 7 +- .../simple/print/SimpleGraphAdapter.java | 7 +- .../simple/print/SimpleGraphObserver.java | 2 +- .../java/com/seaofnodes/simple/type/Field.java | 33 +- .../com/seaofnodes/simple/type/TypeInteger.java | 2 +- .../com/seaofnodes/simple/type/TypeMemPtr.java | 2 + .../com/seaofnodes/simple/type/TypeStruct.java | 24 +- .../java/com/seaofnodes/simple/type/TypeTuple.java | 10 +- 32 files changed, 850 insertions(+), 389 deletions(-) create mode 100644 src/main/java/com/seaofnodes/simple/Ary.java create mode 100644 src/main/java/com/seaofnodes/simple/node/StructNode.java (limited to 'src/main') diff --git a/src/main/java/com/seaofnodes/simple/Ary.java b/src/main/java/com/seaofnodes/simple/Ary.java new file mode 100644 index 00000000..8654d41e --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/Ary.java @@ -0,0 +1,217 @@ +package com.seaofnodes.simple; + +import java.lang.StringBuilder; +import java.lang.reflect.Array; +import java.util.*; + +// ArrayList with saner syntax +public class Ary extends AbstractList implements List { + public E[] _es; + public int _len; + + public Ary(E[] es) { this(es,es.length); } + public Ary(E[] es, int len) { if( es.length==0 ) es=Arrays.copyOf(es,1); _es=es; _len=len; } + @SuppressWarnings("unchecked") + public Ary(Class clazz) { this((E[]) Array.newInstance(clazz, 1),0); } + + /** @return list is empty */ + public boolean isEmpty() { return _len==0; } + /** @return active list length */ + public int len() { return _len; } + public int size() { return len(); } + + /** @param i element index + * @return element being returned; throws if OOB + * @exception AIOOBE if !(0 <= i < _len) + */ + public E get( int i ) { return at(i); } + public E at( int i ) { + range_check(i); + return _es[i]; + } + + /** @return last element */ + public E last( ) { return at(_len-1); } + + /** Add element in amortized constant time + * @param e Element to add at end of list + **/ + public E push( E e ) { + if( _len >= _es.length ) _es = Arrays.copyOf(_es,Math.max(1,_es.length<<1)); + return (_es[_len++] = e); + } + /** @return remove and return last element */ + public E pop( ) { + range_check(0); + return _es[--_len]; + } + @Override public E removeLast() { return pop(); } + + + /** Add element in amortized constant time + * @param e Element to add at end of list + * @return true */ + public boolean add( E e ) { + if( _len >= _es.length ) _es = Arrays.copyOf(_es,Math.max(1,_es.length<<1)); + _es[_len++] = e; + return true; + } + + ///** Slow, linear-time, element insert. Preserves order. + // * @param i index to insert at, between 0 and _len inclusive. + // * @param e Element to insert + // */ + //public void insert( int i, E e ) { + // if( i < 0 || i>_len ) + // throw new ArrayIndexOutOfBoundsException(""+i+" >= "+_len); + // if( _len >= _es.length ) _es = Arrays.copyOf(_es,Math.max(1,_es.length<<1)); + // System.arraycopy(_es,i,_es,i+1,(_len++)-i); + // _es[i] = e; + //} + + /** Fast, constant-time, element removal. Does not preserve order + * @param i element to be removed + * @return element removed */ + public E del( int i ) { + range_check(i); + E tmp = _es[i]; + _es[i]=_es[--_len]; + return tmp; + } + + ///** Element removal, using '=='. Does not preserve order. + // * @param e element to be removed + // * @return element removed */ + //public E del( E e ) { + // for( int i=0; i<_len; i++ ) { + // E tmp = _es[i]; + // if( tmp==e ) { + // _es[i]=_es[--_len]; + // return tmp; + // } + // } + // return null; + //} + + /** Slow, linear-time, element removal. Preserves order. + * @param i element to be removed + * @return element removed */ + @Override public E remove( int i ) { + range_check(i); + E e = _es[i]; + System.arraycopy(_es,i+1,_es,i,(--_len)-i); + return e; + } + + /** Remove all elements */ + public void clear( ) { _len=0; } + + /** Set existing element + * @param i element to set + * @param e value to set + * @return old value + * @exception AIOOBE if !(0 <= i < _len) + */ + public E set( int i, E e ) { + range_check(i); + E old = _es[i]; + _es[i] = e; + return old; + } + + public Ary setLen( int len ) { + if( len > _len ) + while( len>= _es.length ) _es = Arrays.copyOf(_es,_es.length<<1); + _len = len; + while( _es.length > (len<<1) ) // Shrink if hugely too large + _es = Arrays.copyOf(_es,_es.length>>1); + Arrays.fill(_es,len,_es.length,null); + return this; + } + + /** @param c Collection to be added */ + public boolean addAll( Collection c ) { + if( c==null ) return false; + for( E e : c ) add(e); + return true; + } + public Ary addAll( Iterable it ) { for( E e : it ) add(e); return this; } + + /** @param es Array to be added */ + public Ary addAll( F[] es ) { + if( es==null || es.length==0 ) return this; + while( _len+es.length > _es.length ) _es = Arrays.copyOf(_es,_es.length<<1); + System.arraycopy(es,0,_es,_len,es.length); + _len += es.length; + return this; + } + + /** @param c Collection to be added */ + public Ary addAll( Ary c ) { + if( c==null || c._len==0 ) return this; + while( _len+c._len > _es.length ) _es = Arrays.copyOf(_es,_es.length<<1); + System.arraycopy(c._es,0,_es,_len,c._len); + _len += c._len; + return this; + } + + /** @return compact array version */ + public E[] asAry() { return Arrays.copyOf(_es,_len); } + + /** Sorts in-place + * @param c Comparator to sort by */ + public void sort_update(Comparator c ) { Arrays.sort(_es, 0, _len, c); } + + /** Find the first matching element using ==, or -1 if none. Note that + * most del calls shuffle the list, so the first element might be random. + * @param e Element to find + * @return index of first matching element, or -1 if none */ + public int find( E e ) { + for( int i=0; i<_len; i++ ) if( _es[i]==e ) return i; + return -1; + } + + + /** @return an iterator */ + @Override public Iterator iterator() { return new Iter(); } + private class Iter implements Iterator { + int _i=0; + @Override public boolean hasNext() { return _i<_len; } + @Override public E next() { return _es[_i++]; } + } + + @Override public String toString() { + StringBuilder sb = new StringBuilder().append('{'); + for( int i=0; i<_len; i++ ) { + if( i>0 ) sb.append(','); + if( _es[i] != null ) sb.append(_es[i].toString()); + } + return sb.append('}').toString(); + } + + private void range_check( int i ) { + if( i < 0 || i>=_len ) + throw new ArrayIndexOutOfBoundsException(""+i+" >= "+_len); + } + + // Note that the hashCode() and equals() are not invariant to changes in the + // underlying array. If the hashCode() is used (e.g., inserting into a + // HashMap) and the then the array changes, the hashCode() will change also. + @Override public boolean equals( Object o ) { + if( this==o ) return true; + if( !(o instanceof Ary ary) ) return false; + if( _len != ary._len ) return false; + if( _es == ary._es ) return true; + for( int i=0; i<_len; i++ ) + if( !(_es[i]==null ? (ary._es[i] == null) : _es[i].equals(ary._es[i])) ) + return false; + return true; + } + @Override public int hashCode( ) { + int sum=_len; + for( int i=0; i<_len; i++ ) + sum += _es[i]==null ? 0 : _es[i].hashCode(); + return sum; + } + +} diff --git a/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java b/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java index d17ec3b6..bb109fc9 100644 --- a/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java +++ b/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java @@ -66,7 +66,7 @@ public abstract class GlobalCodeMotion { // Reverse Post-Order on CFG for( int j=rpo.size()-1; j>=0; j-- ) { CFGNode cfg = rpo.get(j); - cfg.loopDepth(); + cfg.loopDepth(); // Pre-compute loop depth for( Node n : cfg._inputs ) _schedEarly(n,visit); // In dead infinite loops, entire code blocks may be unreachable @@ -98,7 +98,7 @@ public abstract class GlobalCodeMotion { // Schedule inputs first, except Phis: following their backedges would // enter a data cycle before its control has been scheduled. for( Node def : n._inputs ) - if( def!=null ) + if( def!=null && !(def instanceof PhiNode) ) _schedEarly(def,visit); // An existing edge 0 already supplies control (or a Phi/Proj binding). if( n.in(0)==null ) { @@ -143,7 +143,7 @@ public abstract class GlobalCodeMotion { // All uses done? for( Node use : n._outputs ) - if( use!=null && late[use._nid]==null ) + if( use!=null && late[use._nid]==null && !(use instanceof PhiNode) ) continue outer; // Nope, await all uses done // Loads need their memory inputs' uses also done @@ -262,11 +262,11 @@ public abstract class GlobalCodeMotion { private static CFGNode anti_dep( LoadNode load, CFGNode stblk, CFGNode defblk, CFGNode lca, Node st, int[] anti ) { // Preserve the full store range for the earlier evaluator scheduler. // It places nodes independently of GCM and may hoist this store. - for( ; stblk != defblk.idom(); stblk = stblk.idom() ) { + for( ; stblk != defblk; stblk = stblk.idom() ) { // Store and Load overlap, need anti-dependence - if( anti[stblk._nid]==load._nid ) { + if( !(stblk instanceof IfNode) && anti[stblk._nid]==load._nid ) { lca = stblk.domLCA(lca,null); // Raise Loads LCA - if( lca == stblk && st != null && Utils.find(st._inputs,load) == -1 ) // And if something moved, + if( lca == stblk && st != null && st._inputs.find(load) == -1 ) // And if something moved, st.addDef(load); // Add anti-dep as well return lca; // Cap this stores' anti-dep to here } diff --git a/src/main/java/com/seaofnodes/simple/IterPeeps.java b/src/main/java/com/seaofnodes/simple/IterPeeps.java index 7075d526..cabc4a15 100644 --- a/src/main/java/com/seaofnodes/simple/IterPeeps.java +++ b/src/main/java/com/seaofnodes/simple/IterPeeps.java @@ -50,7 +50,7 @@ public abstract class IterPeeps { return (N)WORK.push(n); } - public static void addAll( ArrayList ary ) { + public static void addAll( Ary ary ) { WORK.addAll(ary); } @@ -176,7 +176,7 @@ public abstract class IterPeeps { return x; } - public void addAll( ArrayList ary ) { + public void addAll( Ary ary ) { for( E n : ary ) push(n); } diff --git a/src/main/java/com/seaofnodes/simple/Parser.java b/src/main/java/com/seaofnodes/simple/Parser.java index a0c15ae3..deb359ce 100644 --- a/src/main/java/com/seaofnodes/simple/Parser.java +++ b/src/main/java/com/seaofnodes/simple/Parser.java @@ -4,12 +4,13 @@ import com.seaofnodes.graph.GraphObserver; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; +import static com.seaofnodes.simple.Utils.TODO; import java.util.*; /** * The Parser converts a Simple source program to the Sea of Nodes intermediate - * representation directly in one pass. There is no intermediate Abstract + * representation directly in one pass. There is no intermediate Abstract * Syntax Tree structure. *

* This is a simple recursive descent parser. All lexical analysis is done here as well. @@ -103,36 +104,27 @@ public class Parser { // Mapping from a type name to a Type. The string name matches // `type.str()` call. No TypeMemPtrs are in here, because Simple does not // have C-style '*ptr' references. - public static HashMap TYPES = new HashMap<>(); + public final HashMap TYPES; + + // Mapping from a type name to the constructor for a Type. + public final HashMap INITS; + public Parser(String source, TypeInteger arg) { PARSER = this; Node.reset(); IterPeeps.reset(); - TYPES.clear(); - TYPES.put("bool",TypeInteger.U1 ); - TYPES.put("byte",TypeInteger.U8 ); - TYPES.put("f32" ,TypeFloat .B32); - TYPES.put("f64" ,TypeFloat .BOT); - TYPES.put("flt" ,TypeFloat .BOT); - TYPES.put("i16" ,TypeInteger.I16); - TYPES.put("i32" ,TypeInteger.I32); - TYPES.put("i64" ,TypeInteger.BOT); - TYPES.put("i8" ,TypeInteger.I8 ); - TYPES.put("int" ,TypeInteger.BOT); - TYPES.put("u1" ,TypeInteger.U1 ); - TYPES.put("u16" ,TypeInteger.U16); - TYPES.put("u32" ,TypeInteger.U32); - TYPES.put("u8" ,TypeInteger.U8 ); SCHEDULED = false; _lexer = new Lexer(source); _scope = new ScopeNode(); _continueScope = _breakScope = null; - START = new StartNode(new Type[]{ Type.CONTROL, TypeMem.BOT, arg }); + START = new StartNode(arg); STOP = new StopNode(source); ZERO = con(0).keep(); XCTRL= new XCtrlNode().peephole().keep(); ALIAS = 2; // alias 0 for the control, 1 for memory + TYPES = defaultTypes(); + INITS = new HashMap<>(); } public Parser(String source) { @@ -142,28 +134,49 @@ public class Parser { @Override public String toString() { return _lexer.toString(); } - String src() { return new String( _lexer._input ); } + public static HashMap defaultTypes() { + return new HashMap<>() {{ + put("bool",TypeInteger.U1 ); + put("byte",TypeInteger.U8 ); + put("f32" ,TypeFloat .B32); + put("f64" ,TypeFloat .BOT); + put("flt" ,TypeFloat .BOT); + put("i16" ,TypeInteger.I16); + put("i32" ,TypeInteger.I32); + put("i64" ,TypeInteger.BOT); + put("i8" ,TypeInteger.I8 ); + put("int" ,TypeInteger.BOT); + put("u1" ,TypeInteger.U1 ); + put("u16" ,TypeInteger.U16); + put("u32" ,TypeInteger.U32); + put("u8" ,TypeInteger.U8 ); + }}; + } // Debugging utility to find a Node by index public static Node find(int nid) { return START.find(nid); } private Node ctrl() { return _scope.ctrl(); } - private Node ctrl(Node n) { return _scope.ctrl(n); } public StopNode parse() { _xScopes.push(_scope); // Enter a new scope for the initial control and arguments _scope.push(); - _scope.define(ScopeNode.CTRL, Type.CONTROL , new CProjNode(START, 0, ScopeNode.CTRL).peephole()); - _scope.define(ScopeNode.ARG0, TypeInteger.BOT, new ProjNode(START, 2, ScopeNode.ARG0).peephole()); - _scope.define("$mem", TypeMem.BOT, new ProjNode(START, 1, "$mem").peephole()); + _scope.define(ScopeNode.CTRL, Type.CONTROL , false, new CProjNode(START, 0, ScopeNode.CTRL).peephole()); + _scope.define(ScopeNode.MEM0, TypeMem.BOT , false, new ProjNode(START, 1, ScopeNode.MEM0).peephole()); + _scope.define(ScopeNode.ARG0, TypeInteger.BOT, false, new ProjNode(START, 2, ScopeNode.ARG0).peephole()); + + // Parse whole program parseBlock(); if( ctrl()._type==Type.CONTROL ) STOP.addReturn(new ReturnNode(ctrl(), ZERO, _scope).peephole()); _scope.pop(); _xScopes.pop(); + for( StructNode init : INITS.values() ) + init.unkeep().kill(); + INITS.clear(); if (!_lexer.isEOF()) throw error("Syntax error, unexpected " + _lexer.getAnyNextToken()); STOP.peephole(); return STOP; @@ -210,48 +223,6 @@ public class Parser { // to ambiguity else return parseExpressionStatement(); } - - /** - * Parse a struct field. - *

-     *     type IDENTIFIER ;
-     * 
- */ - private Field parseField() { - Type t = type(); - if( t==null ) - throw errorSyntax("Requires a field type, found '"+_lexer.getAnyNextToken()+"'"); - return require(Field.make(requireId().intern(),ALIAS++,t),";"); - } - - /** - * Parse a struct declaration, and return the following statement. - * Only allowed in top level scope. - * Structs cannot be redefined. - * - * @return The statement following the struct - */ - private Node parseStruct() { - if (_xScopes.size() > 1) throw errorSyntax("struct declarations can only appear in top level scope"); - String typeName = requireId(); - Type t = TYPES.get(typeName); - if( t!=null && !(t instanceof TypeMemPtr tmp && tmp._obj._fields==null) ) - throw errorSyntax("struct '" + typeName + "' cannot be redefined"); - // Parse a collection of fields - ArrayList fields = new ArrayList<>(); - require("{"); - while (!peek('}') && !_lexer.isEOF()) { - Field field = parseField(); - if (fields.contains(field)) throw errorSyntax("Field '" + field + "' already defined in struct '" + typeName + "'"); - fields.add(field); - } - require("}"); - // Build and install the TypeStruct - TypeStruct ts = TypeStruct.make(typeName, fields.toArray(new Field[fields.size()])); - TYPES.put(typeName, TypeMemPtr.make(ts)); - return parseStatement(); - } - /** * Parses a while statement * @@ -332,6 +303,8 @@ public class Parser { // At exit the false control is the current control, and // the scope is the exit scope after the exit test. + _xScopes.pop(); + _xScopes.push(exit); return _scope = exit; } @@ -340,15 +313,15 @@ public class Parser { ctrl(XCTRL); // Kill current scope // Prune nested lexical scopes that have depth > than the loop head // We use _breakScope as a proxy for the loop head scope to obtain the depth - while( cur._idxs.size() > _breakScope._idxs.size() ) + while( cur._lexSize.size() > _breakScope._lexSize.size() ) cur.pop(); // If this is a continue then first time the target is null // So we just use the pruned current scope as the base for the // "continue" - if (toScope == null) + if( toScope == null ) return cur; // toScope is either the break scope, or a scope that was created here - assert toScope._idxs.size() <= _breakScope._idxs.size(); + assert toScope._lexSize.size() <= _breakScope._lexSize.size(); toScope.ctrl(toScope.mergeScopes(cur)); return toScope; } @@ -427,43 +400,39 @@ public class Parser { - /** - * Parses an expression statement or a declaration statement where type is a struct - * - *
-     *      name;         // Error
-     * type name;         // Define name with default initial value
-     * type name = expr;  // Define name with given   initial value
-     *      name = expr;  // Reassign existing
-     *             expr   // Something else
-     * 
- * @return an expression {@link Node}, never {@code null} + /** Parse: name [=expr] */ - private Node parseExpressionStatement() { + private Node parseAsgn(Type t) { + boolean isDecl = t!=null; // Having a type is a declaration, missing one is updating a prior name int old = _lexer._position; - Type t = type(); String name = requireId(); Node expr; - if( match(";") ) { // Assign a default value - // No type and no expr is an error + if( peek(';') || peek(',') ) { + // Bare "name" is not allowed if( t==null ) throw errorSyntax("expression"); - expr = new ConstantNode(t.makeInit()).peephole(); - } else if( match("=") ) { // Assign "= expr;" - expr = require(parseExpression(), ";"); - } else { // Neither, so just a normal expression parse + // Does this declaration need an initializer and not getting one? + Type init = t instanceof TypeMemPtr tmp && !tmp._nil + // Must be initialized in the "new" constructor + ? Type.TOP + // Else takes the default + : t.makeInit(); + expr = con(init); + + } else if( !match("=") ) { // Something else _lexer._position = old; - return require(parseExpression(),";"); - } + return null; + } else + expr = parseExpression(); // Defining a new variable vs updating an old one - if( t != null ) { - if( _scope.define(name,t,expr) == null ) - throw error("Redefining name '" + name + "'"); - } else { - if( _scope.lookup(name)==null ) + if( !isDecl ) { // Assigning over an existing name + // Lookup + ScopeNode.Var def = _scope.lookup(name); + if( def==null ) throw error("Undefined name '" + name + "'"); - t = _scope.lookupDeclaredType(name); + t = def._type; // Declared field type } + // Auto-widen int to float if( expr._type instanceof TypeInteger && t instanceof TypeFloat ) expr = new ToFloatNode(expr).peephole(); @@ -476,9 +445,86 @@ public class Parser { // Type is sane if( !e.isa(t) ) throw error("Type " + e.str() + " is not of declared type " + t.str()); - return _scope.update(name,expr); + + if( isDecl ) { + if( !_scope.define(name,t,false,expr) ) + throw error("Redefining name '" + name + "'"); + } else + _scope.update(name,expr); + return expr; } + /** + * Parses an expression statement or a declaration statement where type is a struct + * + *
+     * type decl [, decl]*;  // Define many names with the same type
+     * asgn;                 // Assign or load a variable
+     * expr;                 // Something else
+     * name;                 // Bare name is an error
+
+     * 
+ * @return an expression {@link Node}, never {@code null} + */ + private Node parseExpressionStatement() { + Node n; + Type t = type(); + if( t != null ) { + // Parse comma-separated declarations + n = parseAsgn(t); + while( match(",") ) + n = parseAsgn(t); + + // Parse "asgn;" which is just "name = expr;" + } else if( ( n = parseAsgn(null)) == null ) { + // Something else + n = parseExpression(); + } + return require(n,";"); + } + + + /** + * Parse a struct declaration, and return the following statement. + * Only allowed in top level scope. + * Structs cannot be redefined. + * + * @return The statement following the struct + */ + private Node parseStruct() { + if (_xScopes.size() > 1) throw errorSyntax("struct declarations can only appear in top level scope"); + String typeName = requireId(); + Type t = TYPES.get(typeName); + if( t!=null && !(t instanceof TypeMemPtr tmp && tmp._obj._fields==null) ) + throw errorSyntax("struct '" + typeName + "' cannot be redefined"); + + // A Block scope parse, and inspect the scope afterward for fields. + _scope.push(); + require("{"); + while (!peek('}') && !_lexer.isEOF()) + parseStatement(); + + // Grab the declarations and build fields and a Struct + int lexlen = _scope._lexSize.last(); + int varlen = _scope._vars._len; + StructNode s = new StructNode(); + Field[] fs = new Field[varlen-lexlen]; + for( int i=lexlen; i init=s._inputs; int idx=0; + if( hasConstructor ) { + idx = _scope.nIns(); + // Push a scope, and pre-assign all struct fields. + _scope.push(); + for( int i=0; i init ) { + Field[] fs = obj._fields; + if( fs==null ) + throw error("Unknown struct type '" + obj._name + "'"); Node prior = mem().keep(); - // No default: only the aliases initialized by this allocation. MemMergeNode input = new MemMergeNode(null); - for( Field f : obj._fields ) - input.alias(f._alias,prior); - Node nnn = new NewNode(TypeMemPtr.make(obj),ctrl(),input.peephole(),size).peephole().keep(); + for( Field f : fs ) input.alias(f._alias,prior); + // Constructor values follow control, memory, and allocation size. + Node[] ns = new Node[3+fs.length]; + ns[0] = ctrl(); + ns[1] = input.peephole(); + ns[2] = size; + for( int i=0; i ALTMP = new Ary<>(Node.class); private Node newArray(TypeStruct ary, Node len) { int base = ary.aryBase (); int scale= ary.aryScale(); Node size = new AddNode(con(base),new ShlNode(len,con(scale)).peephole()).peephole(); - Node ptr = newStruct(ary,size); - int alias = ary._fields[0]._alias; // Length alias - store("#",alias,TypeInteger.BOT,ptr,con(ary.offset(0)),len.unkeep(),true,null); + ALTMP.clear(); ALTMP.add(len); ALTMP.add(con(ary._fields[1]._type.makeInit())); + Node ptr = newStruct(ary,size,0,ALTMP); + len.unkeep(); return ptr; } // Memory is one hidden SSA variable, including across branches and loops. - private Node mem() { return _scope.lookup("$mem"); } - private Node mem(Node n) { return _scope.update("$mem",n); } + private Node mem() { return _scope.mem(); } + private void mem(Node n) { _scope.mem(n); } private void store(String name, int alias, Type glb, Node ptr, Node off, Node val, boolean init, Node ctrl) { Node prior = mem().keep(); @@ -785,48 +872,45 @@ public class Parser { // Sanity check expr for being a reference if( !(expr._type instanceof TypeMemPtr ptr) ) throw error("Expected reference but got " + expr._type.str()); - if( ptr == TypeMemPtr.TOP ) throw error("Accessing field '" + name + "' from null"); - if( ptr._obj == null ) throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'"); + + // Happens when parsing known dead code, which often has other typing + // issues. Since the code is dead, possibly due to inlining, lets not + // spoil the user experience with error messages. + if( ctrl()._type==Type.XCONTROL ) + // Exit out via parsing the trailing expression + return matchOpx('=','=') ? parseExpression() : parsePostfix(con(Type.TOP)); + // Sanity check field name for existing TypeMemPtr tmp = (TypeMemPtr)TYPES.get(ptr._obj._name); if( tmp == null ) throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'"); TypeStruct base = tmp._obj; int fidx = base.find(name); if( fidx == -1 ) throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'"); - expr.keep(); // Get field type and layout offset from base type and field index fidx Field f = base._fields[fidx]; // Field from field index - Node off; - if( name.equals("[]") ) { // If field is an array body - // Array index math - Node idx = require(parseExpression(),"]"); - Node shl = new ShlNode(idx,con(base.aryScale())).peephole(); - off = new AddNode(con(base.aryBase()),shl).peephole(); - } else { // Else normal struct field - // Hardwired field offset - off = con(base.offset(fidx)); - } - if( match("=") ) { - // Disambiguate "obj.fld==x" boolean test from "obj.fld=x" field assignment - if( peek('=') ) _lexer._position--; - else { - off.keep(); - Node val = parseExpression(); - // Auto-truncate when storing to narrow fields - val = zsMask(val,f._type).keep(); - // Array control stands in for the future bounds check. - store(name,f._alias,f._type,expr.unkeep(),off.unkeep(),val,false,base.isAry() ? ctrl() : null); - return val.unkeep(); // "obj.a = expr" returns the expression while updating memory - } + expr.keep(); + Node off = name.equals("[]") // If field is an array body + // Array index math + ? new AddNode(con(base.aryBase()),new ShlNode(require(parseExpression(),"]"),con(base.aryScale())).peephole()).peephole() + // Struct field offsets are hardwired + : con(base.offset(fidx)); + + // Disambiguate "obj.fld==x" boolean test from "obj.fld=x" field assignment + if( matchOpx('=','=') ) { + off.keep(); + Node val = zsMask(parseExpression(),f._type).keep(); + // Array control stands in for the future bounds check. + store(name,f._alias,f._type,expr.unkeep(),off.unkeep(),val,false,base.isAry() ? ctrl() : null); + return val.unkeep(); } Node load = new LoadNode(name, f._alias, f._type.glb(), mem(), expr.unkeep(), 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() ) load.setDef(0,ctrl()); + //if( base.isAry() ) load.setDef(0,ctrl()); return parsePostfix(load.peephole()); } @@ -859,13 +943,9 @@ public class Parser { * floatLiteral: [digits].[digits]?[e [digits]]? * */ - private ConstantNode parseLiteral() { - return (ConstantNode) new ConstantNode(_lexer.parseNumber()).peephole(); - } - - public static Node con( long con ) { - return new ConstantNode(TypeInteger.constant(con)).peephole(); - } + private ConstantNode parseLiteral() { return con(_lexer.parseNumber()); } + public static Node con( long con ) { return con(TypeInteger.constant(con)); } + public static ConstantNode con( Type t ) { return (ConstantNode)new ConstantNode(t).peephole(); } ////////////////////////////////// // Utilities for lexical analysis @@ -874,6 +954,7 @@ public class Parser { private boolean match (String syntax) { return _lexer.match (syntax); } // Match must be "exact", not be followed by more id letters private boolean matchx(String syntax) { return _lexer.matchx(syntax); } + private boolean matchOpx(char c0, char c1) { return _lexer.matchOpx(c0,c1); } // Return true and do NOT skip if 'ch' is next private boolean peek(char ch) { return _lexer.peek(ch); } private boolean peekIsId() { return _lexer.peekIsId(); } @@ -993,6 +1074,13 @@ public class Parser { _position -= syntax.length(); return false; } + boolean matchOpx(char c0, char c1) { + skipWhiteSpace(); + if( _position+1 >= _input.length || _input[_position]!=c0 || _input[_position+1]==c1 ) + return false; + _position++; + return true; + } private boolean peek(char ch) { skipWhiteSpace(); diff --git a/src/main/java/com/seaofnodes/simple/node/AndNode.java b/src/main/java/com/seaofnodes/simple/node/AndNode.java index 1f223bcb..bd03d6cb 100644 --- a/src/main/java/com/seaofnodes/simple/node/AndNode.java +++ b/src/main/java/com/seaofnodes/simple/node/AndNode.java @@ -14,8 +14,11 @@ public class AndNode extends Node { @Override public Type compute() { - if( in(1)._type instanceof TypeInteger i0 && - in(2)._type instanceof TypeInteger i1 ) { + Type t1 = in(1)._type, t2 = in(2)._type; + if( t1.isHigh() || t2.isHigh() ) + return TypeInteger.TOP; + if( t1 instanceof TypeInteger i0 && + t2 instanceof TypeInteger i1 ) { if( i0.isConstant() && i1.isConstant() ) return TypeInteger.constant(i0.value()&i1.value()); // Sharpen allowed bits if either value is narrowed diff --git a/src/main/java/com/seaofnodes/simple/node/IfNode.java b/src/main/java/com/seaofnodes/simple/node/IfNode.java index 70340f7f..f21894dc 100644 --- a/src/main/java/com/seaofnodes/simple/node/IfNode.java +++ b/src/main/java/com/seaofnodes/simple/node/IfNode.java @@ -38,8 +38,8 @@ public class IfNode extends CFGNode implements MultiNode { return TypeTuple.IF_NEITHER; // If constant is 0 then false branch is reachable // Else true branch is reachable - if (t instanceof TypeInteger ti && ti.isConstant()) - return ti==TypeInteger.ZERO ? TypeTuple.IF_FALSE : TypeTuple.IF_TRUE; + if( t.isConstant() ) + return t.makeInit()==t ? TypeTuple.IF_FALSE : TypeTuple.IF_TRUE; return TypeTuple.IF_BOTH; } diff --git a/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/src/main/java/com/seaofnodes/simple/node/LoadNode.java index b0517a4c..8d604b28 100644 --- a/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -2,9 +2,9 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; +import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeMem; +import com.seaofnodes.simple.type.*; import java.util.BitSet; import java.util.ArrayList; @@ -74,8 +74,8 @@ public class LoadNode extends MemOpNode { // Simple Load-after-New on same address. if( mem() instanceof ProjNode p && p.in(0) instanceof NewNode nnn && - ptr == nnn.proj(0) ) // Must check same object - return new ConstantNode(_declaredType.makeInit()); + ptr == nnn.proj(0) ) // Must check same object + return nnn.in(nnn.findAlias(_alias)); // Load from New init // Load-after-Store on same address, but bypassing provably unrelated // stores. This is a more complex superset of the above two peeps. @@ -98,12 +98,12 @@ public class LoadNode extends MemOpNode { // Pointers cannot overlap mem = st.mem(); // Proved never equal break; - case PhiNode phi: - break outer; // Assume related + case PhiNode phi: break outer; // Assume related + case ConstantNode top: break outer; // Assume shortly dead case ProjNode mproj: if( mproj.in(0) instanceof NewNode nnn1 ) { if( ptr instanceof ProjNode pproj && pproj.in(0) == mproj.in(0) ) - return new ConstantNode(_declaredType.makeInit()); // Load from a New + return nnn1.in(nnn1.findAlias(_alias)); // Load from New init if( !(ptr instanceof ProjNode pproj && pproj.in(0) instanceof NewNode nnn2) ) break outer; // Cannot tell, ptr not related to New mem = nnn1.mem();// Bypass unrelated New @@ -148,7 +148,6 @@ public class LoadNode extends MemOpNode { ptr2 instanceof ProjNode && ptr2.in(0) instanceof NewNode; } - // Profitable if we find a matching Store on this Phi arm. private boolean profit(PhiNode phi, int idx) { Node px = phi.in(idx); diff --git a/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java index a4b105c9..faf456de 100644 --- a/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java +++ b/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java @@ -30,7 +30,7 @@ public class MemMergeNode extends Node { setDef(alias,mem); } - // Scalar contents of one alias. New's struct supplies the initialized type; + // Scalar contents of one alias. New's field inputs supply the initialized type; // join it with the incoming contents for all previously allocated objects. // Phis use their cached types, so this query does not recurse around loops. static Type contents(Node mem, int alias, Node dep) { @@ -40,7 +40,8 @@ public class MemMergeNode extends Node { if( mem instanceof ProjNode proj && proj.in(0) instanceof NewNode nnn ) { assert proj._idx==1 && nnn.field(alias)!=null; nnn.addDep(dep); - return contents(nnn.mem(),alias,dep).meet(nnn.field(alias)._type.makeInit()); + Node init = nnn.in(nnn.findAlias(alias)).addDep(dep); + return contents(nnn.mem(),alias,dep).meet(init._type); } // There are no heap arguments in this chapter. Before the first New, // the set of allocated objects (and hence stored values) is empty. diff --git a/src/main/java/com/seaofnodes/simple/node/MulNode.java b/src/main/java/com/seaofnodes/simple/node/MulNode.java index b403c2bf..a9ac07e6 100644 --- a/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -19,6 +19,8 @@ public class MulNode extends Node { return TypeInteger.TOP; if( t1 instanceof TypeInteger i1 && t2 instanceof TypeInteger i2 ) { + if( i1==TypeInteger.ZERO || i2==TypeInteger.ZERO) + return TypeInteger.ZERO; if (i1.isConstant() && i2.isConstant()) return TypeInteger.constant(i1.value()*i2.value()); } diff --git a/src/main/java/com/seaofnodes/simple/node/NewNode.java b/src/main/java/com/seaofnodes/simple/node/NewNode.java index f92d3cdf..51341c41 100644 --- a/src/main/java/com/seaofnodes/simple/node/NewNode.java +++ b/src/main/java/com/seaofnodes/simple/node/NewNode.java @@ -4,19 +4,22 @@ import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.type.*; -/** Allocate and zero an object. Inputs {ctrl, $mem, size}; results {ptr, $mem}. +/** Allocate and initialize an object. Inputs {ctrl, $mem, size, fields...}; + * results {ptr, $mem}. * The memory input and result cover only the aliases in the allocated struct. */ public class NewNode extends Node implements MultiNode { public final TypeMemPtr _ptr; - public NewNode(TypeMemPtr ptr, Node ctrl, Node mem, Node size) { - super(ctrl,mem,size); - assert ctrl._type==Type.CONTROL || ctrl._type==Type.XCONTROL; - assert mem._type instanceof TypeMem; - assert size._type instanceof TypeInteger; + public NewNode(TypeMemPtr ptr, Node... nodes) { + super(nodes); _ptr = ptr; + assert nodes.length==3+ptr._obj._fields.length; + assert nodes[0]._type==Type.CONTROL || nodes[0]._type==Type.XCONTROL; + assert nodes[1]._type instanceof TypeMem; + assert nodes[2]._type instanceof TypeInteger; + for( int i=3; i _print1(ExprPrinter p) { return p.p("new ").p(_ptr._obj.str()); } + // Find matching alias input + int findAlias(int alias) { + return 3+_ptr._obj.findAlias(alias); + } + + @Override public TypeTuple compute() { return TypeTuple.make(_ptr,TypeMem.BOT); diff --git a/src/main/java/com/seaofnodes/simple/node/Node.java b/src/main/java/com/seaofnodes/simple/node/Node.java index 541c8eb1..fa25b58a 100644 --- a/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/src/main/java/com/seaofnodes/simple/node/Node.java @@ -4,12 +4,12 @@ import com.seaofnodes.print.BaseNode; import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.Ary; import com.seaofnodes.simple.IRPrinter; import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeFloat; - import java.util.*; import java.util.function.Function; @@ -25,9 +25,9 @@ public abstract class Node extends BaseNode implements OutNode, Cloneable *

* Generally fixed length, ordered, nulls allowed, no unused trailing space. * Ordering is required because e.g. "a/b" is different from "b/a". - * The first input (offset 0) is often a {@link #isCFG} node. + * The first input (offset 0) is often a {@link CFGNode} node. */ - public ArrayList _inputs; + public Ary _inputs; /** * Outputs reference Nodes that are not null and have this Node as an @@ -38,7 +38,7 @@ public abstract class Node extends BaseNode implements OutNode, Cloneable * walked in either direction. These outputs are typically used for * efficient optimizations but otherwise have no semantics meaning. */ - public ArrayList _outputs; + public Ary _outputs; /** * Current computed type for this Node. This value changes as the graph @@ -56,9 +56,9 @@ public abstract class Node extends BaseNode implements OutNode, Cloneable protected Node(Node... inputs) { super(UNIQUE_ID++); // allocate unique dense ID - _inputs = new ArrayList<>(); + _inputs = new Ary<>(Node.class); Collections.addAll(_inputs,inputs); - _outputs = new ArrayList<>(); + _outputs = new Ary<>(Node.class); for( Node n : _inputs ) if( n != null ) n.addUse( this ); @@ -84,7 +84,7 @@ public abstract class Node extends BaseNode implements OutNode, Cloneable */ public Node in(int i) { return _inputs.get(i); } public Node out(int i) { return _outputs.get(i); } - @Override public ArrayList outs() { return _outputs; } + @Override public Ary outs() { return _outputs; } public int nIns() { return _inputs.size(); } @@ -125,7 +125,7 @@ public abstract class Node extends BaseNode implements OutNode, Cloneable old_def.delUse(this) ) // If we removed the last use, the old def is now dead old_def.kill(); // Kill old def moveDepsToWorklist(); - // Return self for easy flow-coding + // Return new_def for easy flow-coding return new_def; } @@ -136,13 +136,19 @@ public abstract class Node extends BaseNode implements OutNode, Cloneable Node delDef(int idx) { unlock(); Node old_def = in(idx); - Utils.del(_inputs, idx); + _inputs.del(idx); if( old_def.delUse(this) ) // If we removed the last use, the old def is now dead old_def.kill(); // Kill old def old_def.moveDepsToWorklist(); return this; } + // Insert the numbered input, sliding other inputs to the right + Node insertDef(int idx, Node new_def) { + _inputs.add(idx,null); + return setDef(idx,new_def); + } + /** * Add a new def to an existing Node. Keep the edges correct by * adding the corresponding def->use edge. @@ -167,17 +173,17 @@ public abstract class Node extends BaseNode implements OutNode, Cloneable // Return true if the output list is empty afterward. // Error is 'use' does not exist; ok for 'use' to be null. protected boolean delUse( Node use ) { - Utils.del(_outputs, Utils.find(_outputs, use)); + _outputs.del(_outputs.find(use)); moveDepsToWorklist(); // User-count and anti-dependence queries can now change. return _outputs.isEmpty(); } - // Shortcut for "popping" n nodes. A "pop" is basically a + // Shortcut for "popping" until n nodes. A "pop" is basically a // setDef(last,null) followed by lowering the nIns() count. - void popN(int n) { + void popUntil(int n) { unlock(); - for( int i=0; i n ) { + Node old_def = _inputs.pop(); if( old_def != null && // If it exists and old_def.delUse(this) ) // If we removed the last use, the old def is now dead old_def.kill(); // Kill old def @@ -214,7 +220,7 @@ public abstract class Node extends BaseNode implements OutNode, Cloneable // Remove bogus null. public N unkeep() { delUse(null); return (N)this; } // Test "keep" status - public boolean iskeep() { return Utils.find(_outputs,null) != -1; } + public boolean iskeep() { return _outputs.find(null) != -1; } // Replace self with nnn in the graph, making 'this' go dead public void subsume( Node nnn ) { @@ -222,7 +228,7 @@ public abstract class Node extends BaseNode implements OutNode, Cloneable while( nOuts() > 0 ) { Node n = _outputs.removeLast(); n.unlock(); - int idx = Utils.find(n._inputs, this); + int idx = n._inputs.find(this); n._inputs.set(idx,nnn); n.moveDepsToWorklist(); // Rewiring can change a dependent query without changing type. nnn.addUse(n); @@ -398,7 +404,7 @@ public abstract class Node extends BaseNode implements OutNode, Cloneable // on a node some distance away, and if that node ever changes we should // retry the peephole. Track a set of Nodes dependent on `this`, and // revisit them if `this` changes. - ArrayList _deps; + Ary _deps; public int nDeps() { return _deps == null ? 0 : _deps.size(); } public Node dep(int idx) { return _deps.get(idx); } @@ -421,10 +427,10 @@ public abstract class Node extends BaseNode implements OutNode, Cloneable if( IterPeeps.midAssert() ) return this; var obs = IterPeeps.midAssert() ? null : Parser.PARSER == null ? null : Parser.PARSER._obs; if( obs != null ) obs.dep(this, dep); - if( _deps==null ) _deps = new ArrayList<>(); - if( Utils.find(_deps ,dep) != -1 ) return this; // Already on list - if( !forwards && Utils.find(_inputs,dep) != -1 ) return this; - if( !forwards && Utils.find(_outputs,dep)!= -1 ) return this; + if( _deps==null ) _deps = new Ary<>(Node.class); + if( _deps .find(dep) != -1 ) return this; // Already on list + if( !forwards && _inputs .find(dep) != -1 ) return this; // No need for deps on immediate neighbors + if( !forwards && _outputs.find(dep) != -1 ) return this; _deps.add(dep); return this; } @@ -554,8 +560,8 @@ public abstract class Node extends BaseNode implements OutNode, Cloneable try { n = (Node)clone(); } catch( CloneNotSupportedException e ) { throw new AssertionError(e); } n._nid = UNIQUE_ID++; - n._inputs = new ArrayList<>(); - n._outputs = new ArrayList<>(); + n._inputs = new Ary<>(Node.class); + n._outputs = new Ary<>(Node.class); n._deps = null; n._hash = 0; return n; diff --git a/src/main/java/com/seaofnodes/simple/node/NotNode.java b/src/main/java/com/seaofnodes/simple/node/NotNode.java index 2e89f013..e388f4a3 100644 --- a/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ b/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -16,7 +16,7 @@ public class NotNode extends Node { Type t0 = in(1)._type; if( t0.isHigh() ) return TypeInteger.BOOL.dual(); switch( t0 ) { - case TypeInteger i0: return i0._max < 0 || i0._min > 0 ? TypeInteger.FALSE : (i0==TypeInteger.ZERO ? TypeInteger.TRUE : TypeInteger.BOT); + case TypeInteger i0: return i0._max < 0 || i0._min > 0 ? TypeInteger.FALSE : (i0==TypeInteger.ZERO ? TypeInteger.TRUE : TypeInteger.BOOL); case TypeFloat i0: return i0.isConstant() ? TypeInteger.constant(i0.value()==0 ? 1 : 0) : TypeInteger.BOT; case TypeMemPtr p0: diff --git a/src/main/java/com/seaofnodes/simple/node/OrNode.java b/src/main/java/com/seaofnodes/simple/node/OrNode.java index 5badd015..5be31b88 100644 --- a/src/main/java/com/seaofnodes/simple/node/OrNode.java +++ b/src/main/java/com/seaofnodes/simple/node/OrNode.java @@ -14,8 +14,11 @@ public class OrNode extends Node { @Override public Type compute() { - if( in(1)._type instanceof TypeInteger i0 && - in(2)._type instanceof TypeInteger i1 ) { + Type t1 = in(1)._type, t2 = in(2)._type; + if( t1.isHigh() || t2.isHigh() ) + return TypeInteger.TOP; + if( t1 instanceof TypeInteger i0 && + t2 instanceof TypeInteger i1 ) { if( i0.isConstant() && i1.isConstant() ) return TypeInteger.constant(i0.value()|i1.value()); } diff --git a/src/main/java/com/seaofnodes/simple/node/OutNode.java b/src/main/java/com/seaofnodes/simple/node/OutNode.java index 7e531145..dbac136b 100644 --- a/src/main/java/com/seaofnodes/simple/node/OutNode.java +++ b/src/main/java/com/seaofnodes/simple/node/OutNode.java @@ -1,7 +1,7 @@ package com.seaofnodes.simple.node; -import java.util.ArrayList; +import com.seaofnodes.simple.Ary; public interface OutNode { - ArrayList outs(); + Ary outs(); } diff --git a/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/src/main/java/com/seaofnodes/simple/node/PhiNode.java index ea5eeb89..8479b4dc 100644 --- a/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -39,7 +39,7 @@ public class PhiNode extends Node { @Override public Type compute() { if( !(region() instanceof RegionNode r) ) - return region()._type==Type.XCONTROL ? Type.TOP : _type; + return region()._type==Type.XCONTROL ? (_type instanceof TypeMem ? TypeMem.TOP : Type.TOP) : _type; // During parsing Phis have to be computed type pessimistically. if( r.inProgress() ) return _declaredType; // Set type to local top of the starting type diff --git a/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/src/main/java/com/seaofnodes/simple/node/RegionNode.java index d4f73ddf..08f48a08 100644 --- a/src/main/java/com/seaofnodes/simple/node/RegionNode.java +++ b/src/main/java/com/seaofnodes/simple/node/RegionNode.java @@ -103,7 +103,7 @@ public class RegionNode extends CFGNode { // A walk that can be cutoff early private static final BitSet VISIT = new BitSet(); - private static boolean walk( IfNode iff, CProjNode cproj, Node n ) { + private boolean walk( IfNode iff, CProjNode cproj, Node n ) { if( VISIT.get(n._nid) ) return false; VISIT.set(n._nid); if( n instanceof CFGNode ) return false; @@ -111,6 +111,7 @@ public class RegionNode extends CFGNode { return false; boolean progress = false; Node pred = iff.pred(); + if( pred._type.isHighOrConst() ) return false; // This will collapse the IF already for( int i=1; i>i1.value()); if( i1.isConstant() ) { diff --git a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java index 36a5060b..84937ad7 100644 --- a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -3,14 +3,13 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.Ary; import com.seaofnodes.simple.type.*; import java.util.*; /** * The Scope node is purely a parser helper - it tracks names to nodes with a - * stack of scopes. + * stack of variable records. */ public class ScopeNode extends Node { @@ -20,43 +19,68 @@ public class ScopeNode extends Node { */ public static final String CTRL = "$ctrl"; public static final String ARG0 = "arg"; + public static final String MEM0 = "$mem"; - /** - * Map variable names to Scope input edge + /** The tracked fields are now complex enough to deserve an array-of-structs layout */ - public final Stack> _idxs; + public static class Var { + public final int _idx; + public final String _name; + public final Type _type; + public final boolean _final; + public Var(int idx, String name, Type type, boolean xfinal) { + _idx = idx; + _name = name; + _type = type; + _final = xfinal; + } + } + + public Node in(Var v) { return in(v._idx); } + + @Override public Type compute() { return Type.BOTTOM; } + @Override public Node idealize() { return null; } + + // All active/live variables in all nested scopes, all run together + public final Ary _vars; + + // Size of each nested lexical scope + public final Ary _lexSize; - /** - * Map variable names to declared Type - */ - final Stack> _decls; // A new ScopeNode - public ScopeNode() { - _idxs = new Stack<>(); - _decls= new Stack<>(); - _type = Type.BOTTOM; - } + public ScopeNode() { _type = Type.BOTTOM; _vars = new Ary<>(Var.class); _lexSize = new Ary<>(Integer.class); } @Override public String label() { return "Scope"; } + @Override protected ExprPrinter _print1(ExprPrinter p) { p.p("Scope[ "); - String[] names = reverseNames(); - for( int j=0; j syms : _idxs ) - for( String name : syms.keySet() ) - names[syms.get(name)] = name; - return names; + public void push() { + _lexSize.push(_vars.size()); + } + public void pop() { + int n = _lexSize.pop(); + popUntil(n); + _vars.setLen(n); } - @Override public Type compute() { return Type.BOTTOM; } - - @Override public Node idealize() { return null; } - - public void push() { _idxs.push(new HashMap<>()); _decls.push(new HashMap<>()); } - public void pop() { popN(_idxs.pop().size()); _decls.pop(); } + // Find name in reverse, return an index into _vars or -1. Linear scan + // instead of hashtable, but probably doesn't matter until the scan + // typically hits many dozens of variables. + int find( String name ) { + for( int i=_vars.size()-1; i>=0; i-- ) + if( _vars.get(i)._name.equals(name) ) + return i; + return -1; + } /** * Create a new variable name in the current scope */ - public Node define( String name, Type declaredType, Node n ) { - HashMap syms = _idxs.lastElement(); - _decls.lastElement().put(name,declaredType); - if( syms.put(name,nIns()) != null ) - return null; // Double define - return addDef(n); + public boolean define( String name, Type declaredType, boolean xfinal, Node init ) { + assert name.charAt(0)!='$' || _lexSize.size()==1; // Later scopes do not define memory + if( _lexSize._len > 1 ) + for( int i=_vars.size()-1; i>=_lexSize.last(); i-- ) + if( _vars.get(i)._name.equals(name) ) + return false; // Double define + _vars.add(new Var(nIns(),name,declaredType,xfinal)); + addDef(init); + return true; } /** * Lookup a name in all scopes starting from most deeply nested. * * @param name Name to be looked up - * @see #update(String, Node, int) + * @return null if not found, or the implementing Node */ - public Node lookup( String name ) { return update(name,null,_idxs.size()-1); } + public Var lookup( String name ) { + int idx = find(name); + // -1 is missed in all scopes, not found + return idx == -1 ? null : _update(_vars.at(idx),null); + } + /** * If the name is present in any scope, then redefine else null * * @param name Name being redefined * @param n The node to bind to the name */ - public Node update( String name, Node n ) { return update(name,n,_idxs.size()-1); } - /** - * Both recursive lookup and update. - *

- * A shared implementation allows us to create lazy phis both during - * lookups and updates; the lazy phi creation is part of chapter 8. - * - * @param name Name whose binding is being updated or looked up - * @param n If null, do a lookup, else update binding - * @param nestingLevel The starting nesting level - * @return node being looked up, or the one that was updated - */ - private Node update( String name, Node n, int nestingLevel ) { - if( nestingLevel<0 ) return null; // Missed in all scopes, not found - var syms = _idxs.get(nestingLevel); // Get the symbol table for nesting level - var idx = syms.get(name); - if( idx == null ) return update(name,n,nestingLevel-1); // Not found in this scope, recursively look in parent scope - Node old = in(idx); + public void update( String name, Node n ) { + int idx = find(name); + assert idx>=0; + _update(_vars.at(idx),n); + } + + Var _update( ScopeNode.Var v, Node st ) { + Node old = in(v._idx); if( old instanceof ScopeNode loop ) { // Lazy Phi! - old = loop.in(idx) instanceof PhiNode phi && loop.ctrl()==phi.region() + Node def = loop.in(v._idx); + old = def instanceof PhiNode phi && loop.ctrl()==phi.region() // Loop already has a real Phi, use it - ? loop.in(idx) + ? def // 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(idx,PhiNode.make(name, lookupDeclaredType(name),loop.ctrl(),loop.update(name,null,nestingLevel),null).peephole()); - setDef(idx,old); + : loop.setDef(v._idx,PhiNode.make(v._name, v._type instanceof TypeMemPtr ? v._type : v._type.glb(), loop.ctrl(), loop.in(loop._update(v,null)._idx),null).peephole()); + setDef(v._idx,old); } - return n==null ? old : setDef(idx,n); // Not lazy, so this is the answer - } - - // Return declared type for a variable - public Type lookupDeclaredType( String name ) { - for( int i=_decls.size(); i>0; i-- ) { - Type t = _decls.get(i-1).get(name); - if( t != null ) return t; - } - return null; + if( st!=null ) setDef(v._idx,st); // Set new value + return v; } /** @@ -177,15 +189,12 @@ public class ScopeNode extends Node { // 1) duplicate the name bindings of the ScopeNode across all stack levels // 2) Make the new ScopeNode a user of all the nodes bound // 3) Ensure that the order of defs is the same to allow easy merging - for( HashMap syms : _idxs ) - dup._idxs.push(new HashMap<>(syms)); - for( HashMap ts : _decls ) - dup._decls.push(ts); // Types don't change, just keep stacks aligned - + dup._vars.addAll(_vars); + dup._lexSize.addAll(_lexSize); dup.addDef(ctrl()); // Control input is just copied + + // Memory is one binding, with the same lazy loop Phi as scalar names. for( int i=1; i (x << i) + (c << i) - if( lhs instanceof AddNode add && add.in(2)._type instanceof TypeInteger c && c.isConstant() ) { + if( lhs instanceof AddNode add && add.in(2)._type instanceof TypeInteger c && c.isConstant() && c!=TypeInteger.ZERO ) { long sum = c.value() << shl.value(); if( Integer.MIN_VALUE <= sum && sum <= Integer.MAX_VALUE ) return new AddNode( new ShlNode(add.in(1),rhs).peephole(), Parser.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 70fd630d..bc6881f1 100644 --- a/src/main/java/com/seaofnodes/simple/node/ShrNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ShrNode.java @@ -14,8 +14,13 @@ public class ShrNode extends Node { @Override public Type compute() { - if (in(1)._type instanceof TypeInteger i0 && - in(2)._type instanceof TypeInteger i1) { + Type t1 = in(1)._type, t2 = in(2)._type; + if( t1.isHigh() || t2.isHigh() ) + return TypeInteger.TOP; + if (t1 instanceof TypeInteger i0 && + t2 instanceof TypeInteger i1 ) { + if( i0 == TypeInteger.ZERO ) + return TypeInteger.ZERO; if( i0.isConstant() && i1.isConstant() ) return TypeInteger.constant(i0.value()>>>i1.value()); } diff --git a/src/main/java/com/seaofnodes/simple/node/StartNode.java b/src/main/java/com/seaofnodes/simple/node/StartNode.java index 812305d5..23830a18 100644 --- a/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ b/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -5,16 +5,14 @@ import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.type.*; import java.util.BitSet; import java.util.HashSet; +import static com.seaofnodes.simple.Utils.TODO; /** Start supplies control, whole memory, and the argument, in that order. */ public class StartNode extends CFGNode implements MultiNode { - private final TypeTuple _args; + final Type _arg; - public StartNode(Type[] args) { - super(); - _type = _args = TypeTuple.make(args); - } + public StartNode(Type arg) { super(); _arg = arg; _type = compute(); } @Override public String label() { return "Start"; } @@ -26,11 +24,11 @@ public class StartNode extends CFGNode implements MultiNode { @Override public boolean blockHead() { return true; } @Override public CFGNode cfg0() { return this; } - @Override - public TypeTuple compute() { return _args; } + @Override public TypeTuple compute() { + return TypeTuple.make(Type.CONTROL,TypeMem.BOT,_arg); + } - @Override - public Node idealize() { return null; } + @Override public Node idealize() { return null; } // No immediate dominator, and idepth==0 @Override public int idepth() { return 0; } diff --git a/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/src/main/java/com/seaofnodes/simple/node/StoreNode.java index 485491c0..8eb104a7 100644 --- a/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -64,23 +64,48 @@ public class StoreNode extends MemOpNode { ptr()._type instanceof TypeMemPtr && // No bother if weird dead pointers // Must have exactly one use of "this" or you get weird // non-serializable memory effects in the worse case. - st.checkNoUseBeyond(this) ) { + checkOnlyUse(st) ) { assert _name.equals(st._name); // Equiv class aliasing is perfect setDef(1,st.mem()); return this; } + // Simple store-after-new on same address. Should pick up the + // an init-store being stomped by a first user store. + if( mem() instanceof ProjNode st && st .in(0) instanceof NewNode nnn && + ptr() instanceof ProjNode ptr && ptr.in(0) == nnn && + ptr()._type instanceof TypeMemPtr tmp && // No bother if weird dead pointers + // Cannot fold a store of a single element over the array body initializer value + !(tmp._obj.isAry() && tmp._obj._fields[1]._alias==_alias) && + // Very sad strong cutout: val has to be legal to hoist to a New + // input, which means it cannot depend on the New. Many many + // things are legal here but difficult to check without doing a + // full dominator check. Example failure: + // "struct C { C? c; }; C self = new C { c=self; }" + val()._type.isHighOrConst() && + // Must have exactly one use of "this" or you get weird + // non-serializable memory effects in the worse case. + checkOnlyUse(st) && + // Folding away a broken store + err()==null ) { + nnn.setDef(nnn.findAlias(_alias),val()); + // Must retype the NewNode + nnn ._type = nnn. compute(); + mem()._type = mem().compute(); + return st; + } + return null; } - // Check that `this` has no uses beyond `that` - private boolean checkNoUseBeyond(Node that) { - if( nOuts()==1 ) return true; + // Check that "mem" has no uses except "this" + private boolean checkOnlyUse(Node mem) { + if( mem.nOuts()==1 ) return true; // Add deps on the other uses (can be e.g. ScopeNode mid-parse) so that // when the other uses go away we can retry. - for( Node use : _outputs ) - if( use != that ) - use.addDep(that); + for( Node use : mem._outputs ) + if( use != this ) + use.addDep(this); return false; } diff --git a/src/main/java/com/seaofnodes/simple/node/StructNode.java b/src/main/java/com/seaofnodes/simple/node/StructNode.java new file mode 100644 index 00000000..429b49cc --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/StructNode.java @@ -0,0 +1,45 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.type.*; + +/** + * Build a compound object + */ +public class StructNode extends Node { + + public TypeStruct _ts; + + @Override public String label() { return _ts==null ? "STRUCT?" : _ts.str(); } + + @Override + protected ExprPrinter _print1(ExprPrinter p) { + if( _ts==null ) return p.p("STRUCT?"); + p.p(_ts._name).p(" {"); + for( int i=0; i { - @Override protected String[] edgeNames(Node n) { - return n instanceof ScopeNode scope && n.nIns()!=0 ? scope.reverseNames() : null; + @Override protected String edgeName(Node n, int i) { + return n instanceof ScopeNode scope && i < scope._vars.size() + ? scope._vars.get(i)._name : null; } @Override protected int projectionIndex(Node n) { return n instanceof ProjNode p ? p._idx : n instanceof CProjNode p ? p._idx : -1; } diff --git a/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java index 450922d7..0d6fce44 100644 --- a/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java +++ b/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java @@ -7,7 +7,7 @@ import com.seaofnodes.simple.node.Node; import java.util.ArrayList; import java.io.IOException; -/** Chapter 15's compilation context; capture and event bookkeeping are shared. */ +/** Chapter 16's compilation context; capture and event bookkeeping are shared. */ public class SimpleGraphObserver extends GraphCapture { private Parser _parser; private Node _ret; diff --git a/src/main/java/com/seaofnodes/simple/type/Field.java b/src/main/java/com/seaofnodes/simple/type/Field.java index 5e674132..e2c0aa59 100644 --- a/src/main/java/com/seaofnodes/simple/type/Field.java +++ b/src/main/java/com/seaofnodes/simple/type/Field.java @@ -12,52 +12,59 @@ public class Field extends Type { // Field name public final String _fname; - // Unique memory alias - public final int _alias; // Type of the field public final Type _type; + // Unique memory alias, not sensibly part of a "type" but very convenient here. + public final int _alias; + // Field must be written to exactly once, no more, no less + public final boolean _final; - private Field(String fname, int alias, Type type ) { + private Field(String fname, Type type, int alias, boolean xfinal ) { super(TFLD); _fname = fname; - _alias = alias; _type = type; + _alias = alias; + _final = xfinal; } // Make with existing alias - public static Field make( String fname, int alias, Type type ) { - return new Field(fname,alias,type).intern(); + public static Field make( String fname, Type type, int alias, boolean xfinal ) { + return new Field(fname,type,alias,xfinal).intern(); + } + public Field makeFrom( Type type ) { + return new Field(_fname,type,_alias,_final).intern(); } - public static final Field TEST = make("test",-2,TypeInteger.ZERO); + public static final Field TEST = make("test",TypeInteger.ZERO,-2,false); public static void gather(ArrayList ts) { ts.add(TEST); } @Override Field xmeet( Type that ) { Field fld = (Field)that; // Invariant assert _fname.equals(fld._fname); assert _alias==fld._alias; - return make(_fname,_alias,_type.meet(fld._type)); + assert _final==fld._final; + return make(_fname,_type.meet(fld._type),_alias,_final); } @Override - public Field dual() { return make(_fname,_alias,_type.dual()); } + public Field dual() { return make(_fname,_type.dual(),_alias,_final); } @Override public Field glb() { - return make(_fname,_alias,_type.glb()); + return make(_fname,_type.glb(),_alias,_final); } // Override in subclasses - int hash() { return _fname.hashCode() ^ _type.hashCode() ^ _alias; } + int hash() { return _fname.hashCode() ^ _type.hashCode() ^ _alias ^ (_final ? 1024 : 0); } boolean eq(Type t) { Field f = (Field)t; - return _fname.equals(f._fname) && _alias==f._alias && _type==f._type; + return _fname.equals(f._fname) && _type==f._type && _alias==f._alias && _final==f._final; } @Override public StringBuilder print( StringBuilder sb ) { - return _type.print(sb.append(_fname).append(":").append(_alias).append(" : ")); + return _type.print(sb.append(_final?"!":"").append(_fname).append(":").append(_alias).append(" : ")); } @Override public String str() { return _fname; } diff --git a/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/src/main/java/com/seaofnodes/simple/type/TypeInteger.java index 042b4ba8..b475055d 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeInteger.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeInteger.java @@ -117,7 +117,7 @@ public class TypeInteger extends Type { @Override public TypeInteger makeInit() { return ZERO; } @Override public TypeInteger nonZero() { if( isHigh() ) return this; - if( _min==0 ) return make(1,_max); // specifically good on BOOL + if( _min==0 ) return make(1,Math.max(_max,1)); // specifically good on BOOL if( _max==0 ) return make(_min,-1); return this; } diff --git a/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java b/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java index ceba733d..9b628bea 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java @@ -61,6 +61,8 @@ public class TypeMemPtr extends Type { @Override public Type nonZero() { return VOIDPTR; } @Override public boolean isHigh() { return this==TOP; } + @Override public boolean isConstant() { return this==NULLPTR; } + @Override public boolean isHighOrConst() { return this==TOP || this==NULLPTR; } @Override public int log_size() { return 2; } // (1<<2)==4-byte pointers diff --git a/src/main/java/com/seaofnodes/simple/type/TypeStruct.java b/src/main/java/com/seaofnodes/simple/type/TypeStruct.java index 922b7914..75f24c03 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeStruct.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeStruct.java @@ -41,21 +41,22 @@ public class TypeStruct extends Type { // Array public static TypeStruct makeAry(TypeInteger len, int lenAlias, Type body, int bodyAlias) { assert !(body instanceof TypeMemPtr tmp && !tmp._nil); - return make(body.str() + "[]", - Field.make("#" , lenAlias,len ), - Field.make("[]",bodyAlias,body)); + return make("[" + body.str() + "]", + Field.make("#" ,len , lenAlias,true), + Field.make("[]",body,bodyAlias,true)); } // A pair of self-cyclic types private static final TypeStruct S1F = make("S1"); private static final TypeStruct S2F = make("S2"); - public static final TypeStruct S1 = make("S1", new Field[]{ Field.make("a", -1, TypeInteger.BOT), Field.make("s2",-2,TypeMemPtr.make(S2F,false)) }); - private static final TypeStruct S2 = make("S2", new Field[]{ Field.make("b", -3, TypeFloat .BOT), Field.make("s1",-4,TypeMemPtr.make(S1F,false)) }); + public static final TypeStruct S1 = make("S1", new Field[]{ Field.make("a", TypeInteger.BOT, -1, false), Field.make("s2",TypeMemPtr.make(S2F,false),-2, false) }); + private static final TypeStruct S2 = make("S2", new Field[]{ Field.make("b", TypeFloat .BOT, -3, false), Field.make("s1",TypeMemPtr.make(S1F,false),-4, false) }); private static final TypeStruct ARY = makeAry(TypeInteger.BOT,-1,TypeFloat.BOT,-2); public static void gather(ArrayList ts) { ts.add(TEST); ts.add(BOT); ts.add(S1); ts.add(S2); ts.add(ARY); } + // Find field index by name public int find(String fname) { for( int i=0; i<_fields.length; i++ ) if( _fields[i]._fname.equals(fname) ) @@ -63,6 +64,15 @@ public class TypeStruct extends Type { return -1; } + // Find field index by alias + public int findAlias( int alias ) { + for( int i=0; i<_fields.length; i++ ) + if( _fields[i]._alias==alias ) + return i; + return -1; + } + + @Override Type xmeet(Type t) { TypeStruct that = (TypeStruct) t; @@ -152,10 +162,10 @@ public class TypeStruct extends Type { if( this==TOP ) return sb.append("StructTop"); sb.append(_name); if( _fields == null ) return sb; // Forward reference struct, just print the name - sb.append(" {\n"); + sb.append(" {"); for( Field f : _fields ) { sb.append(" ").append(f._fname).append(" :"); - f._type.print(sb).append(";\n"); + f._type.print(sb).append("; "); } return sb.append("}"); } diff --git a/src/main/java/com/seaofnodes/simple/type/TypeTuple.java b/src/main/java/com/seaofnodes/simple/type/TypeTuple.java index 59ddf34b..a34c64db 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeTuple.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeTuple.java @@ -7,15 +7,17 @@ public class TypeTuple extends Type { public final Type[] _types; private TypeTuple(Type[] types) { super(TTUPLE); _types = types; } - public static TypeTuple make(Type... types) { return new TypeTuple(types).intern(); } + public static TypeTuple make(Type... types) { return new TypeTuple(types).intern(); } - private static TypeTuple TEST = make(TypeInteger.BOT,TypeMemPtr.TEST); - public static void gather(ArrayList ts) { ts.add(TEST); } + private static final TypeTuple TEST = make(TypeInteger.BOT,TypeMemPtr.TEST); + public static final TypeTuple START = make(Type.CONTROL,TypeMem.TOP,TypeInteger.BOT); + public static void gather(ArrayList ts) { ts.add(TEST); ts.add(START); } @Override Type xmeet(Type other) { TypeTuple tt = (TypeTuple)other; // contract from xmeet - assert _types.length == tt._types.length; + if( _types.length != tt._types.length ) + return BOTTOM; Type[] ts = new Type[_types.length]; for( int i=0; i<_types.length; i++ ) ts[i] = _types[i].meet(tt._types[i]); -- cgit v1.2.3