diff options
| author | Cliff Click <cliffc@acm.org> | 2024-10-12 14:02:06 -0700 |
|---|---|---|
| committer | Cliff Click <cliffc@acm.org> | 2024-10-23 17:51:09 -0700 |
| commit | 1ee105dce9c32460feed5c3a20c42abbaa7c1896 (patch) | |
| tree | 1746b458d995622bc11d796e48fe288240709f99 /src | |
| parent | 67dc6202dee2ce983778ba3143cc3f00196aeb7c (diff) | |
| download | Simple-linear-chapter16.tar.gz Simple-linear-chapter16.zip | |
Chapter 16: Constructors and Final fieldslinear-chapter16
Co-authored-by: Thalia Archibald <thalia@archibald.dev>
Diffstat (limited to 'src')
50 files changed, 1552 insertions, 665 deletions
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<E> extends AbstractList<E> implements List<E> { + 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<E> 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<E> 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<? extends E> c ) { + if( c==null ) return false; + for( E e : c ) add(e); + return true; + } + public Ary<E> addAll( Iterable<? extends E> it ) { for( E e : it ) add(e); return this; } + + /** @param es Array to be added */ + public <F extends E> Ary<E> 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<E> addAll( Ary<? extends E> 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<? super E> 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<E> iterator() { return new Iter(); } + private class Iter implements Iterator<E> { + 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 2eac6ccc..29841266 100644 --- a/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java +++ b/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java @@ -66,9 +66,13 @@ 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); + if( cfg instanceof RegionNode ) + for( Node phi : cfg._outputs ) + if( phi instanceof PhiNode ) + _schedEarly(phi,visit); } } @@ -91,7 +95,7 @@ public abstract class GlobalCodeMotion { // a data-only loop, eventually attempting relying on some pre-visited- // not-post-visited data op with no scheduled control. for( Node def : n._inputs ) - if( def!=null ) + if( def!=null && !(def instanceof PhiNode) ) _schedEarly(def,visit); // If not-pinned (e.g. constants, projections, phi) and not-CFG if( !n.isPinned() ) { @@ -134,13 +138,14 @@ 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 if( n instanceof LoadNode ld ) for( Node memuse : ld.mem()._outputs ) - if( late[memuse._nid]==null && + if( !(memuse instanceof PhiNode) && + late[memuse._nid]==null && // Load-use directly defines memory (memuse._type instanceof TypeMem || // Load-use indirectly defines memory @@ -235,6 +240,7 @@ public abstract class GlobalCodeMotion { break; case LoadNode ld: break; // Loads do not cause anti-deps on other loads case ReturnNode ret: break; // Load must already be ahead of Return + case ScopeMinNode ret: break; // Mem uses now on ScopeMin case NeverNode never: break; default: throw Utils.TODO(); } @@ -245,11 +251,11 @@ public abstract class GlobalCodeMotion { // private static CFGNode anti_dep( LoadNode load, CFGNode stblk, CFGNode defblk, CFGNode lca, Node st ) { // Walk store blocks "reach" from its scheduled location to its earliest - for( ; stblk != defblk.idom(); stblk = stblk.idom() ) { + for( ; stblk != defblk; stblk = stblk.idom() ) { // Store and Load overlap, need anti-dependence - if( stblk._anti==load._nid ) { + if( !(stblk instanceof IfNode) && stblk._anti==load._nid ) { lca = stblk._idom(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/GraphVisualizer.java b/src/main/java/com/seaofnodes/simple/GraphVisualizer.java index f5fa1350..de6d65a3 100644 --- a/src/main/java/com/seaofnodes/simple/GraphVisualizer.java +++ b/src/main/java/com/seaofnodes/simple/GraphVisualizer.java @@ -1,10 +1,11 @@ package com.seaofnodes.simple; -import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.node.*; import java.util.*; +import static com.seaofnodes.simple.Utils.TODO; + /** * Simple visualizer that outputs GraphViz dot format. * The dot output must be saved to a file and run manually via dot to generate the SVG output. @@ -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 ScopeNode || n==Parser.XCTRL) + if (n instanceof ProjNode || n instanceof CProjNode || n instanceof ScopeMinNode || n==Parser.XCTRL) 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; @@ -154,26 +155,34 @@ public class GraphVisualizer { // Build a nested scope display, walking the _prev edge private void scope( StringBuilder sb, ScopeNode scope ) { sb.append("\tnode [shape=plaintext];\n"); - int level=1; - for( int idx = scope._idxs.size()-1; idx>=0; idx-- ) { - var syms = scope._idxs.get(idx); + int last = scope.nIns(); + int max = scope._lexSize.size(); + for( int i = 0; i < max; i++ ) { + int level = max-i-1; String scopeName = makeScopeName(scope, level); sb.append("\tsubgraph cluster_").append(scopeName).append(" {\n"); // Magic "cluster_" in the subgraph name + // Special for memory ScopeMinNode + if( level==0 ) { + ScopeMinNode 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"); sb.append("\t\t\t<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">\n"); // Add the scope level - int scopeLevel = scope._idxs.size()-level; - sb.append("\t\t\t<TR><TD BGCOLOR=\"cyan\">").append(scopeLevel).append("</TD>"); - for(String name: syms.keySet()) - sb.append("<TD PORT=\"").append(makePortName(scopeName, name)).append("\">").append(name).append("</TD>"); + sb.append("\t\t\t<TR><TD BGCOLOR=\"cyan\">").append(level).append("</TD>"); + int lexStart=scope._lexSize.at(level); + for( int j=lexStart; j<last; j++ ) { + var v = scope._vars.at(j); + sb.append("<TD PORT=\"").append(makePortName(scopeName, v._name)).append("\">").append(v._name).append("</TD>"); + } + last = lexStart; sb.append("</TR>\n"); sb.append("\t\t\t</TABLE>>];\n"); - level++; } // Scope clusters nest, so the graphics shows the nested scopes, so // they are not closed as they are printed; so they just keep nesting. // We close them all at once here. - sb.append( "\t}\n".repeat( level-1 ) ); // End all Scope clusters + sb.append( "\t}\n".repeat( max ) ); // End all Scope clusters } private String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; } @@ -229,27 +238,25 @@ public class GraphVisualizer { // Walk the scope edges private void scopeEdges( StringBuilder sb, ScopeNode scope ) { sb.append("\tedge [style=dashed color=cornflowerblue];\n"); - int level=1; - for( int i = scope._idxs.size()-1; i>=0; i-- ) { - var syms = scope._idxs.get(i); - String scopeName = makeScopeName(scope, level); - for( String name : syms.keySet() ) { - int idx = syms.get(name); - Node def = scope.in(idx); - while( def instanceof ScopeNode lazy ) - def = lazy.in(idx); - if( def==null ) continue; - sb.append("\t") - .append(scopeName).append(":") - .append('"').append(makePortName(scopeName, name)).append('"') // wrap port name with quotes because $ctrl is not valid unquoted - .append(" -> "); - if( def instanceof CProjNode proj ) { - String mname = proj.ctrl().uniqueName(); - sb.append(mname).append(":p").append(proj._idx); - } else sb.append(def.uniqueName()); - sb.append(";\n"); - } - level++; + int level=0; + for( var v : scope._vars ) { + Node def = scope.in(v._idx); + while( def instanceof ScopeNode lazy ) + def = lazy.in(v._idx); + if( def==null ) continue; + while( level < scope._lexSize.size() && v._idx >= scope._lexSize.at(level) ) + level++; + String scopeName = makeScopeName(scope, level-1); + sb.append("\t") + .append(scopeName).append(":") + .append('"').append(makePortName(scopeName, v._name)).append('"') // wrap port name with quotes because $ctrl is not valid unquoted + .append(" -> "); + if( def instanceof CProjNode proj ) { + sb.append(def.in(0).uniqueName()).append(":p").append(proj._idx); + } else if( def instanceof ProjNode proj ) { + sb.append(def.in(0).uniqueName()).append(":p").append(proj._idx); + } else sb.append(def.uniqueName()); + sb.append(";\n"); } } diff --git a/src/main/java/com/seaofnodes/simple/IterPeeps.java b/src/main/java/com/seaofnodes/simple/IterPeeps.java index 783fc4f1..96ae350e 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<Node> ary ) { + public static void addAll( Ary<Node> ary ) { WORK.addAll(ary); } @@ -170,7 +170,7 @@ public abstract class IterPeeps { return x; } - public void addAll( ArrayList<E> ary ) { + public void addAll( Ary<E> 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 4bb18d6b..a8064e17 100644 --- a/src/main/java/com/seaofnodes/simple/Parser.java +++ b/src/main/java/com/seaofnodes/simple/Parser.java @@ -2,12 +2,13 @@ package com.seaofnodes.simple; 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. * <p> * This is a simple recursive descent parser. All lexical analysis is done here as well. @@ -95,35 +96,26 @@ 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<String, Type> TYPES = new HashMap<>(); + public final HashMap<String, Type> TYPES; + + // Mapping from a type name to the constructor for a Type. + public final HashMap<String, StructNode> INITS; + public Parser(String source, TypeInteger arg) { 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, 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) { @@ -133,13 +125,29 @@ public class Parser { @Override public String toString() { return _lexer.toString(); } - String src() { return new String( _lexer._input ); } + public static HashMap<String, Type> 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() { return parse(false); } @@ -147,8 +155,12 @@ public class Parser { _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, 1, ScopeNode.ARG0).peephole()); + ScopeMinNode mem = new ScopeMinNode(); + mem.addDef(null); + _scope.define(ScopeNode.CTRL, Type.CONTROL , false, new CProjNode(START, 0, ScopeNode.CTRL).peephole()); + mem.addDef( new ProjNode(START, 1, ScopeNode.MEM0).peephole()); + _scope.define(ScopeNode.MEM0, TypeMem.TOP , false, mem .peephole()); + _scope.define(ScopeNode.ARG0, TypeInteger.BOT, false, new ProjNode(START, 2, ScopeNode.ARG0).peephole()); // Parse whole program parseBlock(); @@ -157,6 +169,9 @@ public class Parser { 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(); if( show ) showGraph(); @@ -205,49 +220,6 @@ public class Parser { // to ambiguity else return parseExpressionStatement(); } - - /** - * Parse a struct field. - * <pre> - * type IDENTIFIER ; - * </pre> - */ - 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<Field> 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)); - START.addMemProj(ts, _scope); // Insert memory edges - return parseStatement(); - } - /** * Parses a while statement * @@ -328,6 +300,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; } @@ -336,15 +310,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; } @@ -429,43 +403,44 @@ public class Parser { return null; } - /** - * Parses an expression statement or a declaration statement where type is a struct - * - * <pre> - * 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 - * </pre> - * @return an expression {@link Node}, never {@code null} + /** Parse: name [=expr] */ - private Node parseExpressionStatement() { + private Node parseAsgn(Type t, boolean xfinal) { + 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 = xfinal || 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 + ScopeMinNode.Var def = _scope.lookup(name); + if( def==null ) throw error("Undefined name '" + name + "'"); - t = _scope.lookupDeclaredType(name); + // TOP fields are for late-initialized fields; these have never + // been written to, and this must be the final write. Other writes + // need to check the final bit. + if( _scope.in(def._idx)._type!=Type.TOP && def._final ) + throw error("Cannot reassign final '"+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(); @@ -478,9 +453,93 @@ 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,xfinal,expr) ) + throw error("Redefining name '" + name + "'"); + } else + _scope.update(name,expr); + return expr; + } + + /** Parse final: [!]asgn + */ + private Node parseFinal(Type t) { + boolean xfinal = t!=null && match("!"); + return parseAsgn(t,xfinal); + } + + /** + * Parses an expression statement or a declaration statement where type is a struct + * + * <pre> + * 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 + + * </pre> + * @return an expression {@link Node}, never {@code null} + */ + private Node parseExpressionStatement() { + Node n; + Type t = type(); + if( t != null ) { + // now parse final [, final]* + n = parseFinal(t); + while( match(",") ) + n = parseFinal(t); + + // Parse "asgn;" which is just "name = expr;" + } else if( ( n = parseAsgn(null,false)) == 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<varlen; i++ ) { + s.addDef(_scope.in(i)); + ScopeMinNode.Var v = _scope._vars.at(i); + fs[i-lexlen] = Field.make(v._name,v._type,ALIAS++,v._final); + } + TypeStruct ts = s._ts = TypeStruct.make(typeName, fs); + TYPES.put(typeName, TypeMemPtr.make(ts)); + INITS.put(typeName,s.peephole().keep()); + // Done with struct/block scope + require("}"); + require(";"); + _scope.pop(); + return null; + } + + // Parse and return a type or null. Valid types always are followed by an // 'id' which the caller must parse. This lets us distinguish forward ref // types (which ARE valid here) from local vars in an (optional) forward @@ -529,14 +588,14 @@ public class Parser { private TypeMemPtr typeAry( Type t ) { if( t instanceof TypeMemPtr tmp && !tmp._nil ) throw error("Arrays of reference types must always be nullable"); - String tname = t.str()+"[]"; + String tname = "["+t.str()+"]"; Type ta = TYPES.get(tname); if( ta != null ) return (TypeMemPtr)ta; // Need make an array type. TypeStruct ts = TypeStruct.makeAry(TypeInteger.BOT,ALIAS++,t,ALIAS++); + assert ts.str().equals(tname); TypeMemPtr tary = TypeMemPtr.make(ts); TYPES.put(tname,tary); - START.addMemProj(ts, _scope); // Insert memory alias edges return tary; } @@ -694,67 +753,107 @@ public class Parser { if( match("(") ) return require(parseExpression(), ")"); if( matchx("true" ) ) return con(1); if( matchx("false") ) return ZERO; - if( matchx("null" ) ) return new ConstantNode(TypeMemPtr.NULLPTR).peephole(); - if( matchx("new") ) { - Type t = type(); - if( t==null ) throw error("Expected a type"); - if( match("[") ) { - Node len = parseExpression().keep(); - if( !(len._type instanceof TypeInteger) ) - throw error("Cannot allocate an array with length "+len._type); - require("]"); - TypeMemPtr tmp = typeAry(t); - return newArray(tmp._obj,len); - } - if( t instanceof TypeMemPtr tmp ) { - TypeStruct obj = tmp._obj; - if( obj._fields==null ) - throw error("Unknown struct type '" + obj._name + "'"); - if( obj.isAry() ) - throw Utils.TODO(); // Missing array length - return newStruct(obj,con(obj.offset(obj._fields.length))); - } - throw error("Cannot allocate a "+t.str()); - } + if( matchx("null" ) ) return con(TypeMemPtr.NULLPTR); + if( matchx("new" ) ) return alloc(); + // Expect an identifier now String name = _lexer.matchId(); if( name == null) throw errorSyntax("an identifier or expression"); - Node n = _scope.lookup(name); - if( n!=null ) return n; + ScopeMinNode.Var n = _scope.lookup(name); + if( n!=null ) return _scope.in(n); throw error("Undefined name '" + name + "'"); } /** - * Return a NewNode with pre-zeroed memory + Parse an allocation + */ + private Node alloc() { + Type t = type(); + if( t==null ) throw error("Expected a type"); + // Parse ary[ length_expr ] + if( match("[") ) { + Node len = parseExpression().keep(); + if( !(len._type instanceof TypeInteger) ) + throw error("Cannot allocate an array with length "+len._type); + require("]"); + TypeMemPtr tmp = typeAry(t); + 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(); + for( int i=0; i<fs.length; i++ ) + _scope.define(fs[i]._fname, fs[i]._type, fs[i]._final, s.in(i)); + // Parse the constructor body + parseBlock(); + require("}"); + init = _scope._inputs; + } + // Check that all fields are initialized + for( int i=idx; i<init.size(); i++ ) + if( init.get(i)._type == Type.TOP ) + 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, con(tmp._obj.offset(fs.length)), idx, init ); + if( hasConstructor ) + _scope.pop(); + return ptr; + } + + + /** + * 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) { + private Node newStruct( TypeStruct obj, Node size, int idx, Ary<Node> init ) { Field[] fs = obj._fields; - Node[] ns = new Node[2+fs.length]; - ns[0] = ctrl(); + if( fs==null ) + throw error("Unknown struct type '" + obj._name + "'"); + int len = fs.length; + Node[] ns = new Node[2+len+len]; + ns[0] = ctrl(); // Control in slot 0 + // Total allocated length in bytes ns[1] = size; - for( int i = 0; i < fs.length; i++ ) - ns[i+2] = memAlias(fs[i]._alias); + // Memory aliases for every field + for( int i = 0; i < len; i++ ) + ns[2 +i] = memAlias(fs[i]._alias); + // Initial values for every field + for( int i = 0; i < len; i++ ) + ns[2+len+i] = init.get(i+idx); Node nnn = new NewNode(TypeMemPtr.make(obj), ns).peephole(); - for( int i = 0; i < fs.length; i++ ) + for( int i = 0; i < len; i++ ) memAlias(fs[i]._alias, new ProjNode(nnn,i+2,memName(fs[i]._alias)).peephole()); return new ProjNode(nnn,1,obj._name).peephole(); } + private static final Ary<Node> 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 - memAlias(alias,new StoreNode("#",alias,TypeInteger.BOT,memAlias(alias),ptr,con( ary.offset(0) ), len.unkeep(), true ).peephole()); - return ptr; + ALTMP.clear(); ALTMP.add(len); ALTMP.add(con(ary._fields[1]._type.makeInit())); + return newStruct(ary,size,0,ALTMP); } // 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.lookup(memName(alias) ); } - private Node memAlias(int alias, Node st) { return _scope.update(memName(alias), st); } + 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(); } /** @@ -777,52 +876,49 @@ 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(); - Node st = new StoreNode(name, f._alias, f._type, memAlias(f._alias), expr.unkeep(), off.unkeep(), val, 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()); - return val.unkeep(); // "obj.a = expr" returns the expression while updating memory - } + 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('=','=') ) { + Node val = parseExpression(); + // Auto-truncate when storing to narrow fields + val = zsMask(val,f._type); + Node st = new StoreNode(name, f._alias, f._type, memAlias(f._alias), expr, off, val, 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()); + return val; // "obj.a = expr" returns the expression while updating memory } - Node load = new LoadNode(name, f._alias, f._type.glb(), memAlias(f._alias), expr.unkeep(), off); + Node load = new LoadNode(name, f._alias, f._type.glb(), 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() ) load.setDef(0,ctrl()); + //if( base.isAry() ) load.setDef(0,ctrl()); return parsePostfix(load.peephole()); } @@ -855,13 +951,9 @@ public class Parser { * floatLiteral: [digits].[digits]?[e [digits]]? * </pre> */ - 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 @@ -870,6 +962,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(); } @@ -989,6 +1082,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 7e4b898b..17a4e0ca 100644 --- a/src/main/java/com/seaofnodes/simple/node/AndNode.java +++ b/src/main/java/com/seaofnodes/simple/node/AndNode.java @@ -21,8 +21,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/CastNode.java b/src/main/java/com/seaofnodes/simple/node/CastNode.java index fbf0e518..462d6cba 100644 --- a/src/main/java/com/seaofnodes/simple/node/CastNode.java +++ b/src/main/java/com/seaofnodes/simple/node/CastNode.java @@ -21,8 +21,7 @@ public class CastNode extends Node { @Override public String uniqueName() { return "Cast_" + _nid; } - @Override - public boolean isPinned() { return true; } + @Override public boolean isPinned() { return true; } @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { diff --git a/src/main/java/com/seaofnodes/simple/node/IfNode.java b/src/main/java/com/seaofnodes/simple/node/IfNode.java index 5457ac6e..3cce89ab 100644 --- a/src/main/java/com/seaofnodes/simple/node/IfNode.java +++ b/src/main/java/com/seaofnodes/simple/node/IfNode.java @@ -41,8 +41,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 0bdf86f0..d15d4f83 100644 --- a/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -2,9 +2,7 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeMem; -import com.seaofnodes.simple.type.TypeStruct; +import com.seaofnodes.simple.type.*; import java.util.BitSet; /** @@ -38,7 +36,6 @@ public class LoadNode extends MemOpNode { if( mem()._type instanceof TypeMem mem && // No constant folding if ptr might null-check _declaredType != mem._t && err()==null ) { - assert mem._alias == _alias; return _declaredType.join(mem._t); } return _declaredType; @@ -57,8 +54,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(1) ) // Must check same object - return new ConstantNode(_declaredType.makeInit()); + ptr == nnn.proj(1) ) // 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. @@ -77,12 +74,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.in(_alias);// Bypass unrelated New @@ -125,7 +122,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/MulNode.java b/src/main/java/com/seaofnodes/simple/node/MulNode.java index f231a97b..b5ef91e9 100644 --- a/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -26,6 +26,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 f06564cd..ddb1e09a 100644 --- a/src/main/java/com/seaofnodes/simple/node/NewNode.java +++ b/src/main/java/com/seaofnodes/simple/node/NewNode.java @@ -1,6 +1,7 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.Utils; import java.util.BitSet; /** @@ -11,23 +12,30 @@ import java.util.BitSet; */ public class NewNode extends Node implements MultiNode { - public TypeMemPtr _ptr; + public final TypeMemPtr _ptr; + public final int _len; public NewNode(TypeMemPtr ptr, Node... nodes) { super(nodes); + _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; - // Memory slices in remaining slots - for( int i=2; i<nodes.length; i++ ) - assert nodes[i]._type instanceof TypeMem; - - _ptr = ptr; + for( int i=0; i<_len; i++ ) { + // Memory slices for all fields. + assert nodes[2+ i]._type instanceof TypeMem; + // Value slices for all fields. + assert nodes[2 + _len + i]._type != null; + } } @Override public String label() { - return "new_" + (_ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._type.str() : _ptr._obj.str()); + return "new_"+glabel(); + } + @Override public String glabel() { + return _ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._type.str() : _ptr._obj.str(); } @Override @@ -36,14 +44,23 @@ public class NewNode extends Node implements MultiNode { return sb.append(_ptr._obj.str()); } + // Find matching alias input + int findAlias(int alias) { + return 2+_ptr._obj.findAlias(alias)+_len; + } + + @Override public TypeTuple compute() { Field[] fs = _ptr._obj._fields; Type[] ts = new Type[fs.length+2]; ts[0] = Type.CONTROL; ts[1] = _ptr; - for( int i=0; i<fs.length; i++ ) - ts[i+2] = TypeMem.make(fs[i]._alias,fs[i]._type.makeInit()).meet( in(i+2)._type ); + for( int i=0; i<fs.length; i++ ) { + TypeMem mem = (TypeMem)in(i+2)._type; + Type tfld = in(2+_len+i)._type.meet(mem._t); + ts[i+2] = TypeMem.make(fs[i]._alias,tfld); + } return TypeTuple.make(ts); } diff --git a/src/main/java/com/seaofnodes/simple/node/Node.java b/src/main/java/com/seaofnodes/simple/node/Node.java index 461f153d..462a403c 100644 --- a/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/src/main/java/com/seaofnodes/simple/node/Node.java @@ -1,11 +1,11 @@ package com.seaofnodes.simple.node; +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; @@ -28,9 +28,9 @@ public abstract class Node implements OutNode { * <p> * 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 final ArrayList<Node> _inputs; + public final Ary<Node> _inputs; /** * Outputs reference Nodes that are not null and have this Node as an @@ -41,7 +41,7 @@ public abstract class Node implements OutNode { * walked in either direction. These outputs are typically used for * efficient optimizations but otherwise have no semantics meaning. */ - public final ArrayList<Node> _outputs; + public final Ary<Node> _outputs; /** @@ -61,9 +61,9 @@ public abstract class Node implements OutNode { protected Node(Node... inputs) { _nid = 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 ); @@ -127,7 +127,7 @@ public abstract class Node implements OutNode { */ public Node in(int i) { return _inputs.get(i); } public Node out(int i) { return _outputs.get(i); } - @Override public ArrayList<Node> outs() { return _outputs; } + @Override public Ary<Node> outs() { return _outputs; } public int nIns() { return _inputs.size(); } @@ -168,7 +168,7 @@ public abstract class Node implements OutNode { 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; } @@ -179,13 +179,20 @@ public abstract class Node implements OutNode { 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 <em>def->use</em> edge. @@ -210,16 +217,16 @@ public abstract class Node implements OutNode { // 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)); 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; i++ ) { - Node old_def = _inputs.removeLast(); + while( nIns() > 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 @@ -259,7 +266,7 @@ public abstract class Node implements OutNode { 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 @@ -268,7 +275,7 @@ public abstract class Node implements OutNode { 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); nnn.addUse(n); } @@ -294,7 +301,7 @@ public abstract class Node implements OutNode { return this; // Peephole optimizations turned off } Node n = peepholeOpt(); - return n==null ? this : deadCodeElim(n.peephole().keep()).unkeep(); // Cannot return null for no-progress + return n==null ? this : deadCodeElim(n.peephole()); // Cannot return null for no-progress } /** @@ -451,7 +458,7 @@ public abstract class Node implements OutNode { // 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<Node> _deps; + Ary<Node> _deps; /** * Add a node to the list of dependencies. Only add it if its not an input @@ -462,10 +469,10 @@ public abstract class Node implements OutNode { // Running peepholes during the big assert cannot have side effects // like adding dependencies. if( IterPeeps.midAssert() ) return this; - if( _deps==null ) _deps = new ArrayList<>(); - if( Utils.find(_deps ,dep) != -1 ) return this; // Already on list - if( Utils.find(_inputs,dep) != -1 ) return this; // No need for deps on immediate neighbors - if( 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( _inputs .find(dep) != -1 ) return this; // No need for deps on immediate neighbors + if( _outputs.find(dep) != -1 ) return this; _deps.add(dep); return this; } diff --git a/src/main/java/com/seaofnodes/simple/node/NotNode.java b/src/main/java/com/seaofnodes/simple/node/NotNode.java index b3c77c45..ffd3e01e 100644 --- a/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ b/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -22,7 +22,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) : i0; case TypeMemPtr p0: // top->top, bot->bot, null->1, *void->0, not-null ptr->0, ptr/nil->bot diff --git a/src/main/java/com/seaofnodes/simple/node/OrNode.java b/src/main/java/com/seaofnodes/simple/node/OrNode.java index 1b32d4fb..b572346f 100644 --- a/src/main/java/com/seaofnodes/simple/node/OrNode.java +++ b/src/main/java/com/seaofnodes/simple/node/OrNode.java @@ -21,8 +21,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<Node> outs(); + Ary<Node> outs(); } diff --git a/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/src/main/java/com/seaofnodes/simple/node/PhiNode.java index 139e80a3..f0140ce6 100644 --- a/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -43,7 +43,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 bd05ce01..941de76b 100644 --- a/src/main/java/com/seaofnodes/simple/node/RegionNode.java +++ b/src/main/java/com/seaofnodes/simple/node/RegionNode.java @@ -68,7 +68,7 @@ public class RegionNode extends CFGNode { if( !hasPhi() && // No Phi users, just a control user in(1) instanceof CProjNode p1 && in(2) instanceof CProjNode p2 && - p1.in(0)==p2.in(0) && + p1.in(0).addDep(this)==p2.in(0).addDep(this) && p1.in(0) instanceof IfNode iff ) return iff.ctrl(); @@ -102,7 +102,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; @@ -110,6 +110,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<n.nIns(); i++ ) { // Using a tested value if( n.in(i) == pred && !(n instanceof CastNode) ) { @@ -121,7 +122,7 @@ public class RegionNode extends CFGNode { n.setDef( i, cast ); IterPeeps.add(n); progress = true; - } + } else pred.addDep( this ); } if( n.in(i)!=null ) progress |= walk(iff,cproj,n.in(i)); diff --git a/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/src/main/java/com/seaofnodes/simple/node/ReturnNode.java index e12f1fef..de4eba52 100644 --- a/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -16,17 +16,11 @@ import java.util.BitSet; public class ReturnNode extends CFGNode { public ReturnNode(Node ctrl, Node data, ScopeNode scope) { - super(ctrl, data); // Add memory slices to Return, so all memory updates are live-on-exit. - // We look up memory slices by the naming convention that they start with $. - // We could also use implicit knowledge that all memory projects are at offset >= 2 - if( scope != null ) { - String[] names = scope.reverseNames(); - for (String name: names) { - if (!name.equals("$ctrl") && name.startsWith("$")) - addDef(scope.lookup(name)); - } - } + super(ctrl, data); + if( scope!=null ) + for( int i=2; i<scope.mem().nIns(); i++ ) + addDef(scope.mem().in(i)); } public Node ctrl() { return in(0); } diff --git a/src/main/java/com/seaofnodes/simple/node/SarNode.java b/src/main/java/com/seaofnodes/simple/node/SarNode.java index ba99fde8..5dc26ad3 100644 --- a/src/main/java/com/seaofnodes/simple/node/SarNode.java +++ b/src/main/java/com/seaofnodes/simple/node/SarNode.java @@ -21,8 +21,13 @@ public class SarNode 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()); if( i1.isConstant() ) { diff --git a/src/main/java/com/seaofnodes/simple/node/ScopeMinNode.java b/src/main/java/com/seaofnodes/simple/node/ScopeMinNode.java new file mode 100644 index 00000000..e4874e15 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/ScopeMinNode.java @@ -0,0 +1,137 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.IterPeeps; +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.type.*; +import java.util.*; + +import static com.seaofnodes.simple.Utils.TODO; + +/* */ +public class ScopeMinNode extends Node { + + /** The tracked fields are now complex enough to deserve a array-of-structs layout + */ + 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 ScopeMinNode() { _type = TypeMem.BOT; } + + + @Override public String label() { return "MEM"; } + + @Override + StringBuilder _print1(StringBuilder sb, BitSet visited) { + sb.append("MEM[ "); + for( int j=2; j<nIns(); j++ ) { + sb.append(j); + sb.append(":"); + Node n = in(j); + while( n instanceof ScopeNode loop ) { + sb.append("Lazy_"); + n = loop.in(j); + } + if( n==null ) sb.append("___ "); + else n._print0(sb, visited).append(" "); + } + sb.setLength(sb.length()-1); + return sb.append("]"); + } + + + @Override public Type compute() { return TypeMem.BOT; } + + @Override public Node idealize() { return null; } + + public Node in( Var v ) { return in(v._idx); } + + Node alias( int alias ) { + return in(alias<nIns() && in(alias)!=null ? alias : 1); + } + + Node alias( int alias, Node st ) { + while( alias >= nIns() ) addDef(null); + return setDef(alias,st); + } + + + // Read or update from memory. + // A shared implementation allows us to create lazy phis both during + // lookups and updates; the lazy phi creation is part of chapter 8. + Node _mem( int alias, Node st ) { + // 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 ) { + ScopeMinNode 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.BOT,loop.ctrl(),loopmem._mem(alias,null),null).peephole() ); + alias(alias,old); + } + // Memory projections are made lazily; expand as needed + return st==null ? old : alias(alias,st); // Not lazy, so this is the answer + } + + + void _merge(ScopeMinNode that, RegionNode r) { + int len = Math.max(nIns(),that.nIns()); + for( int i = 2; i < len; i++) + if( alias(i) != that.alias(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 = _vars.at(i); + Node lhs = this._mem(i,null); + Node rhs = that._mem(i,null); + alias(i, new PhiNode(Parser.memName(i), TypeMem.BOT, r, lhs, rhs).peephole()); + } + } + + // Fill in the backedge of any inserted Phis + void _endLoopMem( ScopeNode scope, ScopeMinNode back, ScopeMinNode exit ) { + for( int i=2; i<back.nIns(); i++ ) { + if( back.in(i) != scope ) { + PhiNode phi = (PhiNode)in(i); + assert phi.region()==scope.ctrl() && phi.in(2)==null; + phi.setDef(2,back.in(i)); // Fill backedge + } + if( exit.alias(i) == scope ) // Replace a lazy-phi on the exit path also + exit.alias(i,in(i)); + } + } + + // Now one-time do a useless-phi removal + void _useless( ) { + for( int i=2; i<nIns(); i++ ) { + if( in(i) instanceof PhiNode phi ) { + // Do an eager useless-phi removal + Node in = phi.peephole(); + IterPeeps.addAll(phi._outputs); + phi.moveDepsToWorklist(); + if( in != phi ) { + if( !phi.iskeep() ) // Keeping phi around for parser elsewhere + phi.subsume(in); + setDef(i,in); // Set the update back into Scope + } + } + } + } + +} diff --git a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java index 877ae4a4..11e54d82 100644 --- a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -3,14 +3,17 @@ package com.seaofnodes.simple.node; 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.*; +import static com.seaofnodes.simple.Utils.TODO; + /** * The Scope node is purely a parser helper - it tracks names to nodes with a - * stack of scopes. + * stack of hashmaps. */ -public class ScopeNode extends Node { +public class ScopeNode extends ScopeMinNode { /** * The control is a name that binds to the currently active control @@ -18,44 +21,49 @@ 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 - */ - public final Stack<HashMap<String, Integer>> _idxs; + // All active/live variables in all nested scopes, all run together + public final Ary<Var> _vars; + + // Since of each nested lexical scope + public final Ary<Integer> _lexSize; - /** - * Map variable names to declared Type - */ - final Stack<HashMap<String, Type>> _decls; // A new ScopeNode - public ScopeNode() { - _idxs = new Stack<>(); - _decls= new Stack<>(); - _type = Type.BOTTOM; - } + public ScopeNode() { _vars = new Ary<>(Var.class); _lexSize = new Ary<>(Integer.class); } @Override public String label() { return "Scope"; } + @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { sb.append("Scope[ "); - String[] names = reverseNames(); - for( int j=0; j<nIns(); j++ ) { - sb.append(names[j]).append(":"); - Node n = in(j); + int j=1; + for( int i=0; i<nIns(); i++ ) { + if( j < _lexSize._len && i == _lexSize.at(j) ) { sb.append("| "); j++; } + Var v = _vars.get(i); + v._type.print(sb); + sb.append(" "); + if( v._final ) sb.append("!"); + sb.append(v._name); + sb.append("="); + Node n = in(i); while( n instanceof ScopeNode loop ) { sb.append("Lazy_"); - n = loop.in(j); + n = loop.in(i); } - n._print0(sb, visited).append(" "); + if( n==null ) sb.append("___"); + else n._print0(sb, visited); + sb.append(", "); } - sb.setLength(sb.length()-1); + sb.setLength(sb.length()-2); return sb.append("]"); } + public Node ctrl() { return in(0); } + public ScopeMinNode mem() { return (ScopeMinNode)in(1); } /** * The ctrl of a ScopeNode is always bound to the currently active @@ -69,91 +77,85 @@ public class ScopeNode extends Node { */ public Node ctrl(Node n) { return setDef(0,n); } - /** - * Recover the names for all variable bindings. - * The result is an array of names that is aligned with the - * inputs to the Node. - * - * This is an expensive operation. - */ - public String[] reverseNames() { - String[] names = new String[nIns()]; - for( HashMap<String,Integer> 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<String,Integer> 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; } + // 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); } + + /** * 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. - * <p> - * 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,new PhiNode(name, lookupDeclaredType(name),loop.ctrl(),loop.update(name,null,nestingLevel),null).peephole()); - setDef(idx,old); + : loop.setDef(v._idx,new PhiNode(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; } /** @@ -176,13 +178,22 @@ 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<String,Integer> syms : _idxs ) - dup._idxs.push(new HashMap<>(syms)); - for( HashMap<String,Type> 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 - for( int i=1; i<nIns(); i++ ) + + // Memory input is a shallow copy + ScopeMinNode memdup = new ScopeMinNode(), 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); + + // Copy of other inputs + for( int i=2; i<nIns(); i++ ) // For lazy phis on loops we use a sentinel // that will trigger phi creation on update dup.addDef(loop ? this : in(i)); @@ -199,49 +210,55 @@ public class ScopeNode extends Node { */ public Node mergeScopes(ScopeNode that) { RegionNode r = (RegionNode) ctrl(new RegionNode(null,ctrl(), that.ctrl()).keep()); - String[] ns = reverseNames(); - // Note that we skip i==0, which is bound to '$ctrl' - for (int i = 1; i < nIns(); i++) - if( in(i) != that.in(i) ) // No need for redundant Phis - // If we are in lazy phi mode we need to a lookup - // by name as it will trigger a phi creation - setDef(i, new PhiNode(ns[i], this.lookupDeclaredType(ns[i]), r, this.lookup(ns[i]), that.lookup(ns[i])).peephole()); + mem()._merge(that.mem(),r); + this ._merge(that ,r); that.kill(); // Kill merged scope IterPeeps.add(r); return r.unkeep().peephole(); } - // Merge the backedge scope into this loop head scope + private void _merge(ScopeNode that, RegionNode r) { + for( int i = 2; i < nIns(); i++) + if( in(i) != that.in(i) ) { // No need for redundant Phis + // If we are in lazy phi mode we need to a lookup + // by name as it will trigger a phi creation + Var v = _vars.at(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()); + } + } + + // 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 ) { Node ctrl = ctrl(); assert ctrl instanceof LoopNode loop && loop.inProgress(); ctrl.setDef(2,back.ctrl()); - for( int i=1; i<nIns(); i++ ) { - if( back.in(i) != this ) { - PhiNode phi = (PhiNode)in(i); - assert phi.region()==ctrl && phi.in(2)==null; - phi.setDef(2,back.in(i)); - } - if( exit.in(i) == this ) // Replace a lazy-phi on the exit path also - exit.setDef(i,in(i)); - } + + mem()._endLoopMem( this, back.mem(), exit.mem() ); + this ._endLoop ( this, back , exit ); back.kill(); // Loop backedge is dead // Now one-time do a useless-phi removal - for( int i=1; i<nIns(); i++ ) { - if( in(i) instanceof PhiNode phi ) { - // Do an eager useless-phi removal - Node in = phi.peephole(); - IterPeeps.addAll(phi._outputs); - phi.moveDepsToWorklist(); - if( in != phi ) { - if( !phi.iskeep() ) // Keeping phi around for parser elsewhere - phi.subsume(in); - setDef(i,in); // Set the update back into Scope - } + 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)); + } + + // Fill in the backedge of any inserted Phis + void _endLoop( ScopeNode scope, Node back, Node exit ) { + for( int i=2; i<nIns(); i++ ) { + if( back.in(i) != scope ) { + PhiNode phi = (PhiNode)in(i); + assert phi.region()==scope.ctrl() && phi.in(2)==null; + phi.setDef(2,back.in(i)); // Fill backedge } + if( exit.in(i) == scope ) // Replace a lazy-phi on the exit path also + exit.setDef(i,in(i)); } - } @@ -251,13 +268,12 @@ public class ScopeNode extends Node { // This Scope looks for direct variable uses, or certain simple // combinations, and replaces the variable with the upcast variant. public Node upcast( Node ctrl, Node pred, boolean invert ) { - if( ctrl._type==Type.XCONTROL ) return null; // Invert the If conditional if( invert ) pred = pred instanceof NotNode not ? not.in(1) : IterPeeps.add(new NotNode(pred).peephole()); // Direct use of a value as predicate. This is a zero/null test. - if( Utils.find(_inputs, pred) != -1 ) { + if( _inputs.find(pred) != -1 ) { if( !(pred._type instanceof TypeMemPtr tmp) ) // Must be an `int`, since int and ptr are the only two value types // being tested. No representation for a generic not-null int, so no upcast. @@ -270,7 +286,7 @@ public class ScopeNode extends Node { if( pred instanceof NotNode not ) { // Direct use of a !value as predicate. This is a zero/null test. - if( Utils.find(_inputs, not.in(1)) != -1 ) { + if( _inputs.find(not.in(1)) != -1 ) { Type tinit = not.in(1)._type.makeInit(); if( not.in(1)._type.isa(tinit) ) return null; // Already zero/null, no reason to upcast return replace(not.in(1), new ConstantNode(tinit).peephole()); diff --git a/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/src/main/java/com/seaofnodes/simple/node/ShlNode.java index 28e6dd14..b67d985a 100644 --- a/src/main/java/com/seaofnodes/simple/node/ShlNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ShlNode.java @@ -21,8 +21,13 @@ public class ShlNode 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()); } @@ -39,7 +44,7 @@ public class ShlNode extends Node { if( (shl.value()&63)==0 ) return lhs; // (x + c) << 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 16d78ebf..22eb45a0 100644 --- a/src/main/java/com/seaofnodes/simple/node/ShrNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ShrNode.java @@ -21,8 +21,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 5964e19f..c03a4b06 100644 --- a/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ b/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -5,6 +5,7 @@ import com.seaofnodes.simple.type.*; import java.util.Arrays; import java.util.BitSet; import java.util.HashSet; +import static com.seaofnodes.simple.Utils.TODO; /** * The Start node represents the start of the function. @@ -15,41 +16,9 @@ import java.util.HashSet; */ public class StartNode extends CFGNode implements MultiNode { - // This field is NOT final, because the tuple expands as we add memory aliases - TypeTuple _args; + final Type _arg; - public StartNode(Type[] args) { - super(); - _args = TypeTuple.make(args); - _type = _args; - } - - /** - * Creates a projection for each of the struct's fields, using the field alias - * as the key. - */ - public void addMemProj( TypeStruct ts, ScopeNode scope) { - if( ts._fields.length==0 ) return; - // Expand args type for more memory projections - Type[] args = _args._types; - int max = args.length; - for( Field f : ts._fields ) - max = Math.max(max,f._alias); - args = Arrays.copyOf(args, max+1); - // For each of the fields we now add a mem projection. - for( Field f : ts._fields ) { - TypeMem tm_decl = TypeMem.make(f._alias,f._type.glb()); - args[f._alias] = tm_decl.dual(); - String name = Parser.memName(f._alias); - Node n = new ProjNode(this, f._alias, name); // No 'compute' until ScopeNode gets typed - n._type = args[f._alias]; - scope.define(name, tm_decl, n); - } - for (int i = 0; i < args.length; i++) - if (args[i] == null) - args[i] = Type.TOP; // We parsed a nested struct/array. Avoid nulls until a caller replaces this alias. - _type = _args = TypeTuple.make(args); - } + public StartNode(Type arg) { super(); _arg = arg; _type = compute(); } @Override public String label() { return "Start"; } @@ -62,11 +31,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.TOP,_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 b1c74b94..7b1bddd5 100644 --- a/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -55,27 +55,52 @@ public class StoreNode extends MemOpNode { // required init-store being stomped by a first user store. if( mem() instanceof StoreNode st && ptr()==st.ptr() && // Must check same object - off()==st.off() && // And same offset + off()==st.off() && // And same offset (could be "same alias" but this handles arrays to same index) 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..db170ab1 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/StructNode.java @@ -0,0 +1,45 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.*; +import java.util.BitSet; + +/** + * Build a compound object + */ +public class StructNode extends Node { + + public TypeStruct _ts; + + @Override public String label() { return _ts==null ? "STRUCT?" : _ts.str(); } + + @Override + StringBuilder _print1(StringBuilder sb, BitSet visited) { + if( _ts==null ) return sb.append("STRUCT?"); + sb.append(_ts._name).append(" {"); + for( int i=0; i<nIns(); i++ ) { + sb.append(_ts._fields[i]._fname).append(":"); + (in(i)==null ? Type.BOTTOM : in(i)._type).print(sb); + sb.append("; "); + } + sb.setLength(sb.length()-2); + return sb.append("}"); + } + + @Override + public TypeStruct compute() { + if( _ts==null ) return TypeStruct.BOT; + Field[] fs = new Field[_ts._fields.length]; + for( int i=0; i<fs.length; i++ ) + fs[i] = _ts._fields[i].makeFrom(in(i)==null ? Type.TOP : in(i)._type); + return TypeStruct.make(_ts._name,fs); + } + + @Override + public Node idealize() { return null; } + + @Override + boolean eq(Node n) { return _ts == ((StructNode)n)._ts; } + + @Override + int hash() { return _ts==null ? 0 : _ts.hashCode(); } +} diff --git a/src/main/java/com/seaofnodes/simple/node/XorNode.java b/src/main/java/com/seaofnodes/simple/node/XorNode.java index 30a88dca..191933f9 100644 --- a/src/main/java/com/seaofnodes/simple/node/XorNode.java +++ b/src/main/java/com/seaofnodes/simple/node/XorNode.java @@ -21,8 +21,11 @@ public class XorNode 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/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<Type> 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 8b484235..ec059b8f 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 abfb8a67..4e4d1305 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<Type> 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; @@ -150,10 +160,10 @@ public class TypeStruct extends Type { public StringBuilder print(StringBuilder sb) { 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<Type> 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<Type> 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]); diff --git a/src/test/java/com/seaofnodes/simple/Chapter05Test.java b/src/test/java/com/seaofnodes/simple/Chapter05Test.java index 9d154413..b5920125 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter05Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter05Test.java @@ -22,7 +22,7 @@ else { return a;"""); StopNode ret = parser.parse().iterate(); - assertEquals("return Phi(Region20,(arg+2),(arg-3));", ret.toString()); + assertEquals("return Phi(Region23,(arg+2),(arg-3));", ret.toString()); } @Test @@ -37,7 +37,7 @@ if (arg == 1) { } return c;""", TypeInteger.BOT); StopNode ret = parser.parse().iterate(); - assertEquals("return Phi(Region19,4,3);", ret.toString()); + assertEquals("return Phi(Region22,4,3);", ret.toString()); } @Test @@ -65,7 +65,7 @@ else b=a+1; return a+b;"""); StopNode ret = parser.parse().iterate(); - assertEquals("return ((arg*2)+Phi(Region23,2,3));", ret.toString()); + assertEquals("return ((arg*2)+Phi(Region26,2,3));", ret.toString()); } @Test @@ -80,7 +80,7 @@ else a=b+1; return a+b;"""); StopNode ret = parser.parse().iterate(); - assertEquals("return ((Phi(Region34,(arg*2),arg)+arg)+Phi(Region,4,5));", ret.toString()); + assertEquals("return ((Phi(Region37,(arg*2),arg)+arg)+Phi(Region,4,5));", ret.toString()); } @Test @@ -100,7 +100,7 @@ else return a; """, TypeInteger.BOT); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region38,Phi(Region24,2,3),Phi(Region36,4,5));", stop.toString()); + assertEquals("return Phi(Region43,Phi(Region28,2,3),Phi(Region41,4,5));", stop.toString()); } @Test @@ -116,7 +116,7 @@ if( arg==0 ) return arg+a+b; """, TypeInteger.BOT); StopNode stop = parser.parse(); - assertEquals("return ((arg+Phi(Region17,1,0))+Phi(Region33,2,0));", stop.toString()); + assertEquals("return ((arg+Phi(Region20,1,0))+Phi(Region37,2,0));", stop.toString()); } @Test @@ -130,7 +130,7 @@ if( arg==1 ) } return a;"""); StopNode ret = parser.parse().iterate(); - assertEquals("return (arg==Phi(Region19,3,2));", ret.toString()); + assertEquals("return (arg==Phi(Region22,3,2));", ret.toString()); } @Test diff --git a/src/test/java/com/seaofnodes/simple/Chapter06Test.java b/src/test/java/com/seaofnodes/simple/Chapter06Test.java index ae37a867..ff1f3251 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter06Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter06Test.java @@ -65,7 +65,7 @@ else b=5; return b;""", TypeInteger.BOT); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region39,42,5);", stop.toString()); + assertEquals("return Phi(Region43,42,5);", stop.toString()); } @Test @@ -84,7 +84,7 @@ else b=5; return b;""", TypeInteger.BOT); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region32,2,5);", stop.toString()); + assertEquals("return Phi(Region38,2,5);", stop.toString()); } @Test @@ -144,7 +144,7 @@ else return a; """, TypeInteger.BOT); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region43,3,Phi(Region41,4,5));", stop.toString()); + assertEquals("return Phi(Region48,3,Phi(Region46,4,5));", stop.toString()); } @Test @@ -200,7 +200,7 @@ if( arg ) { return a+b; """); StopNode ret = parser.parse().iterate(); - assertEquals("return Phi(Region28,4,1);", ret.toString()); + assertEquals("return Phi(Region35,4,1);", ret.toString()); } @@ -251,7 +251,7 @@ if( arg ) { return a+b+c; """); StopNode ret = parser.parse().iterate(); - assertEquals("return (Phi(Region40,Phi(Region25,2,3),0)+Phi(Region,3,1));", ret.toString()); + assertEquals("return (Phi(Region48,Phi(Region29,2,3),0)+Phi(Region,3,1));", ret.toString()); } diff --git a/src/test/java/com/seaofnodes/simple/Chapter07Test.java b/src/test/java/com/seaofnodes/simple/Chapter07Test.java index e6e0560b..b54c24a3 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter07Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter07Test.java @@ -18,7 +18,7 @@ public class Chapter07Test { """); Node._disablePeephole = true; StopNode stop = parser.parse(); - assertEquals("return Phi(Loop8,arg,(Phi_arg+1));", stop.toString()); + assertEquals("return Phi(Loop10,arg,(Phi_arg+1));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); Node._disablePeephole = false; } @@ -36,7 +36,7 @@ if(arg){}else{ return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region26,1,Phi(Loop15,1,(Phi_a+1)));", stop.toString()); + assertEquals("return Phi(Region31,1,Phi(Loop18,1,(Phi_a+1)));", stop.toString()); } @Test @@ -56,7 +56,7 @@ while(i < arg) { return sum; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,0,Phi(Loop22,Phi_sum,(Phi_sum+Phi(Loop,0,(Phi_j+1)))));", stop.toString()); + assertEquals("return Phi(Loop12,0,Phi(Loop26,Phi_sum,(Phi_sum+Phi(Loop,0,(Phi_j+1)))));", stop.toString()); } @Test @@ -73,7 +73,7 @@ return b; """); Node._disablePeephole = true; StopNode stop = parser.parse(); - assertEquals("return Phi(Loop10,2,Phi(Region29,Phi_b,4));", stop.toString()); + assertEquals("return Phi(Loop12,2,Phi(Region34,Phi_b,4));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); Node._disablePeephole = false; } @@ -93,7 +93,7 @@ while(a < 10) { return b; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,2,(Phi(Region29,Phi_b,4)+1));", stop.toString()); + assertEquals("return Phi(Loop12,2,(Phi(Region34,Phi_b,4)+1));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -111,7 +111,7 @@ return a; """); Node._disablePeephole = true; StopNode stop = parser.parse(); - assertEquals("return Phi(Loop9,1,((Phi_a+1)+2));", stop.toString()); + assertEquals("return Phi(Loop11,1,((Phi_a+1)+2));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); Node._disablePeephole = false; } @@ -128,7 +128,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop11,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -142,7 +142,7 @@ return a; """); Node._disablePeephole = true; StopNode stop = parser.parse(); - assertEquals("return Phi(Loop9,1,2);", stop.toString()); + assertEquals("return Phi(Loop11,1,2);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); Node._disablePeephole = false; } @@ -156,7 +156,7 @@ while(arg) a = 2; return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,1,2);", stop.toString()); + assertEquals("return Phi(Loop11,1,2);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -173,7 +173,7 @@ return a; """); Node._disablePeephole = true; StopNode stop = parser.parse(); - assertEquals("return Phi(Loop9,1,((Phi_a+1)+2));", stop.toString()); + assertEquals("return Phi(Loop11,1,((Phi_a+1)+2));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); Node._disablePeephole = false; } @@ -190,7 +190,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop11,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -208,7 +208,7 @@ return a; """); Node._disablePeephole = true; StopNode stop = parser.parse(); - assertEquals("return Phi(Loop10,1,((Phi_a+1)+2));", stop.toString()); + assertEquals("return Phi(Loop12,1,((Phi_a+1)+2));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); Node._disablePeephole = false; } @@ -226,7 +226,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop12,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } diff --git a/src/test/java/com/seaofnodes/simple/Chapter08Test.java b/src/test/java/com/seaofnodes/simple/Chapter08Test.java index 7760ae09..c199b78c 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter08Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter08Test.java @@ -23,7 +23,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region37,Phi(Region26,Phi(Loop8,arg,(Phi_arg+1)),Add),Add);", stop.toString()); + assertEquals("return Phi(Region45,Phi(Region32,Phi(Loop10,arg,(Phi_arg+1)),Add),Add);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof RegionNode); Assert.assertEquals(5L, Evaluator.evaluate(stop, 1)); Assert.assertEquals(10L, Evaluator.evaluate(stop, 6)); @@ -46,7 +46,7 @@ while(arg < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,1,Phi(Region43,Phi_a,(Phi_a+1)));", stop.toString()); + assertEquals("return Phi(Loop11,1,Phi(Region52,Phi_a,(Phi_a+1)));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -65,7 +65,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region35,Phi(Loop8,arg,(Phi_arg+1)),Add);", stop.toString()); + assertEquals("return Phi(Region43,Phi(Loop10,arg,(Phi_arg+1)),Add);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof RegionNode); } @@ -81,7 +81,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region26,Phi(Loop8,arg,(Phi_arg+1)),Add);", stop.toString()); + assertEquals("return Phi(Region32,Phi(Loop10,arg,(Phi_arg+1)),Add);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof RegionNode); } @@ -100,7 +100,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop8,arg,(Phi_arg+1));", stop.toString()); + assertEquals("return Phi(Loop10,arg,(Phi_arg+1));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -117,7 +117,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop8,arg,(Phi_arg+1));", stop.toString()); + assertEquals("return Phi(Loop10,arg,(Phi_arg+1));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -185,7 +185,7 @@ while(arg < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region29,Phi(Loop9,1,(Phi_a+1)),Add);", stop.toString()); + assertEquals("return Phi(Region35,Phi(Loop11,1,(Phi_a+1)),Add);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof RegionNode); } @@ -201,7 +201,7 @@ while(1) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return (Phi(Loop9,1,Add)+1);", stop.toString()); + assertEquals("return (Phi(Loop11,1,Add)+1);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode prj && prj._idx==1 ); } diff --git a/src/test/java/com/seaofnodes/simple/Chapter09Test.java b/src/test/java/com/seaofnodes/simple/Chapter09Test.java index 64bbf245..b1d9d6e5 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter09Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter09Test.java @@ -69,7 +69,7 @@ while (arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,arg,(Phi_arg+2));", stop.toString()); + assertEquals("return Phi(Loop11,arg,(Phi_arg+2));", stop.toString()); Assert.assertEquals(11L, Evaluator.evaluate(stop, 1)); } @@ -86,7 +86,7 @@ while (arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,arg,(Phi_arg+4));", stop.toString()); + assertEquals("return Phi(Loop12,arg,(Phi_arg+4));", stop.toString()); Assert.assertEquals(13L, Evaluator.evaluate(stop, 1)); } @@ -115,7 +115,7 @@ while (arg) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop16,arg,(Phi_arg+1));", stop.toString()); + assertEquals("return Phi(Loop18,arg,(Phi_arg+1));", stop.toString()); } @Test @@ -185,7 +185,7 @@ while(arg) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop11,0,(-(Phi_a+3)));", stop.toString()); + assertEquals("return Phi(Loop13,0,(-(Phi_a+3)));", stop.toString()); } @Test @@ -283,7 +283,7 @@ return v1+v0; public void testFuzz8() { Parser parser = new Parser("while(arg) arg = arg - 1; return arg;"); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop8,arg,(Phi_arg-1));", stop.toString()); + assertEquals("return Phi(Loop10,arg,(Phi_arg-1));", stop.toString()); } @Test diff --git a/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/src/test/java/com/seaofnodes/simple/Chapter10Test.java index def9aaa6..2a33031d 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter10Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter10Test.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.evaluator.Evaluator; import com.seaofnodes.simple.node.StopNode; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -38,10 +39,10 @@ return p-r; struct Bar { int a; int b; -} +}; struct Foo { int x; -} +}; Foo? foo = null; Bar bar = new Bar; bar.a = 1; @@ -55,7 +56,7 @@ return bar.a; @Test public void testExample() { Parser parser = new Parser(""" -struct Vector2D { int x; int y; } +struct Vector2D { int x; int y; }; Vector2D v = new Vector2D; v.x = 1; if (arg) @@ -73,7 +74,7 @@ return v; Parser parser = new Parser(""" struct s0 { int v0; -} +}; s0? v1=null; int v3=v1.zAicm; """); @@ -84,7 +85,7 @@ int v3=v1.zAicm; @Test public void testBug2() { Parser parser = new Parser(""" -struct s0 { int v0; } +struct s0 { int v0; }; arg=0+new s0.0; """); try { parser.parse(); fail(); } @@ -94,7 +95,7 @@ arg=0+new s0.0; @Test public void testLoop() { Parser parser = new Parser(""" -struct Bar { int a; } +struct Bar { int a; }; Bar bar = new Bar; while (arg) { bar.a = bar.a + 2; @@ -102,14 +103,14 @@ while (arg) { } return bar.a; """); - StopNode stop = parser.parse(false).iterate(); - assertEquals("return Phi(Loop13,0,(Phi_a+2));", stop.toString()); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Loop16,0,(Phi_a+2));", stop.toString()); } @Test public void testIf() { Parser parser = new Parser(""" -struct Bar { int a; } +struct Bar { int a; }; Bar bar = new Bar; if (arg) bar = null; bar.a = 1; @@ -122,7 +123,7 @@ return bar.a; @Test public void testIf2() { Parser parser = new Parser(""" -struct Bar { int a; } +struct Bar { int a; }; Bar? bar = null; if (arg) bar = new Bar; bar.a = 1; @@ -135,7 +136,7 @@ return bar.a; @Test public void testIf3() { Parser parser = new Parser(""" -struct Bar { int a; } +struct Bar { int a; }; Bar bar = null; if (arg) bar = null; bar.a = 1; @@ -148,21 +149,21 @@ return bar.a; @Test public void testIfOrNull() { Parser parser = new Parser(""" -struct Bar { int a; } +struct Bar { int a; }; Bar? bar = new Bar; if (arg) bar = null; if( bar ) bar.a = 1; return bar; """); StopNode stop = parser.parse(false).iterate(); - assertEquals("return Phi(Region33,(*void)Phi(Region20,null,Bar),null);", stop.toString()); + assertEquals("return Phi(Region38,(*void)Phi(Region24,null,Bar),null);", stop.toString()); } @Test public void testIfOrNull2() { Parser parser = new Parser( """ -struct Bar { int a; } +struct Bar { int a; }; Bar? bar = new Bar; if (arg) bar = null; int rez = 3; @@ -171,13 +172,13 @@ else bar.a = 1; return rez; """); StopNode stop = parser.parse(false).iterate(); - assertEquals("return Phi(Region39,4,3);", stop.toString()); + assertEquals("return Phi(Region44,4,3);", stop.toString()); } @Test public void testWhileWithNullInside() { Parser parser = new Parser(""" -struct s0 {int v0;} +struct s0 {int v0;}; s0? v0 = new s0; int ret = 0; while(arg) { @@ -197,7 +198,7 @@ return ret; Parser parser = new Parser(""" struct s0 { int v0; -} +}; s0? v1=new s0; s0? v1; v1=new s0; @@ -214,7 +215,7 @@ v1=new s0; struct Iter { int x; int len; -} +}; Iter i = new Iter; i.len = arg; int sum=0; @@ -225,16 +226,14 @@ while( i.x < i.len ) { return sum; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop18,0,(Phi(Loop,0,(Phi_x+1))+Phi_sum));", stop.toString()); + assertEquals("return Phi(Loop21,0,(Phi(Loop,0,(Phi_x+1))+Phi_sum));", stop.toString()); } - - @Test public void test1() { Parser parser = new Parser(""" -struct s0 {int v0;} +struct s0 {int v0;}; s0 ret = new s0; while(arg) { s0 v0 = new s0; @@ -246,13 +245,13 @@ while(arg) { return ret; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop13,s0,Phi(Region37,s0,Phi_ret));", stop.toString()); + assertEquals("return Phi(Loop16,s0,Phi(Region43,s0,Phi_ret));", stop.toString()); } @Test public void test2() { Parser parser = new Parser(""" -struct s0 {int v0;} +struct s0 {int v0;}; s0 ret = new s0; s0 v0 = new s0; while(arg) { @@ -264,14 +263,14 @@ while(arg) { return ret; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop17,s0,Phi(Region38,s0,Phi_ret));", stop.toString()); + assertEquals("return Phi(Loop20,s0,Phi(Region44,s0,Phi_ret));", stop.toString()); } @Test public void test3() { Parser parser = new Parser(""" -struct s0 {int v0;} +struct s0 {int v0;}; s0 ret = new s0; while(arg < 10) { s0 v0 = new s0; @@ -281,21 +280,19 @@ while(arg < 10) { return ret; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop13,s0,Phi(Region35,s0,Phi_ret));", stop.toString()); + assertEquals("return Phi(Loop16,s0,Phi(Region41,s0,Phi_ret));", stop.toString()); } @Test public void testBug3() { Parser parser = new Parser(""" -struct s0 { - int f0; -} -if(0>=0) return new s0; +struct s0 { int f0; }; return new s0; int v0=null.f0; """); - try { parser.parse(); fail(); } - catch( Exception e ) { assertEquals("Accessing unknown field 'f0' from 'null'", e.getMessage()); } + StopNode stop = parser.parse().iterate(); + assertEquals("return s0;", stop.toString()); + assertEquals("Obj<s0>{f0=0}", Evaluator.evaluate(stop, 0).toString()); } @Test @@ -319,7 +316,7 @@ if(0) { Parser parser = new Parser(""" struct s0 { int f0; -} +}; if(0) return 0; else return new s0; if(new s0.f0) return 0; @@ -346,7 +343,7 @@ return 0; @Test public void testBug7() { Parser parser = new Parser(""" -struct s0 { int f0; } +struct s0 { int f0; }; s0 v0 = new s0; while(v0.f0) {} s0 v1 = v0; diff --git a/src/test/java/com/seaofnodes/simple/Chapter11Test.java b/src/test/java/com/seaofnodes/simple/Chapter11Test.java index 5f9f9cac..d6b7bb00 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter11Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter11Test.java @@ -292,7 +292,7 @@ else { } """); StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ return 9; return 0; return 0; return 0; ]", stop.toString()); + assertEquals("Stop[ return 9; return 0; return 0; ]", stop.toString()); } @@ -324,7 +324,7 @@ while( prime <= arg ) { return primeCount; """); StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ return 0; return Phi(Loop20,1,Phi(Region87,Phi_primeCount,Phi(Region81,(Phi_primeCount+1),Phi_primeCount))); ]", stop.toString()); + assertEquals("Stop[ return 0; return Phi(Loop23,1,Phi(Region100,Phi_primeCount,Phi(Region93,(Phi_primeCount+1),Phi_primeCount))); ]", stop.toString()); assertEquals(0L, Evaluator.evaluate(stop, 1)); // No primes 1 or below assertEquals(1L, Evaluator.evaluate(stop, 2)); // 2 assertEquals(2L, Evaluator.evaluate(stop, 3)); // 2, 3 @@ -337,7 +337,7 @@ return primeCount; public void testAntiDeps1() { Parser parser = new Parser( """ -struct S { int f; } +struct S { int f; }; S v=new S; v.f = 2; int i=new S.f; @@ -355,7 +355,7 @@ return i; public void testAntiDeps2() { Parser parser = new Parser( """ -struct S { int f; } +struct S { int f; }; S v = new S; v.f = arg; S t = new S; @@ -369,14 +369,14 @@ if (arg) { return i; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region40,.f,0);", stop.toString()); + assertEquals("return Phi(Region45,.f,0);", stop.toString()); } @Test public void testAntiDeps3() { Parser parser = new Parser( """ -struct S { int f; } +struct S { int f; }; S v0 = new S; S? v1; if (arg) v1 = new S; @@ -396,7 +396,7 @@ return v0; public void testAntiDeps4() { Parser parser = new Parser( """ -struct S { int f; } +struct S { int f; }; S v = new S; v.f = arg; S t = new S; @@ -413,7 +413,7 @@ return i; public void testAntiDeps5() { Parser parser = new Parser( """ -struct S { int f; } +struct S { int f; }; S v = new S; while(1) { while(arg+1) { arg=arg-1; } @@ -430,7 +430,7 @@ return v; public void testAntiDeps6() { Parser parser = new Parser( """ -struct s { int v; } +struct s { int v; }; s ptr=new s; while( -arg ) ptr = new s; @@ -445,7 +445,7 @@ while(1) public void testAntiDeps7() { Parser parser = new Parser( """ -struct S { int f; } +struct S { int f; }; S v = new S; S t = new S; int i = v.f; @@ -463,7 +463,7 @@ return i; public void testAntiDeps8() { Parser parser = new Parser( """ -struct S { int f; } +struct S { int f; }; S v = new S; S t = new S; while(arg) { @@ -476,14 +476,14 @@ while(arg) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop17,arg,Phi(Region42,.f,0));", stop.toString()); + assertEquals("return Phi(Loop20,arg,Phi(Region48,.f,0));", stop.toString()); } @Test public void testAntiDeps9() { Parser parser = new Parser( """ -struct S { int f; } +struct S { int f; }; S v = new S; S t = new S; if (arg) { @@ -500,8 +500,8 @@ return v; @Test public void testExample2() { Parser parser = new Parser( - """ -struct S { int f; } +""" +struct S { int f; }; S v = new S; int i = arg; while (arg > 0) { @@ -515,4 +515,39 @@ return v; StopNode stop = parser.parse().iterate(); //assertEquals("return new S;", stop.toString()); } + + @Test + public void testScheduleUse() { + Parser parser = new Parser( +""" +int v0=0; +while(0>=0) { + u1 v1=0; + v1=v0; + if(v1*0) + v0=-v1; +} +return 0; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return 0;", stop.toString()); + } + + @Test + public void testLoopCarriedDep() { + Parser parser = new Parser( +""" +u32 v0=0; +{ + int v1=0; + while(v1) { + v1=1>>>v0!=0; + v0=v1/0; + } +} +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return 0;", stop.toString()); + } + } diff --git a/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/src/test/java/com/seaofnodes/simple/Chapter12Test.java index 2a637ce6..c0f6266a 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter12Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter12Test.java @@ -43,7 +43,7 @@ while( 1 ) { return guess; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,(flt)arg,(((ToFloat/Phi_guess)+Phi_guess)/2.0));", stop.toString()); + assertEquals("return Phi(Loop11,(flt)arg,(((ToFloat/Phi_guess)+Phi_guess)/2.0));", stop.toString()); assertEquals(3.0, Evaluator.evaluate(stop, 9)); assertEquals(1.414213562373095, Evaluator.evaluate(stop, 2)); } diff --git a/src/test/java/com/seaofnodes/simple/Chapter13Test.java b/src/test/java/com/seaofnodes/simple/Chapter13Test.java index 416aba31..165d23e5 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter13Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter13Test.java @@ -2,7 +2,6 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.evaluator.Evaluator; import com.seaofnodes.simple.node.StopNode; -import org.junit.Ignore; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -23,7 +22,7 @@ return 3.14; public void testLinkedList0() { Parser parser = new Parser( """ -struct LLI { LLI? next; int i; } +struct LLI { LLI? next; int i; }; LLI? head = null; while( arg ) { LLI x = new LLI; @@ -35,14 +34,14 @@ while( arg ) { return head.next.i; """); try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Might be null accessing 'next'",e.getMessage()); } + catch( Exception e ) { assertEquals("Might be null accessing 'i'",e.getMessage()); } } @Test public void testLinkedList1() { Parser parser = new Parser( """ -struct LLI { LLI? next; int i; } +struct LLI { LLI? next; int i; }; LLI? head = null; while( arg ) { LLI x = new LLI; @@ -65,8 +64,8 @@ return next.i; public void testCoRecur() { Parser parser = new Parser( """ -struct int0 { int i; flt0? f; } -struct flt0 { flt f; int0? i; } +struct int0 { int i; flt0? f; }; +struct flt0 { flt f; int0? i; }; int0 i0 = new int0; i0.i = 17; flt0 f0 = new flt0; @@ -83,7 +82,7 @@ return f0.i.f.i.i; public void testNullRef0() { Parser parser = new Parser( """ -struct N { N next; int i; } +struct N { N? next; int i; }; N n = new N; return n.next; """); @@ -95,33 +94,33 @@ return n.next; public void testNullRef1() { Parser parser = new Parser( """ -struct N { N next; int i; } -N n = new N; -n.next = new N; +struct M { int m; }; +struct N { M next; int i; }; +N n = new N { next = new M; }; return n.next; """); StopNode stop = parser.parse().iterate(); - assertEquals("return N;", stop.toString()); + assertEquals("return M;", stop.toString()); } @Test public void testNullRef2() { Parser parser = new Parser( """ -struct N { N next; int i; } -N n = new N; -n.next = null; +struct M { int m; }; +struct N { M next; int i; }; +N n = new N { next = null; } return n.next; """); try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Cannot store null into field *N next",e.getMessage()); } + catch( Exception e ) { assertEquals("Type null is not of declared type *M",e.getMessage()); } } @Test public void testNullRef3() { Parser parser = new Parser( """ -struct N { N next; int i; } +struct N { N? next; int i; }; N n = new N; n.i = 3.14; return n.i; @@ -145,7 +144,7 @@ struct S{}; public void testForwardRef0() { Parser parser = new Parser( """ -struct S1 { S2 s; } +struct S1 { S2 s; }; return new S2; """); try { parser.parse().iterate(); fail(); } @@ -156,8 +155,8 @@ return new S2; public void testForwardRef1() { Parser parser = new Parser( """ -struct S1 { S2? s; } -struct S2 { int x; } +struct S1 { S2? s; }; +struct S2 { int x; }; return new S1.s=new S2; """); StopNode stop = parser.parse().iterate(); @@ -168,8 +167,8 @@ return new S1.s=new S2; public void testcheckNull() { Parser parser = new Parser( """ -struct I {int i;} -struct P {I pi;} +struct I {int i;}; +struct P { I? pi; }; P p1 = new P; P p2 = new P; p2.pi = new I; diff --git a/src/test/java/com/seaofnodes/simple/Chapter14Test.java b/src/test/java/com/seaofnodes/simple/Chapter14Test.java index 048cb93a..7d7bb25a 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter14Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter14Test.java @@ -59,7 +59,7 @@ while( b ) b = b + 456;// Truncate return b; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,123,((Phi_b+456)&255));", stop.toString()); + assertEquals("return Phi(Loop11,123,((Phi_b+456)&255));", stop.toString()); } @Test @@ -93,7 +93,7 @@ return b; public void testRefLoad() { Parser parser = new Parser( """ -struct Foo { u1 b; } +struct Foo { u1 b; }; Foo f = new Foo; f.b = 123; return f.b; @@ -166,6 +166,39 @@ return arg; } @Test + public void testCloneAnd() { + Parser parser = new Parser(""" +int v0=0; +u32 v1 = 1&(1<<arg)&(1<<arg); +while(arg) v1=-v0; +while(v1) break; +return v1; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return (Phi(Loop18,((1<<arg)&1),0)&Phi(Loop,Shl,4294967295));", stop.toString()); + assertEquals(1L, Evaluator.evaluate(stop, 0)); + } + + @Test + public void testAndHigh() { + Parser parser = new Parser(""" +int v0=0; +if(0&0>>>0) { + while(0) { + u8 v1=0; + v0=0>>>0; + v1=arg; + while(v1+0) {} + } +} +return v0; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return 0;", stop.toString()); + assertEquals(0L, Evaluator.evaluate(stop, 0)); + } + + @Test public void testTypes() { Parser parser = new Parser( """ diff --git a/src/test/java/com/seaofnodes/simple/Chapter15Test.java b/src/test/java/com/seaofnodes/simple/Chapter15Test.java index 6ec7cc1a..d2d60b34 100644 --- a/src/test/java/com/seaofnodes/simple/Chapter15Test.java +++ b/src/test/java/com/seaofnodes/simple/Chapter15Test.java @@ -4,9 +4,6 @@ import com.seaofnodes.simple.evaluator.Evaluator; import com.seaofnodes.simple.node.StopNode; import org.junit.Test; import org.junit.Ignore; - -import java.nio.charset.StandardCharsets; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -14,7 +11,8 @@ public class Chapter15Test { @Test public void testJig() { - Parser parser = new Parser(""" + Parser parser = new Parser( +""" return 3.14; """); StopNode stop = parser.parse().iterate(); @@ -23,29 +21,10 @@ return 3.14; } @Test - public void testLifetime() { - Parser parser = new Parser(""" -u8[] b = new u8[5]; -if (arg) { - b[0] = b[0] + 1; -} else { - b[1] = b[1] * 2; -} -return b[0] + b[1]; -"""); - StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region69,1,0);", stop.toString()); - assertEquals(0L, Evaluator.evaluate(stop, 0)); - assertEquals(1L, Evaluator.evaluate(stop, 1)); - } - - @Test public void testCyclic() { Parser parser = new Parser( """ -struct C { - C? l; -} +struct C { C? l; }; C c = new C; c.l = c; return c; @@ -70,8 +49,8 @@ output[i] = 1; return output; """); StopNode stop = parser.parse().iterate(); - assertEquals("return u8[];", stop.toString()); - assertEquals("\u0001", Evaluator.evaluate(stop, 0).toString()); + assertEquals("return [u8];", stop.toString()); + assertEquals("Obj<[u8]>{#=1,[]=[1]}", Evaluator.evaluate(stop, 0).toString()); } @Test @@ -117,20 +96,20 @@ return a[0]; public void testBasic4() { Parser parser = new Parser( """ -struct A { int i; } +struct A { int i; }; A?[] a = new A?[2]; return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return *A?[];", stop.toString()); - assertEquals("Obj<*A?[]>{#=2,[]=[null,null]}", Evaluator.evaluate(stop, 0).toString()); + assertEquals("return [*A?];", stop.toString()); + assertEquals("Obj<[*A?]>{#=2,[]=[null,null]}", Evaluator.evaluate(stop, 0).toString()); } @Test public void testBasic5() { Parser parser = new Parser( """ -struct S { int x; flt y; } +struct S { int x; flt y; }; // A new S S s = new S; s.x=99; s.y = 3.14; @@ -151,8 +130,9 @@ else { return rez; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region118,1.2,Phi(Region115,2.3,.y));", stop.toString()); + assertEquals("return Phi(Region117,1.2,Phi(Region114,2.3,3.14));", stop.toString()); assertEquals(3.14, Evaluator.evaluate(stop, 0)); + assertEquals(1.2 , Evaluator.evaluate(stop, 1)); } @Ignore @@ -160,7 +140,7 @@ return rez; public void testBasic6() { Parser parser = new Parser( """ -struct S { int x; flt y; } +struct S { int x; flt y; }; // A new S S s = new S; s.x=99; s.y = 3.14; @@ -186,7 +166,7 @@ return rez; Parser parser = new Parser( """ // Can we define a forward-reference array? -struct Tree { Tree?[] _kids; } +struct Tree { Tree?[]? _kids; }; Tree root = new Tree; root._kids = new Tree?[2]; root._kids[0] = new Tree; @@ -194,17 +174,14 @@ return root; """); StopNode stop = parser.parse().iterate(); assertEquals("return Tree;", stop.toString()); - assertEquals("Obj<Tree>{_kids=Obj<*Tree?[]>{#=2,[]=[Obj<Tree>{_kids=null},null]}}", Evaluator.evaluate(stop, 0).toString()); + assertEquals("Obj<Tree>{_kids=Obj<[*Tree?]>{#=2,[]=[Obj<Tree>{_kids=null},null]}}", Evaluator.evaluate(stop, 0).toString()); } @Test public void testNestedStructAddMemProj() { Parser parser = new Parser( """ -struct S { - int a; - int[] b; -} +struct S { int a; int[] b; }; return 0; """); StopNode stop = parser.parse(); @@ -274,9 +251,9 @@ while( j < nprimes ) { return rez; """); StopNode stop = parser.parse().iterate(); - assertEquals("return int[];", stop.toString()); + assertEquals("return [int];", stop.toString()); Evaluator.Obj obj = (Evaluator.Obj)Evaluator.evaluate(stop, 20); - assertEquals("int[] {\n # :int;\n [] :int;\n}",obj.struct().toString()); + assertEquals("[int] { # :int; [] :int; }",obj.struct().toString()); long nprimes = (Long)obj.fields()[0]; long[] primes = new long[]{2,3,5,7,11,13,17,19}; for( int i=0; i<nprimes; i++ ) @@ -287,7 +264,7 @@ return rez; public void testNewNodeInit() { Parser parser = new Parser( """ -struct S {int i; flt f;} +struct S {int i; flt f;}; S s1 = new S; S s2 = new S; s2.i = 3; @@ -318,7 +295,7 @@ return new flt; int is = new int[2]; """); try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Type *int[] is not of declared type int",e.getMessage()); } + catch( Exception e ) { assertEquals("Type *[int] is not of declared type int",e.getMessage()); } } @Test @@ -348,4 +325,67 @@ return is[1]; catch( NegativeArraySizeException e ) { assertEquals("-1",e.getMessage()); } } + @Test + public void testProgress() { + Parser parser = new Parser(""" +i8 v1=0&0; +u8 v2=0; +byte v4=0; +if(0) {} +while(v2<0) { + v4=0-v1; + break; +} +int v5=0&0; +while(v5+(0&0)) { + int v7=0&0; + while(v7) + v4=0>>>v5; + while(v1) + return 0; +} +return v1; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return 0;", stop.toString()); + assertEquals(0L, Evaluator.evaluate(stop, 0)); + } + + @Test + public void testSharpNot() { + Parser parser = new Parser( +""" +if(0>>0) {} +while(0) {} +u32 v7=0; +int v8=0; +while(0<--1>>>---(v7*0==v8)) {} +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return 0;", stop.toString()); + } + + + @Test + public void testProgress2() { + Parser parser = new Parser( +""" +if(1) {} +else { + while(arg>>>0&0>>>0) {} + byte v3=0>>>0; + while(0) {} + int v7=0>>>0; + while(v7<0>>>0) { + while(0+v7<=0) if(1) arg=-12; + if(arg) { + v3=arg+v3+0; + arg=0; + } + } +} +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return 0;", stop.toString()); + } } diff --git a/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/src/test/java/com/seaofnodes/simple/Chapter16Test.java new file mode 100644 index 00000000..ad287e82 --- /dev/null +++ b/src/test/java/com/seaofnodes/simple/Chapter16Test.java @@ -0,0 +1,199 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.evaluator.Evaluator; +import com.seaofnodes.simple.node.StopNode; +import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class Chapter16Test { + + @Test + public void testJig() { + Parser parser = new Parser( +""" +return 3.14; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return 3.14;", stop.toString()); + assertEquals(3.14, Evaluator.evaluate(stop, 0)); + } + + + @Test + public void testMulti0() { + Parser parser = new Parser( +""" +int x, y; +return x+y; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return 0;", stop.toString()); + assertEquals(0L, Evaluator.evaluate(stop, 0)); + } + @Test + public void testMulti1() { + Parser parser = new Parser( +""" +int x=2, y=x+1; +return x+y; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return 5;", stop.toString()); + assertEquals(5L, Evaluator.evaluate(stop, 0)); + } + + @Test + public void testFinal0() { + Parser parser = new Parser( +""" +int !x=2; +x=3; +return x; +"""); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Cannot reassign final 'x'",e.getMessage()); } + } + + @Test + public void testFinal1() { + Parser parser = new Parser( +""" +int !x=2, y=3; +if( arg ) { int x = y; x = x*x; y=x; } // Shadow final x +return y; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Region21,9,3);", stop.toString()); + assertEquals(3L, Evaluator.evaluate(stop, 0)); + assertEquals(9L, Evaluator.evaluate(stop, 1)); + } + + @Test + public void testConstruct0() { + Parser parser = new Parser(""" +struct X { int x=3; }; +X z = new X; +return z.x; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return 3;", stop.toString()); + assertEquals(3L, Evaluator.evaluate(stop, 0)); + } + + @Test + public void testConstruct1() { + Parser parser = new Parser(""" +struct X { int !x; }; +X z = new X { x=3; }; +return z.x; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return 3;", stop.toString()); + assertEquals(3L, Evaluator.evaluate(stop, 0)); + } + + @Test + public void testConstruct2() { + Parser parser = new Parser(""" +struct X { int x=3; }; +X z = new X { x = 4; }; +return z.x; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return 4;", stop.toString()); + assertEquals(4L, Evaluator.evaluate(stop, 0)); + } + + + @Test + public void testStructFinal() { + Parser parser = new Parser(""" +struct Point { int !x, !y; }; +Point p = new Point { x=3; y=4; }; +return p; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return Point;", stop.toString()); + assertEquals("Obj<Point>{x=3,y=4}", Evaluator.evaluate(stop, 0).toString()); + } + + // Same as the Chapter13 test with the same name, but using the new + // constructor syntax + @Test + public void testLinkedList1() { + Parser parser = new Parser( +""" +struct LLI { LLI? next; int i; }; +LLI? head = null; +while( arg ) { + head = new LLI { next=head; i=arg; }; + arg = arg-1; +} +if( !head ) return 0; +LLI? next = head.next; +if( !next ) return 1; +return next.i; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("Stop[ return 0; return 1; return .i; ]", stop.toString()); + assertEquals(0L, Evaluator.evaluate(stop, 0)); + assertEquals(1L, Evaluator.evaluate(stop, 1)); + assertEquals(2L, Evaluator.evaluate(stop, 3)); + } + + @Test + public void testLinkedList2() { + Parser parser = new Parser( +""" +struct LLI { LLI? next; int i; }; +LLI? head = null; +while( arg ) { + head = new LLI { + next=head; + // Any old code in the constructor + int tmp=arg; + while( arg > 10 ) { + tmp = tmp + arg; + arg = arg - 1; + } + i=tmp; + }; + arg = arg-1; +} +if( !head ) return 0; +LLI? next = head.next; +if( !next ) return 1; +return next.i; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("Stop[ return 0; return 1; return .i; ]", stop.toString()); + assertEquals(0L, Evaluator.evaluate(stop, 0)); + assertEquals(1L, Evaluator.evaluate(stop, 1)); + assertEquals(2L, Evaluator.evaluate(stop, 11)); + } + + @Test + public void testSquare() { + Parser parser = new Parser( +""" +struct Square { + flt !side = arg; + // Newtons approximation to the square root, computed in a constructor. + // The actual allocation will copy in this result as the initial + // value for 'diag'. + flt diag = arg*arg/2; + while( 1 ) { + flt next = (side/diag + diag)/2; + if( next == diag ) break; + diag = next; + } +}; +return new Square; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return Square;", stop.toString()); + assertEquals("Obj<Square>{side=3.0,diag=1.7320508075688772}", Evaluator.evaluate(stop, 3).toString()); + assertEquals("Obj<Square>{side=4.0,diag=2.0}", Evaluator.evaluate(stop, 4).toString()); + } +} diff --git a/src/test/java/com/seaofnodes/simple/FuzzerWrap.java b/src/test/java/com/seaofnodes/simple/FuzzerWrap.java index 699711fc..eaeed8e2 100644 --- a/src/test/java/com/seaofnodes/simple/FuzzerWrap.java +++ b/src/test/java/com/seaofnodes/simple/FuzzerWrap.java @@ -1,10 +1,9 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.fuzzer.Fuzzer; -import org.junit.Test; +import java.util.Random; import org.junit.Ignore; - - +import org.junit.Test; import static org.junit.Assert.assertTrue; /** @@ -25,6 +24,15 @@ public class FuzzerWrap { @Test + public void fuzzPeepsSmall() { + Random R = new Random(System.currentTimeMillis()); + var fuzzer = new Fuzzer(); + for (int i=0; i<100; i++) + fuzzer.fuzzPeeps(R.nextLong()); + assertTrue(fuzzer.noExceptions()); + } + + @Test @Ignore public void fuzzPeepTiming() { var fuzzer = new Fuzzer(); diff --git a/src/test/java/com/seaofnodes/simple/SchedulerTest.java b/src/test/java/com/seaofnodes/simple/SchedulerTest.java index 853999b1..168e0ed7 100644 --- a/src/test/java/com/seaofnodes/simple/SchedulerTest.java +++ b/src/test/java/com/seaofnodes/simple/SchedulerTest.java @@ -41,7 +41,7 @@ return i; Parser parser = new Parser(""" struct S { int f; -} +}; S v0=new S; if(arg) v0.f=1; return v0; @@ -57,7 +57,7 @@ return v0; Parser parser = new Parser(""" struct S { int f; -} +}; S v=new S; v.f = 2; int i=new S.f; diff --git a/src/test/java/com/seaofnodes/simple/TypeTest.java b/src/test/java/com/seaofnodes/simple/TypeTest.java index cb45a0d7..6f7409cd 100644 --- a/src/test/java/com/seaofnodes/simple/TypeTest.java +++ b/src/test/java/com/seaofnodes/simple/TypeTest.java @@ -13,11 +13,11 @@ public class TypeTest { @Test public void testTypeAdHoc() { TypeStruct s1 = TypeStruct.make("s1", new Field[]{ - Field.make("a",-1, TypeInteger.BOT), - Field.make("b",-2, TypeInteger.BOT) }); + Field.make("a", TypeInteger.BOT,-1, false), + Field.make("b", TypeInteger.BOT,-2, false) }); TypeStruct s2 = TypeStruct.make("s2", new Field[]{ - Field.make("a",-3, TypeInteger.BOT), - Field.make("b",-4, TypeInteger.BOT) }); + Field.make("a", TypeInteger.BOT,-3, false), + Field.make("b", TypeInteger.BOT,-4, false) }); Assert.assertEquals(s1, s1.glb()); Assert.assertNotEquals(s1, s1.dual()); Assert.assertEquals(s1, s1.dual().glb()); diff --git a/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java index 34921b94..28896283 100644 --- a/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java +++ b/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java @@ -192,28 +192,20 @@ public class Evaluator { if( n < 0 ) throw new NegativeArraySizeException(""+n); body = new Object[(int)n+1]; // Array body - var c = type._fields[type._fields.length-1]._type; - if (c instanceof TypeInteger) { - for (int i=0; i<n; i++) body[i+type._fields.length-1] = 0L; - } else if (c instanceof TypeFloat) { - for (int i = 0; i < n; i++) body[i+type._fields.length-1] = 0D; + var elem = type._fields[1]._type; + if (elem instanceof TypeInteger) { + for (int i=0; i<n; i++) body[i+1] = 0L; + } else if (elem instanceof TypeFloat) { + for (int i = 0; i < n; i++) body[i+1] = 0D; } else { - assert c instanceof TypeMemPtr; + assert elem instanceof TypeMemPtr; } - num = type._fields.length-1; + // Length value + body[0] = vall(alloc.in(2+2)); } else { - body = new Object[type._fields.length]; - num = type._fields.length; - } - for (int i=0; i<num; i++) { - var c = type._fields[i]._type; - if (c instanceof TypeInteger) { - body[i] = 0L; - } else if (c instanceof TypeFloat) { - body[i] = 0D; - } else { - assert c instanceof TypeMemPtr; - } + body = new Object[num = type._fields.length]; + for (int i=0; i<num; i++) + body[i] = val(alloc.in(2+i+num)); } Object[] mems = new Object[type._fields.length+2]; // mems[0] is control @@ -315,6 +307,7 @@ public class Evaluator { case NewNode alloc -> alloc(alloc); case CProjNode cproj -> ((Object[])val(cproj.ctrl()))[cproj._idx]; case ProjNode proj -> ((Object[])val( proj.in(0) ))[ proj._idx]; + case ScopeMinNode mem -> null; default -> throw new AssertionError("Unexpected node " + node); }; } @@ -325,8 +318,8 @@ public class Evaluator { public Object evaluate(long parameter, int loops) { if (start == null) return Status.TIMEOUT; var s = new Object[start.compute()._types.length]; - s[1] = parameter; - for(int i=2;i<s.length;i++) s[i] = MEMORY; + s[1] = MEMORY; + s[2] = parameter; values[start._nid] = s; int i=0; Scheduler.Block block = this.startBlock; diff --git a/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java b/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java index 59c15d3f..06169e2c 100644 --- a/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java +++ b/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java @@ -560,7 +560,7 @@ public class Scheduler { blocks.put(first, switch(last) { case null -> new Block(arr, null, -1, new Block[0]); case IfNode i -> new Block(arr, i, -1, new Block[2]); - case RegionNode r -> new Block(arr, r, Utils.find(r._inputs, prev), new Block[1]); + case RegionNode r -> new Block(arr, r, r._inputs.find(prev), new Block[1]); case ReturnNode r -> new Block(arr, r, -1, new Block[0]); default -> throw new AssertionError("Unexpected block exit node"); }); diff --git a/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java b/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java index 7cd854bf..d3cbb0ee 100644 --- a/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java +++ b/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java @@ -109,7 +109,7 @@ public class ScriptGenerator { static { new Parser("", TypeInteger.BOT); - for( var e : Parser.TYPES.entrySet() ) { + for( var e : Parser.defaultTypes().entrySet() ) { if( e.getValue() instanceof TypeInteger ) { INTTYPES.add(new TypeInt(e.getKey())); } |
