diff options
| author | Cliff Click <cliffc@acm.org> | 2024-11-09 13:47:37 -0800 |
|---|---|---|
| committer | Cliff Click <cliffc@acm.org> | 2024-11-23 09:03:20 -0800 |
| commit | e386e6323e34f2a3d4e5b5779cdefd12c9552bd6 (patch) | |
| tree | 3ed7fa23321abfc8b988862799f5bfdc7aaea7e5 | |
| parent | baa84f5eb99cd21f6704e71515b6ec7bbd78624f (diff) | |
| download | Simple-e386e6323e34f2a3d4e5b5779cdefd12c9552bd6.tar.gz Simple-e386e6323e34f2a3d4e5b5779cdefd12c9552bd6.zip | |
Change field mutability
Fields & locals with an initializer are immutable by default; those without are mutable. Make mutable with `!`. Fields are always mutable in a constructor.
Add a trinary `pred ? then : else` syntax.
SubNode retains as much precision as AddNode.
Sharpen some asserts and error cases.
Limit Load node cloning if the ptr is not obvious.
19 files changed, 365 insertions, 95 deletions
diff --git a/chapter17/README.md b/chapter17/README.md index 5a7f48d3..95cc397e 100644 --- a/chapter17/README.md +++ b/chapter17/README.md @@ -1,6 +1,7 @@ # Chapter 17: Syntax Sugar -# Post-increment + +## Post-increment Allow `arg++` and `arg--` with the usual meanings; the value is updated and the expression is the pre-update value. Greedy match is used so `arg---arg` parses @@ -8,12 +9,13 @@ as `(arg--)-arg`. Also allows `s.fld++` and `ary[idx]++`, and their `--` variants. -# var & val + +## var & val `var` can be used in the `type` position to indicate a "variable" (mutable -value), whose type will be inferred from the initalizing expression. +value), whose type will be inferred from the initalizing expression. -`val` is the same as `var`, except its a "value" (not mutable). +`val` is the same as `var`, except it is a "value" (not mutable). The inferred value is the `glb` of the peephole type, which means `var` and `val` will not infer types like `u8` or `f32`, instead inferring `int` and @@ -26,9 +28,91 @@ infers as `S s? = new S;` +## Initialized fields are immutable by default + +Typed field declarations with an initializer are immutable by default, and can +be made mutable with a leading `!`. `var` and `val` keep their current sense. +Fields are immutable at the end of either constructor, but can be changed +during construction. + + +Examples: + + +```java +struct Point { int x,y; }; // Fields x and y are not initialized, mutable +Point p = new Point; +p.x++; // Mutate is ok +return p.x; +``` + +```java +struct Point { int x=3,y=4; }; // Fields x and y are initialized, immutable +Point p = new Point; +p.x++; // Error! +return p.x; +``` + +```java +struct Point { int x=3,y=4; }; // Fields x and y are initialized, immutable +Point p = new Point { x=x*x; y=y*y; }; // Ok to mutate in the constructor +p.x++; // Error! +return p.x; +``` + +```java +struct Point { int !x=3, !y=4; }; // Fields x and y are initialized, mutable +Point p = new Point; +p.x++; // Mutate is OK +return p.x; +``` + +```java +struct Point { var x,y; }; // Error, var and val require an initializer +Point p = new Point; +p.x++; +return p.x; +``` + +```java +struct Point { var x=3,y=4; }; // Fields x and y are mutable +Point p = new Point; +p.x++; // Mutate is OK +return p.x; +``` + +```java +struct Point { val x=3,y=4; }; // Fields x and y are immutable +Point p = new Point; +p.x++; // Error! +return p.x; +``` + + +## Deep Immutability (proposal) + +```cpp +struct Bar { int x; }; +Bar b = new Bar; /* b = null; *//* b.x++; */ // immutable & deep immutable +Bar !b = new Bar; b = null; /* b.x++; */ // mutable & deep immutable +Bar b! = new Bar; /* b = null; */ b.x++; // immutable & deep mutable +Bar !b! = new Bar; b = null; b.x++; // mutable & deep mutable (ignoring NPE) +val b = new Bar; /* b = null; *//* b.x++; */ // immutable & deep immutable +var b = new Bar; b = null; b.x++; // mutable & inherits deep mutability from RHS +``` + + +## Trinary + +Allow `pred ? e_true : e_false`. Also allow `pred ? e_true`, where the false result +is the zero type version of the true result. + + + + + -invert ! C++-style for-loop for-interator trinary @@ -36,6 +120,9 @@ switch + + + # Chapter 18: Functions Some thinking on function syntax: diff --git a/chapter17/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter17/src/main/java/com/seaofnodes/simple/IterPeeps.java index 96ae350e..81b89513 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/IterPeeps.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/IterPeeps.java @@ -1,6 +1,7 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; import java.util.ArrayList; import java.util.Arrays; @@ -113,9 +114,12 @@ public abstract class IterPeeps { MID_ASSERT = true; int old_cnt = Node.ITER_CNT, old_nop = Node.ITER_NOP_CNT; Node changed = stop.walk( n -> { - if( WORK.on(n) ) return null; - Node m = n.peepholeOpt(); - if( m==null ) return null; + Node m = n; + if( n.compute().isa(n._type) ) { // Types must be forwards, even if on worklist + if( WORK.on(n) ) return null; + m = n.peepholeOpt(); + if( m==null ) return null; + } System.err.println("BREAK HERE FOR BUG"); return m; }); diff --git a/chapter17/src/main/java/com/seaofnodes/simple/Parser.java b/chapter17/src/main/java/com/seaofnodes/simple/Parser.java index fac012a4..371bddf1 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/Parser.java @@ -150,7 +150,7 @@ public class Parser { 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); } + private <N extends Node> N ctrl(N n) { return _scope.ctrl(n); } public StopNode parse() { return parse(false); } public StopNode parse(boolean show) { @@ -165,7 +165,7 @@ public class Parser { _scope.define(ScopeNode.ARG0, TypeInteger.BOT, false, new ProjNode(START, 2, ScopeNode.ARG0).peephole()); // Parse whole program - parseBlock(); + parseBlock(false); if( ctrl()._type==Type.CONTROL ) STOP.addReturn(new ReturnNode(ctrl(), ZERO, _scope).peephole()); @@ -189,9 +189,9 @@ public class Parser { * Does not parse the opening or closing '{}' * @return a {@link Node} or {@code null} */ - private Node parseBlock() { + private Node parseBlock(boolean inCon) { // Enter a new scope - _scope.push(); + _scope.push(inCon); while (!peek('}') && !_lexer.isEOF()) parseStatement(); // Exit scope @@ -210,7 +210,7 @@ public class Parser { private Node parseStatement() { if( false ) return null; else if (matchx("return") ) return parseReturn(); - else if (match ("{") ) return require(parseBlock(),"}"); + else if (match ("{") ) return require(parseBlock(false),"}"); else if (matchx("if") ) return parseIf(); else if (matchx("while") ) return parseWhile(); else if (matchx("break") ) return parseBreak(); @@ -324,7 +324,7 @@ public class Parser { return cur; // toScope is either the break scope, or a scope that was created here assert toScope._lexSize.size() <= _breakScope._lexSize.size(); - toScope.ctrl(toScope.mergeScopes(cur)); + toScope.ctrl(toScope.mergeScopes(cur).peephole()); return toScope; } @@ -342,9 +342,16 @@ public class Parser { * @return a {@link Node}, never {@code null} */ private Node parseIf() { - require("("); // Parse predicate - var pred = require(parseExpression(), ")").keep(); + require("("); + var pred = require(parseExpression(), ")"); + return parseTrinary(pred,true,"else"); + } + + // Parse a conditional expression, merging results. + private Node parseTrinary( Node pred, boolean stmt, String fside ) { + pred.keep(); + // IfNode takes current control and predicate Node ifNode = new IfNode(ctrl(), pred).peephole(); // Setup projection nodes @@ -359,19 +366,24 @@ public class Parser { // Parse the true side ctrl(ifT.unkeep()); // set ctrl token to ifTrue projection _scope.upcast(ifT,pred,false); // Up-cast predicate - parseStatement(); // Parse true-side + Node lhs = stmt ? parseStatement() : parseExpression().keep(); // Parse true-side ScopeNode tScope = _scope; // Parse the false side _scope = fScope; // Restore scope, then parse else block if any ctrl(ifF.unkeep()); // Ctrl token is now set to ifFalse projection - _scope.upcast(ifF,pred,true); // Up-cast predicate - if (matchx("else")) { - parseStatement(); + // Up-cast predicate, even if not else clause, because predicate can + // remain true if the true clause exits: `if( !ptr ) return 0; return ptr.fld;` + _scope.upcast(ifF,pred,true); + boolean doRHS = match(fside); + Node rhs = doRHS + ? (stmt ? parseStatement() : parseExpression()) + : (stmt ? null : con(lhs._type.makeZero())); + if( doRHS ) fScope = _scope; - } pred.unkeep(); + // Check for `if(pred) int x=17;` if( tScope.nIns() != ndefs || fScope.nIns() != ndefs ) throw error("Cannot define a new name on one arm of an if"); @@ -379,10 +391,12 @@ public class Parser { _scope = tScope; _xScopes.pop(); // Discard pushed from graph display - return ctrl(tScope.mergeScopes(fScope)); + RegionNode r = ctrl(tScope.mergeScopes(fScope)); + Node ret = stmt ? r : new PhiNode("",lhs._type.meet(rhs._type),r,lhs,rhs).peephole(); + r.peephole(); + return ret; } - /** * Parses a return statement; "return" already parsed. * The $ctrl edge is killed. @@ -410,42 +424,39 @@ public class Parser { /** Parse: name [=expr] */ - private Node parseAsgn(Type t, boolean xfinal) { - boolean isDecl = t!=null; // Having a type is a declaration, missing one is updating a prior name + // t==null ==>> re-assign: !final, expr=parse(), t:=def ._type + // t==TOP ==>> value : final, expr=parse(), t:=expr._type + // t==BOT ==>> variable : !final, expr=parse(), t:=expr._type + // t==type && !expr ==>> declaratn: !final, expr=makeInit; t:=type; + // t==type && expr ==>> declaratn: final=inCon && noBang, expr=parse() ; t:=type; + private Node parseAsgn(Type t, boolean hasBang) { + boolean isDecl = t!=null; int old = _lexer._position; String name = requireId(); - Node expr; - if( peek(';') || peek(',') ) { - // Bare "name" is not allowed - if( t==null ) throw errorSyntax("expression"); - // 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(); - // "var" and "val" require an expression to infer - if( init == null ) throw errorSyntax("expression"); - expr = con(init); - - } else if( !match("=") ) { // Something else - _lexer._position = old; - return null; - } else - expr = parseExpression(); + + boolean hasType = t!=null && t!=Type.TOP && t!=Type.BOTTOM; // User written type + boolean hasExpr = !(peek(';') || peek(',')); // Expression follows + if( hasExpr && !match("=") ) // Something else + { _lexer._position = old; return null; } + if( !hasExpr && t==Type.BOTTOM ) throw errorSyntax("expression"); + Node expr = hasExpr ? parseExpression() : con(t.makeInit()); + boolean xfinal = t==Type.TOP || (hasType && hasExpr && !hasBang && _scope.inCon()); // Defining a new variable vs updating an old one - if( !isDecl ) { // Assigning over an existing name - // Lookup + if( t==null ) { + // Lookup old variable ScopeMinNode.Var def = _scope.lookup(name); if( def==null ) throw error("Undefined name '" + 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 ) + // outside the constructor need to check the final bit. + if( _scope.in(def._idx)._type!=Type.TOP && def._final && !_scope.inCon() ) throw error("Cannot reassign final '"+name+"'"); t = def.type(); // Declared field type + } else if( !hasType && hasExpr ) { + // Discovered type from expression + t = expr._type.glb(); } // Auto-widen int to float @@ -475,10 +486,7 @@ public class Parser { /** Parse final: [!]asgn */ private Node parseFinal(Type t) { - // TOP is a "val" marker - uses type inference for a "value", i.e. a final. - // BOT is a "var" marker - uses type inference for a "variable", i.e. not final - boolean xfinal = t==Type.TOP || (t!=null && match("!")); - return parseAsgn(t,xfinal); + return parseAsgn(t,match("!")); } /** @@ -526,7 +534,7 @@ public class Parser { throw errorSyntax("struct '" + typeName + "' cannot be redefined"); // A Block scope parse, and inspect the scope afterward for fields. - _scope.push(); + _scope.push(true); require("{"); while (!peek('}') && !_lexer.isEOF()) parseStatement(); @@ -562,6 +570,9 @@ public class Parser { if( tname==null ) return null; // Convert the type name to a type. Type t0 = TYPES.get(tname); + // No new types as keywords + if( t0 == null && KEYWORDS.contains(tname) ) + { _lexer._position = old1; return null; } Type t1 = t0 == null ? TypeMemPtr.make(TypeStruct.makeFRef(tname)) : t0; // Null: assume a forward ref type // Nest arrays and '?' as needed while( true ) { @@ -616,11 +627,14 @@ public class Parser { * Parse an expression of the form: * * <pre> - * expr : compareExpr + * expr : bitwise [? expr [: expr]] * </pre> * @return an expression {@link Node}, never {@code null} */ - private Node parseExpression() { return parseBitwise(); } + private Node parseExpression() { + Node expr = parseBitwise(); + return match("?") ? parseTrinary(expr,false,":") : expr; + } /** * Parse an bitwise expression @@ -823,7 +837,7 @@ public class Parser { 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(); + parseBlock(true); require("}"); init = _scope._inputs; } @@ -948,6 +962,8 @@ public class Parser { // ary[idx]++ if( matchx("++") || matchx("--") ) { + if( f._final && !f._fname.equals("[]") ) + throw error("Cannot reassign final '"+f._fname+"'"); Node inc = new AddNode(load,con( _lexer.peek(-1)=='+' ? 1 : -1)).peephole(); Node val = zsMask(inc,f._type); Node st = new StoreNode(name, f._alias, f._type, memAlias(f._alias), expr, off, val, false); diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/AddNode.java index f3b213fb..0834a381 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -36,7 +36,7 @@ public class AddNode extends Node { return TypeInteger.BOT; } - private static boolean overflow( long x, long y ) { + static boolean overflow( long x, long y ) { if( (x ^ y ) < 0 ) return false; // unequal signs, never overflow return (x ^ (x + y)) < 0; // sum has unequal signs, so overflow } diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/CFGNode.java index 9e9d6970..3513f265 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/CFGNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/CFGNode.java @@ -28,15 +28,6 @@ public abstract class CFGNode extends Node { // Block head is Start, Region, CProj, but not e.g. If, Return, Stop public boolean blockHead() { return false; } - // Should be exactly 1 tail from a block head - public CFGNode blockTail() { - assert blockHead(); - for( Node n : _outputs ) - if( n instanceof CFGNode cfg ) - return cfg; - return null; - } - // ------------------------------------------------------------------------ /** * Immediate dominator tree depth, used to approximate a real IDOM during diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/LoadNode.java index 2c0afda2..555b9ae4 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -87,6 +87,8 @@ public class LoadNode extends MemOpNode { break outer; // Cannot tell, ptr not related to New mem = nnn1.in(_alias);// Bypass unrelated New break; + } else if( mproj.in(0) instanceof StartNode ) { + break outer; } else throw Utils.TODO(); default: throw Utils.TODO(); @@ -101,7 +103,11 @@ public class LoadNode extends MemOpNode { // else ptr.x = e1; : e1; // val = ptr.x; ptr.x = val; if( mem() instanceof PhiNode memphi && memphi.region()._type == Type.CONTROL && memphi.nIns()== 3 && - off() instanceof ConstantNode ) { + // Offset can be hoisted + off() instanceof ConstantNode && + // Pointer can be hoisted + hoistPtr(ptr,memphi) ) { + // Profit on RHS/Loop backedge if( profit(memphi,2) || // Else must not be a loop to count profit on LHS. @@ -125,6 +131,25 @@ public class LoadNode extends MemOpNode { ptr2 instanceof ProjNode && ptr2.in(0) instanceof NewNode; } + private static boolean hoistPtr(Node ptr, PhiNode memphi ) { + // Can I hoist ptr above the Region? + if( !(memphi.region() instanceof RegionNode r) ) + return false; // Dead or dying Region/Phi + // If ptr from same Region, then yes, just use hoisted split pointers + if( ptr instanceof PhiNode pphi && pphi.region() == r ) + return true; + + // No, so can we lift this ptr? + CFGNode cptr = ptr.cfg0(); + if( cptr != null ) + // Pointer is controlled high + // TODO: Really needs to be the LCA of all inputs is high + return cptr.idepth() <= r.idepth(); + + // Dunno without a longer walk + return false; + } + // 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/chapter17/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter17/src/main/java/com/seaofnodes/simple/node/Node.java index c67fb910..35dbc19e 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/Node.java @@ -153,7 +153,7 @@ public abstract class Node implements OutNode { * @param new_def the new definition * @return new_def for flow coding */ - public Node setDef(int idx, Node new_def ) { + public <N extends Node> N setDef(int idx, N new_def ) { unlock(); Node old_def = in(idx); if( old_def == new_def ) return new_def; // No change diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/ScopeNode.java index f7446c51..2fc495c5 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -29,9 +29,11 @@ public class ScopeNode extends ScopeMinNode { // Since of each nested lexical scope public final Ary<Integer> _lexSize; + // True if parsing inside of a constructor + public final Ary<Boolean> _inCons; // A new ScopeNode - public ScopeNode() { _vars = new Ary<>(Var.class); _lexSize = new Ary<>(Integer.class); } + public ScopeNode() { _vars = new Ary<>(Var.class); _lexSize = new Ary<>(Integer.class); _inCons = new Ary<>(Boolean.class); } @Override public String label() { return "Scope"; } @@ -75,17 +77,24 @@ public class ScopeNode extends ScopeMinNode { * * @return Node that was bound */ - public Node ctrl(Node n) { return setDef(0,n); } + public <N extends Node> N ctrl(N n) { return setDef(0,n); } - public void push() { + public void push() { push(false); } + public void push(boolean inCon) { + assert _lexSize._len==_inCons._len; _lexSize.push(_vars.size()); + _inCons.push(inCon); } public void pop() { + assert _lexSize._len==_inCons._len; int n = _lexSize.pop(); + _inCons.pop(); popUntil(n); _vars.setLen(n); } + public boolean inCon() { return _inCons.last(); } + // 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. @@ -180,6 +189,7 @@ public class ScopeNode extends ScopeMinNode { // 3) Ensure that the order of defs is the same to allow easy merging dup._vars.addAll(_vars); dup._lexSize.addAll(_lexSize); + dup._inCons .addAll(_inCons ); dup.addDef(ctrl()); // Control input is just copied // Memory input is a shallow copy @@ -208,13 +218,13 @@ public class ScopeNode extends ScopeMinNode { * @param that The ScopeNode to be merged into this * @return A new node representing the merge point */ - public Node mergeScopes(ScopeNode that) { + public RegionNode mergeScopes(ScopeNode that) { RegionNode r = (RegionNode) ctrl(new RegionNode(null,ctrl(), that.ctrl()).keep()); mem()._merge(that.mem(),r); this ._merge(that ,r); that.kill(); // Kill merged scope IterPeeps.add(r); - return r.unkeep().peephole(); + return r.unkeep(); } private void _merge(ScopeNode that, RegionNode r) { diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/SubNode.java index 009fb30e..5d6c2643 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -29,6 +29,11 @@ public class SubNode extends Node { t2 instanceof TypeInteger i2 ) { if (i1.isConstant() && i2.isConstant()) return TypeInteger.constant(i1.value()-i2.value()); + // Fold ranges like {2-3} - {0-1} into {1-3}. + if( !AddNode.overflow(i1._min,-i2._max) && + !AddNode.overflow(i1._max,-i2._min) && + i2._min != Long.MIN_VALUE ) + return TypeInteger.make(i1._min-i2._max,i1._max-i2._min); } // Sub of same is 0 if( in(1)==in(2) ) diff --git a/chapter17/src/main/java/com/seaofnodes/simple/type/Type.java b/chapter17/src/main/java/com/seaofnodes/simple/type/Type.java index 7467be79..e454f48f 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/type/Type.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/type/Type.java @@ -82,6 +82,7 @@ public class Type { public StringBuilder typeName( StringBuilder sb) { return print(sb); } public Type makeInit() { return null; } + public Type makeZero() { return null; } public Type nonZero() { return glb(); } // ---------------------------------------------------------- diff --git a/chapter17/src/main/java/com/seaofnodes/simple/type/TypeFloat.java b/chapter17/src/main/java/com/seaofnodes/simple/type/TypeFloat.java index dc36c082..37f6ce71 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/type/TypeFloat.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/type/TypeFloat.java @@ -108,6 +108,7 @@ public class TypeFloat extends Type { public Type glb() { return BOT; } @Override public TypeFloat makeInit() { return ZERO; } + @Override public TypeFloat makeZero() { return ZERO; } @Override int hash() { return (int)(Double.hashCode(_con) ^ _sz); } diff --git a/chapter17/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/chapter17/src/main/java/com/seaofnodes/simple/type/TypeInteger.java index b475055d..06848dc7 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/type/TypeInteger.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/type/TypeInteger.java @@ -81,6 +81,7 @@ public class TypeInteger extends Type { if( this==I16 || this==U16 ) return 1; // 1<<1 == 2 bytes if( this==I32 || this==U32 ) return 2; // 1<<2 == 4 bytes if( this==BOT ) return 3; // 1<<3 == 8 bytes + if( isHighOrConst() ) return 0; throw Utils.TODO(); } @@ -115,6 +116,7 @@ public class TypeInteger extends Type { @Override public TypeInteger dual() { return make(_max,_min); } @Override public TypeInteger glb() { return BOT; } @Override public TypeInteger makeInit() { return ZERO; } + @Override public TypeInteger makeZero() { return ZERO; } @Override public TypeInteger nonZero() { if( isHigh() ) return this; if( _min==0 ) return make(1,Math.max(_max,1)); // specifically good on BOOL diff --git a/chapter17/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java b/chapter17/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java index 449bf89e..cc4b9fcd 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java @@ -59,7 +59,8 @@ public class TypeMemPtr extends Type { public TypeMemPtr glb() { return make(_obj.glb(),true); } // Is forward-reference @Override public boolean isFRef() { return _obj.isFRef(); } - @Override public TypeMemPtr makeInit() { return NULLPTR; } + @Override public Type makeInit() { return _nil ? NULLPTR : Type.TOP; } + @Override public Type makeZero() { return NULLPTR; } @Override public Type nonZero() { return VOIDPTR; } @Override public boolean isHigh() { return this==TOP; } diff --git a/chapter17/src/test/java/com/seaofnodes/simple/BrainFuckTest.java b/chapter17/src/test/java/com/seaofnodes/simple/BrainFuckTest.java index abb8b834..de6852c7 100644 --- a/chapter17/src/test/java/com/seaofnodes/simple/BrainFuckTest.java +++ b/chapter17/src/test/java/com/seaofnodes/simple/BrainFuckTest.java @@ -24,7 +24,7 @@ public class BrainFuckTest { Parser parser = new Parser( encoded + """ var pc = 0, d = 0; -u8[] output = new u8[0]; +u8[] !output = new u8[0]; val data = new u8[100]; while (pc < program#) { diff --git a/chapter17/src/test/java/com/seaofnodes/simple/Chapter03Test.java b/chapter17/src/test/java/com/seaofnodes/simple/Chapter03Test.java index c76df903..4525918c 100644 --- a/chapter17/src/test/java/com/seaofnodes/simple/Chapter03Test.java +++ b/chapter17/src/test/java/com/seaofnodes/simple/Chapter03Test.java @@ -31,7 +31,7 @@ public class Chapter03Test { @Test public void testVarScopeNoPeephole() { - Parser parser = new Parser("int a=1; int b=2; int c=0; { int b=3; c=a+b; } return c; "); + Parser parser = new Parser("int a=1; int b=2; int !c=0; { int b=3; c=a+b; } return c; "); Node._disablePeephole = true; StopNode ret = parser.parse(); Node._disablePeephole = false; @@ -58,7 +58,7 @@ public class Chapter03Test { @Test public void testBad1() { try { - new Parser("int a=1; int b=2; int c=0; { int b=3; c=a+b;").parse(); + new Parser("int a=1; int b=2; int !c=0; { int b=3; c=a+b;").parse(); fail(); } catch( RuntimeException e ) { assertEquals("Syntax error, expected }: ",e.getMessage()); diff --git a/chapter17/src/test/java/com/seaofnodes/simple/Chapter04Test.java b/chapter17/src/test/java/com/seaofnodes/simple/Chapter04Test.java index 70f38dd6..24612fee 100644 --- a/chapter17/src/test/java/com/seaofnodes/simple/Chapter04Test.java +++ b/chapter17/src/test/java/com/seaofnodes/simple/Chapter04Test.java @@ -96,14 +96,14 @@ public class Chapter04Test { @Test public void testBug1() { - Parser parser = new Parser("int a=arg+1; int b=a; b=1; return a+2; "); + Parser parser = new Parser("int a=arg+1; int !b=a; b=1; return a+2; "); StopNode ret = parser.parse(); assertEquals("return (arg+3);", ret.print()); } @Test public void testBug2() { - Parser parser = new Parser("int a=arg+1; a=a; return a; "); + Parser parser = new Parser("int !a=arg+1; a=a; return a; "); StopNode ret = parser.parse(); assertEquals("return (arg+1);", ret.print()); } diff --git a/chapter17/src/test/java/com/seaofnodes/simple/Chapter13Test.java b/chapter17/src/test/java/com/seaofnodes/simple/Chapter13Test.java index 165d23e5..aa659147 100644 --- a/chapter17/src/test/java/com/seaofnodes/simple/Chapter13Test.java +++ b/chapter17/src/test/java/com/seaofnodes/simple/Chapter13Test.java @@ -144,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(); } diff --git a/chapter17/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/chapter17/src/test/java/com/seaofnodes/simple/Chapter16Test.java index ad287e82..be48ec55 100644 --- a/chapter17/src/test/java/com/seaofnodes/simple/Chapter16Test.java +++ b/chapter17/src/test/java/com/seaofnodes/simple/Chapter16Test.java @@ -43,23 +43,23 @@ return x+y; 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 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; +int x=2, y=3; if( arg ) { int x = y; x = x*x; y=x; } // Shadow final x return y; """); @@ -107,7 +107,7 @@ return z.x; @Test - public void testStructFinal() { + public void testStructFinal0() { Parser parser = new Parser(""" struct Point { int !x, !y; }; Point p = new Point { x=3; y=4; }; @@ -118,6 +118,67 @@ return p; assertEquals("Obj<Point>{x=3,y=4}", Evaluator.evaluate(stop, 0).toString()); } + @Test + public void testStructFinal1() { + Parser parser = new Parser(""" +struct Point { int x=3, y=4; }; +Point p = new Point { x=5; y=6; }; +p.x++; +return p; +"""); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Cannot reassign final 'x'",e.getMessage()); } + } + + @Test + public void testStructFinal2() { + Parser parser = new Parser(""" +struct Point { int x=3, y=4; }; +Point p = new Point; +p.x++; +return p; +"""); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Cannot reassign final 'x'",e.getMessage()); } + } + + @Test + public void testStructFinal3() { + Parser parser = new Parser(""" +struct Point { var x; var y; }; +Point p = new Point; +p.x++; +return p; +"""); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Syntax error, expected expression: ;",e.getMessage()); } + } + + @Test + public void testStructFinal4() { + Parser parser = new Parser(""" +struct Point { val x=3; val y=4; }; +Point p = new Point; +p.x++; +return p; +"""); + try { parser.parse().iterate(); fail(); } + catch( Exception e ) { assertEquals("Cannot reassign final 'x'",e.getMessage()); } + } + + @Test + public void testStructFinal5() { + Parser parser = new Parser(""" +struct Point { var x=3; var y=4; }; +Point p = new Point; +p.x++; +return p; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return Point;", stop.toString()); + assertEquals("Obj<Point>{x=4,y=4}", Evaluator.evaluate(stop, 0).toString()); + } + // Same as the Chapter13 test with the same name, but using the new // constructor syntax @Test @@ -152,7 +213,7 @@ while( arg ) { head = new LLI { next=head; // Any old code in the constructor - int tmp=arg; + int !tmp=arg; while( arg > 10 ) { tmp = tmp + arg; arg = arg - 1; @@ -182,7 +243,7 @@ struct Square { // 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; + flt !diag = arg*arg/2; while( 1 ) { flt next = (side/diag + diag)/2; if( next == diag ) break; diff --git a/chapter17/src/test/java/com/seaofnodes/simple/Chapter17Test.java b/chapter17/src/test/java/com/seaofnodes/simple/Chapter17Test.java index 96f12480..5db9592e 100644 --- a/chapter17/src/test/java/com/seaofnodes/simple/Chapter17Test.java +++ b/chapter17/src/test/java/com/seaofnodes/simple/Chapter17Test.java @@ -2,6 +2,7 @@ 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; @@ -97,6 +98,71 @@ return d; catch( Exception e ) { assertEquals("Syntax error, expected expression: ;",e.getMessage()); } } + + @Test + public void testTrinary0() { + Parser parser = new Parser(""" +return arg ? 1 : 2; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Region19,1,2);", stop.toString()); + assertEquals(2L, Evaluator.evaluate(stop, 0)); + assertEquals(1L, Evaluator.evaluate(stop, 1)); + } + + @Test + public void testTrinary1() { + Parser parser = new Parser(""" +return arg ? 0 : arg; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return 0;", stop.toString()); + assertEquals(0L, Evaluator.evaluate(stop, 0)); + } + + @Test + public void testTrinary2() { + Parser parser = new Parser(""" +struct Bar { int x; }; +var b = arg ? new Bar : null; +return b ? b.x++ + b.x++ : -1; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Region54,1,-1);", stop.toString()); + assertEquals(-1L, Evaluator.evaluate(stop, 0)); + assertEquals( 1L, Evaluator.evaluate(stop, 1)); + } + + @Test + public void testTrinary3() { + Parser parser = new Parser(""" +struct Bar { int x; }; +var b = arg ? new Bar; +return b ? b.x++ + b.x++ : -1; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Region54,1,-1);", stop.toString()); + assertEquals(-1L, Evaluator.evaluate(stop, 0)); + assertEquals( 1L, Evaluator.evaluate(stop, 1)); + } + + @Ignore + @Test + public void testTrinary4() { + Parser parser = new Parser(""" +struct Bar { Bar? next; int x; }; +var b = arg ? new Bar { next = (arg==2) ? new Bar{x=2;}; x=1; }; +return b ? b.next ? b.next.x : b.x; // parses "b ? (b.next ? b.next.x : b.x) : 0" +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Region51,1,-1);", stop.toString()); + assertEquals( 0L, Evaluator.evaluate(stop, 0)); + assertEquals( 1L, Evaluator.evaluate(stop, 1)); + assertEquals( 2L, Evaluator.evaluate(stop, 2)); + } + + + @Test public void testLinkedList2() { Parser parser = new Parser(""" |
