From f5f0580331b4e29a0a0dc43d9885dfe0f0aed865 Mon Sep 17 00:00:00 2001 From: Cliff Click Date: Fri, 13 Jun 2025 15:42:08 -0700 Subject: Chapter 25: SSA Before Types Are Known MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Hsiang-Ying Fu <66062782+fuhsnn@users.noreply.github.com> Co-authored-by: Tamás Hadházy <85063808+Hels15@users.noreply.github.com> Co-authored-by: fuhsnn <66062782+fuhsnn@users.noreply.github.com> Co-authored-by: Symious <14933944+symious@users.noreply.github.com> Co-authored-by: Jun Kawasaki Co-authored-by: Hadházy Tamás Co-authored-by: Claude Opus 5 --- src/main/java/com/seaofnodes/simple/IterPeeps.java | 2 +- src/main/java/com/seaofnodes/simple/Parser.java | 152 +++++++++++------ .../com/seaofnodes/simple/codegen/CodeGen.java | 9 + .../com/seaofnodes/simple/codegen/InstSelect.java | 23 ++- .../com/seaofnodes/simple/codegen/Machine.java | 5 +- .../java/com/seaofnodes/simple/codegen/Opto.java | 62 +++++++ .../com/seaofnodes/simple/codegen/TypeCheck.java | 36 +++- .../java/com/seaofnodes/simple/node/AddFNode.java | 1 + .../java/com/seaofnodes/simple/node/AddNode.java | 43 +++-- .../java/com/seaofnodes/simple/node/AndNode.java | 6 +- .../com/seaofnodes/simple/node/ArithFNode.java | 1 + .../java/com/seaofnodes/simple/node/ArithNode.java | 184 ++++++++++++++++----- .../java/com/seaofnodes/simple/node/BoolNode.java | 128 +++++++++----- .../com/seaofnodes/simple/node/BulkMemPhiNode.java | 3 +- .../java/com/seaofnodes/simple/node/CallNode.java | 2 +- .../java/com/seaofnodes/simple/node/CastNode.java | 8 + .../com/seaofnodes/simple/node/ConstantNode.java | 10 +- .../com/seaofnodes/simple/node/ConvertNode.java | 103 ++++++++++++ .../java/com/seaofnodes/simple/node/DivFNode.java | 1 + .../java/com/seaofnodes/simple/node/DivNode.java | 23 ++- .../com/seaofnodes/simple/node/FldOffNode.java | 24 +++ .../java/com/seaofnodes/simple/node/FunNode.java | 8 + .../java/com/seaofnodes/simple/node/GuardMach.java | 17 ++ .../java/com/seaofnodes/simple/node/GuardNode.java | 66 ++++++++ .../java/com/seaofnodes/simple/node/IfNode.java | 4 +- .../java/com/seaofnodes/simple/node/LoadNode.java | 85 ++++++---- .../java/com/seaofnodes/simple/node/MemOpNode.java | 10 +- .../com/seaofnodes/simple/node/MemPhiNode.java | 5 +- .../com/seaofnodes/simple/node/MinusFNode.java | 1 + .../java/com/seaofnodes/simple/node/MinusNode.java | 66 ++++++-- .../java/com/seaofnodes/simple/node/ModeNode.java | 12 ++ .../java/com/seaofnodes/simple/node/MulFNode.java | 1 + .../java/com/seaofnodes/simple/node/MulNode.java | 15 +- .../java/com/seaofnodes/simple/node/NewNode.java | 10 +- src/main/java/com/seaofnodes/simple/node/Node.java | 14 +- .../java/com/seaofnodes/simple/node/OrNode.java | 5 +- .../java/com/seaofnodes/simple/node/ParmNode.java | 24 ++- .../java/com/seaofnodes/simple/node/PhiNode.java | 91 +++++----- .../com/seaofnodes/simple/node/PtrToIntMach.java | 17 ++ .../com/seaofnodes/simple/node/PtrToIntNode.java | 35 ++++ .../com/seaofnodes/simple/node/ReturnNode.java | 25 ++- .../java/com/seaofnodes/simple/node/SarNode.java | 6 +- .../java/com/seaofnodes/simple/node/ScopeNode.java | 31 ++-- .../java/com/seaofnodes/simple/node/ShlNode.java | 8 +- .../java/com/seaofnodes/simple/node/ShrNode.java | 5 +- .../java/com/seaofnodes/simple/node/StoreNode.java | 23 ++- .../com/seaofnodes/simple/node/StructNode.java | 11 +- .../java/com/seaofnodes/simple/node/SubFNode.java | 1 + .../java/com/seaofnodes/simple/node/SubNode.java | 13 +- .../java/com/seaofnodes/simple/node/XorNode.java | 6 +- .../seaofnodes/simple/node/cpus/arm/CallARM.java | 2 +- .../com/seaofnodes/simple/node/cpus/arm/arm.java | 2 +- .../simple/node/cpus/riscv/CallRISC.java | 2 +- .../simple/node/cpus/x86_64_v2/CallX86.java | 2 +- .../simple/node/cpus/x86_64_v2/x86_64_v2.java | 2 +- .../java/com/seaofnodes/simple/type/Field.java | 2 +- src/main/java/com/seaofnodes/simple/type/Type.java | 23 ++- .../com/seaofnodes/simple/type/TypeConAry.java | 2 +- .../java/com/seaofnodes/simple/type/TypeFloat.java | 2 +- .../com/seaofnodes/simple/type/TypeFunPtr.java | 17 +- .../com/seaofnodes/simple/type/TypeInteger.java | 2 +- .../java/com/seaofnodes/simple/type/TypeMem.java | 9 +- .../com/seaofnodes/simple/type/TypeMemPtr.java | 6 +- .../java/com/seaofnodes/simple/type/TypeNil.java | 2 +- .../com/seaofnodes/simple/type/TypeScalar.java | 31 ++++ .../com/seaofnodes/simple/type/TypeStruct.java | 27 ++- .../java/com/seaofnodes/simple/type/TypeTuple.java | 23 ++- 67 files changed, 1238 insertions(+), 359 deletions(-) create mode 100644 src/main/java/com/seaofnodes/simple/node/ConvertNode.java create mode 100644 src/main/java/com/seaofnodes/simple/node/FldOffNode.java create mode 100644 src/main/java/com/seaofnodes/simple/node/GuardMach.java create mode 100644 src/main/java/com/seaofnodes/simple/node/GuardNode.java create mode 100644 src/main/java/com/seaofnodes/simple/node/ModeNode.java create mode 100644 src/main/java/com/seaofnodes/simple/node/PtrToIntMach.java create mode 100644 src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java create mode 100644 src/main/java/com/seaofnodes/simple/type/TypeScalar.java (limited to 'src/main') diff --git a/src/main/java/com/seaofnodes/simple/IterPeeps.java b/src/main/java/com/seaofnodes/simple/IterPeeps.java index a3b66e09..2fe4e172 100644 --- a/src/main/java/com/seaofnodes/simple/IterPeeps.java +++ b/src/main/java/com/seaofnodes/simple/IterPeeps.java @@ -206,7 +206,7 @@ public class IterPeeps { * True if Node is on the WorkList */ public boolean on( E x ) { return _on.get(x._nid); } - boolean isEmpty() { return _len==0; } + public boolean isEmpty() { return _len==0; } /** * Removes a random Node from the WorkList; null if WorkList is empty diff --git a/src/main/java/com/seaofnodes/simple/Parser.java b/src/main/java/com/seaofnodes/simple/Parser.java index 81c7be32..56233250 100644 --- a/src/main/java/com/seaofnodes/simple/Parser.java +++ b/src/main/java/com/seaofnodes/simple/Parser.java @@ -228,7 +228,7 @@ public class Parser { RegionNode r = new RegionNode( null,null,null).init(); assert r.inProgress(); PhiNode rmem = new BulkMemPhiNode(ScopeNode.MEM0,r,null).init(); - PhiNode rrez = new PhiNode(ScopeNode.ARG0,Type.BOTTOM,r,null).init(); + PhiNode rrez = new PhiNode(ScopeNode.ARG0,r,null).init(); ReturnNode ret = new ReturnNode(r, rmem, rrez, rpc, fun).init(); fun.setRet(ret); assert ret.inProgress(); @@ -617,7 +617,7 @@ public class Parser { // Merge results RegionNode r = ctrl(tScope.mergeScopes(fScope,loc())); - Node ret = peep(new PhiNode("",lhs._type.meet(rhs._type).glb(false),r,lhs.unkeep(),rhs.unkeep())); + Node ret = peep(new PhiNode("",r,lhs.unkeep(),rhs.unkeep())); // Immediately fail e.g. `arg ? 7 : ptr` ParseException err; if( !fside.equals("else") && (err=ret.err()) !=null ) throw err; @@ -664,7 +664,8 @@ public class Parser { // Else safety check for accessing fields out of function scope. if( instance(def) ) { pos(old_pre_equals); // setup for `self.name = expr` - Node self = _scope.in(_scope.lookup("self")); // Insert self reference + Var selfVar = _scope.lookup("self"); + Node self = liftExpr(_scope.in(selfVar),selfVar.type(),true); return parsePostfixName(self,name); } @@ -690,33 +691,16 @@ public class Parser { // Apply declared access permissions; widen floats and narrow integers. // Early error if types do not match variable. - private Node liftExpr( Node expr, Type t, boolean isLoad ) { - if( expr._type instanceof TypeMemPtr tmp && TYPES.get(tmp._obj._name).isFRef() ) - throw error("Must define forward ref "+tmp._obj._name); - // Read-only access is deep; a fixed binding alone does not remove access. - if( t instanceof TypeMemPtr tmp && tmp.isFinal() ) { + private Node liftExpr(Node expr, Type t, boolean isLoad) { + if( t==Type.BOTTOM || t==Type.TOP ) return expr; + // Constructor sentinels record missing initialization, not a value + // needing conversion. Keep them visible to the completion check. + if( expr instanceof ConstantNode && !(expr instanceof FRefNode) && + (expr._type==Type.TOP || expr._type==Type.BOTTOM) ) return expr; + if( t instanceof TypeMemPtr tmp && tmp.isFinal() ) expr = peep(new ReadOnlyNode(expr)); - } - if( !expr._type.accessISA(t) ) - throw error("Type " + expr._type.str() + " is not of declared type " + t.str()); - // Auto-widen array to i64 (cast ptr to raw int bits) - if( t == TypeInteger.BOT && expr._type instanceof TypeMemPtr tmp && tmp._obj.isAry() ) - expr = peep(new AddNode(peep(new CastNode(t,ctrl(),expr)),off(tmp._obj,"[]"))); - // Auto-widen int to float - expr = widenInt( expr, t ); - // Auto-narrow wide ints to narrow ints. For loads, emit code to force - // the loaded value to match the declared sign/zero bits. For stores, - // just force the type, acting "as if" the store silently truncates. - if( expr._type instanceof TypeFloat && t instanceof TypeInteger ) - expr = peep(new ToIntegerNode(expr)); - Type et = expr._type; - if( isLoad ) { expr = zsMask(expr,t); et = expr._type; } - else if( et instanceof TypeInteger && t instanceof TypeInteger ) et=t; - - // Type is sane - if( et!=Type.BOTTOM && (!et.shallowISA(t) || !et.accessISA(t)) ) - expr = peep(new CastNode(t.isConstant() ? t : t.widen(),null,expr)); - return expr; + if( expr instanceof FunPtrNode && t instanceof TypeFunPtr ) return expr; + return peep(new ConvertNode(t,expr)); } private Node widenInt( Node expr, Type t ) { @@ -828,8 +812,6 @@ public class Parser { // Lift type to the declaration. This will report as an error later if // we cannot lift the type. - if( !lift._type.isa(t) ) - lift = peep(new CastNode(t.widen(),null,lift)); // Define a new name if( !_scope.define(name,t,xfinal || fld_final,lift, loc) ) throw error("Redefining name '" + name + "'", loc); @@ -1083,7 +1065,7 @@ public class Parser { else if( !match("!=") ) break; lhs.keep(); Node rhs = parseComparison(); - lhs = peep(new BoolNode.EQ(lhs.unkeep(),rhs).widen()); + lhs = peep(new BoolNode.EQ(lhs.unkeep(),rhs)); if( !eq ) lhs = peep(new NotNode(lhs)); } return lhs; @@ -1149,7 +1131,7 @@ public class Parser { ctrl(ifT.unkeep()); RegionNode r = fail.mergeScopes(_scope, loc()).init(); _scope = fail; - return new PhiNode("",TypeInteger.BOOL,r,con(0),con(1)).peephole(); + return new PhiNode("", r,con(0),con(1)).peephole(); } private int parseCompDir() { @@ -1164,7 +1146,7 @@ public class Parser { if( Math.abs(dir)==2 ) { Node tmp=lhs; lhs=rhs; rhs=tmp; } Node cmp = dir < 0 ? new BoolNode.LE(lhs,rhs) : new BoolNode.LT(lhs,rhs); - return peep(cmp.widen()); + return peep(cmp); } /** @@ -1187,7 +1169,7 @@ public class Parser { ParseException err; if( (err=lhs.err()) != null ) throw err; - lhs = peep(lhs.widen()); + lhs = peep(lhs); } return lhs; } @@ -1208,7 +1190,7 @@ public class Parser { else if( match("-") ) lhs = new SubNode(lhs,null); else break; lhs.setDef(2,parseMultiplication()); - lhs = peep(lhs.widen()); + lhs = peep(lhs); } return lhs; } @@ -1229,7 +1211,7 @@ public class Parser { else if( match("/") ) lhs = new DivNode(lhs,null); else break; lhs.setDef(2,parseUnary()); - lhs = peep(lhs.widen()); + lhs = peep(lhs); } return lhs; } @@ -1253,9 +1235,7 @@ public class Parser { if( n != null && !(n.type() instanceof TypeMemPtr) ) { if( n._final ) throw error("Cannot reassign final '"+n._name+"'"); - Node expr = n.type() instanceof TypeFloat - ? peep(new AddFNode(_scope.in(n),con(TypeFloat.constant(delta)))) - : zsMask(peep(new AddNode(_scope.in(n),con(delta))),n.type()); + Node expr = liftExpr(peep(new AddNode(_scope.in(n),con(delta))),n.type(),true); _scope.update(n,expr); return expr; } @@ -1263,7 +1243,7 @@ public class Parser { // Reset, try again pos(old); } - if (match("-")) return peep(new MinusNode(parseUnary()).widen()); + if (match("-")) return peep(new MinusNode(parseUnary())); if (match("!")) return peep(new NotNode(parseUnary())); // Else be a primary expression return parsePrimary(); @@ -1325,13 +1305,18 @@ public class Parser { // If a method var, allow nested blocks and methods, but not fcns or other structs. // Else safety check for accessing fields out of function scope. if( instance(var) ) { - Node self = _scope.in(_scope.lookup("self")); // Insert self reference + Var selfVar = _scope.lookup("self"); + Node self = liftExpr(_scope.in(selfVar),selfVar.type(),true); return parsePostfixName(self,id); } // Load local value - Node rvalue = _scope.in(var); - if( rvalue._type == Type.BOTTOM ) + Node oldValue = _scope.in(var); + if( oldValue instanceof ConstantNode && !(oldValue instanceof FRefNode) && oldValue._type==Type.BOTTOM ) + throw error("Cannot read uninitialized field '"+id+"'"); + Node rvalue = liftExpr(oldValue,var.type(),true); + if( rvalue!=oldValue ) _scope.update(var,rvalue); + if( rvalue instanceof ConstantNode && rvalue._type == Type.BOTTOM ) if( rvalue instanceof FRefNode ) return parsePostfix(rvalue); else throw error("Cannot read uninitialized field '"+id+"'"); @@ -1407,7 +1392,7 @@ public class Parser { }; // Convert to float ops, or narrow int types; error if not declared type. // Also, if postfix LHS is still keep() - return liftExpr(peep(op.widen()),t,true); + return liftExpr(peep(op),t,true); } @@ -1458,7 +1443,7 @@ public class Parser { } // Check that all fields are initialized for( int i=idx; i { P.parse(); + // Close the declarations as a group before replacing old forward + // shapes in the graph. A can mention B before B mentions A; both + // must use the same complete cycle, including through functions. + HashMap closed = new HashMap<>(); + for( var entry : Parser.TYPES.entrySet() ) + if( entry.getValue() instanceof TypeMemPtr ptr && !ptr._obj._open ) + closed.put(entry.getKey(),ptr.makeFrom(ptr._obj.close(Parser.TYPES))); + Parser.TYPES.putAll(closed); + _start.walk(n -> { n.upgradeType(Parser.TYPES); return null; }); }); } diff --git a/src/main/java/com/seaofnodes/simple/codegen/InstSelect.java b/src/main/java/com/seaofnodes/simple/codegen/InstSelect.java index e8e87785..5a91cf9f 100644 --- a/src/main/java/com/seaofnodes/simple/codegen/InstSelect.java +++ b/src/main/java/com/seaofnodes/simple/codegen/InstSelect.java @@ -65,7 +65,7 @@ class InstSelect { } // Produce a machine node from n; map it to flag as done so stops cycles. - map.put(n, x=_code._mach.instSelect(n) ); + map.put(n, x=select(n) ); // Stop retains the callable body. Its lifetime edge is not a machine operand. if( n instanceof FunPtrNode ) x._inputs.pop(); // Walk machine op and replace inputs with mapped inputs @@ -87,4 +87,25 @@ class InstSelect { } } + + private Node select(Node n) { + if( n instanceof GuardNode guard ) return new GuardMach(guard); + if( n instanceof PtrToIntNode ptr ) return new PtrToIntMach(ptr); + Node op = n; + if( n instanceof ModeNode mode && mode.mode()==2 ) { + // Adapt to the existing CPU selectors. As with machine expansion, + // copy inputs without adding uses to the old graph; _instOuts + // rebuilds reverse edges after selection has finished. + op = switch(n) { + case AddNode add -> new AddFNode(add); + case SubNode sub -> new SubFNode(sub); + case MulNode mul -> new MulFNode(mul); + case DivNode div -> new DivFNode(div); + case MinusNode neg -> new MinusFNode(neg); + default -> n; + }; + op._type = n._type; + } + return _code._mach.instSelect(op); + } } diff --git a/src/main/java/com/seaofnodes/simple/codegen/Machine.java b/src/main/java/com/seaofnodes/simple/codegen/Machine.java index a95faefc..7ca465ba 100644 --- a/src/main/java/com/seaofnodes/simple/codegen/Machine.java +++ b/src/main/java/com/seaofnodes/simple/codegen/Machine.java @@ -99,8 +99,9 @@ Pre-alloc Post-Alloc SP+48 RPC // Simple functions already return canonical full-width integer values. protected static TypeInteger cReturn(ProjNode prj) { if( prj._idx==2 && prj.in(0) instanceof CallEndNode cend && - cend.call().fptr() instanceof ExternNode ext && - ((TypeFunPtr)ext._con).ret() instanceof TypeInteger ret && ret.log_size()<3 ) + cend.call().fptr()._type instanceof TypeFunPtr tfp && + CodeGen.CODE.externFunc(tfp)!=null && + tfp.ret() instanceof TypeInteger ret && ret.log_size()<3 ) return ret; return null; } diff --git a/src/main/java/com/seaofnodes/simple/codegen/Opto.java b/src/main/java/com/seaofnodes/simple/codegen/Opto.java index 9f07c1d1..c78e732e 100644 --- a/src/main/java/com/seaofnodes/simple/codegen/Opto.java +++ b/src/main/java/com/seaofnodes/simple/codegen/Opto.java @@ -4,6 +4,7 @@ import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; import com.seaofnodes.simple.util.Ary; +import java.util.BitSet; // Optimistic Algorithm abstract public class Opto { @@ -50,6 +51,8 @@ abstract public class Opto { // OPTIMISTIC INTERPROCEDURAL SCCP sccp(code, oldTypes); + resolveNumericModes(code); + if( !code._iter._work.isEmpty() ) sccp(code, oldTypes); // After SCCP add everything changed to the worklist and see if // peepholes can make more progress. @@ -179,4 +182,63 @@ abstract public class Opto { }); } + // Resolve arithmetic modes which are ambiguous only because they belong to + // a recursive component. Float wins because integer operands can be + // converted to float. + private static void resolveNumericModes(CodeGen code) { + final BitSet visited = new BitSet(); + code._stop.walk(n -> { + if( n instanceof ModeNode modeNode && modeNode.mode()==0 ) { + visited.clear(); + visited.set(n._nid); // Inspect inputs, not Bool's integer result + int mode = 0; + for( int i=1; i { return modeNode.mode(); } + case ModeNode modeNode -> {} + case PhiNode phi -> {} + case ReturnNode ret -> {} + case ProjNode proj when proj.in(0) instanceof CallEndNode cend -> { + for( int i=1; i { return 0; } + } + for( int i=1; i { - Parser.ParseException msg = n.err(); - if( msg != null && code._obs != null ) code._obs.error(n, msg.getMessage()); - return msg; + Ary errs = new Ary<>(Node.class); + code._stop.walk(n -> { + if( n.err()!=null ) errs.add(n); + return null; }); - if( err != null ) - throw err; + if( errs.isEmpty() ) return; + + // A bad Load now produces BOTTOM. Report the bad access before a + // Convert or arithmetic use that merely received its failed result. + Node first = errs.at(0); + for( Node n : errs ) + if( noBottomInputs(n) ) { first=n; break; } + for( Node n : errs ) + if( noBottomInputs(n) && + ((n instanceof MemOpNode nm && first instanceof MemOpNode fm && + nm._loc!=null && nm._loc.before(fm._loc)) || + (n instanceof CFGNode nc && first instanceof CFGNode fc && nc.idepth()=2 && lhs.in(1) == lhs ) return null; // Do we have (x + con1) + con2? @@ -73,7 +83,7 @@ public class AddNode extends ArithNode { // because if it later became a constant then we could make this // transformation. if( addDep(lhs.in(2))._type.isConstant() && rhs._type.isConstant() ) - return new AddNode(lhs.in(1),new AddNode(lhs.in(2),rhs).peephole()); + return copy(lhs.in(1),copy(lhs.in(2),rhs).peephole()); // Do we have ((x + (phi cons)) + con) ? @@ -87,13 +97,13 @@ public class AddNode extends ArithNode { // Do we rotate (x + y) + z // into (x + z) + y ? if( spine_cmp(lhs.in(2),rhs,this) ) - return new AddNode(new AddNode(lhs.in(1),rhs).peephole(),lhs.in(2)); + return copy(copy(lhs.in(1),rhs).peephole(),lhs.in(2)); return super.idealize(); } // Rotation is only valid for associative ops, e.g. Add, Mul, And, Or, Xor. - // Do we have ((phi cons)|(x + (phi cons)) + con|(phi cons)) ? + // Do we have ((phi cons)|(x + (phi cons)) + con|(phi cons)|(op y con)) ? // Push constant up through the phi: x + (phi con0+con0 con1+con1...) static Node phiCon(Node op, boolean rotate) { Node lhs = op.in(1); @@ -126,7 +136,7 @@ public class AddNode extends ArithNode { for( int i=1; i hi._nid; } - @Override Node copy(Node lhs, Node rhs) { return new AddNode(lhs,rhs); } - @Override Node copyF() { return new AddFNode(null,null); } + @Override Node copy(Node lhs, Node rhs) { return new AddNode(lhs,rhs,_mode); } } diff --git a/src/main/java/com/seaofnodes/simple/node/AndNode.java b/src/main/java/com/seaofnodes/simple/node/AndNode.java index 9957472b..2bb289e9 100644 --- a/src/main/java/com/seaofnodes/simple/node/AndNode.java +++ b/src/main/java/com/seaofnodes/simple/node/AndNode.java @@ -5,9 +5,8 @@ import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; public class AndNode extends ArithNode { - public AndNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); } + public AndNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs, (byte)1); } - @Override public String label() { return "And"; } @Override public String op() { return "&"; } @Override long doOp( long x, long y ) { return x & y; } @Override TypeInteger doOp(TypeInteger x, TypeInteger y) { @@ -20,6 +19,7 @@ public class AndNode extends ArithNode { public Node idealize() { Node lhs = in(1); Node rhs = in(2); + Type t1 = lhs._type; Type t2 = rhs._type; if( !(t1 instanceof TypeInteger && t2 instanceof TypeInteger t2i) ) @@ -28,7 +28,7 @@ public class AndNode extends ArithNode { // And of -1. We do not check for (-1&x) because this will already // canonicalize to (x&-1). We do not check for zero, because // the compute() call will return a zero already. - if( t2i.isConstant() && t2i.value()==-1 ) + if( t2i.isConstant() && t2i.value()==-1 && forceInt(lhs) ) return lhs; // Move constants to RHS: con*arg becomes arg*con diff --git a/src/main/java/com/seaofnodes/simple/node/ArithFNode.java b/src/main/java/com/seaofnodes/simple/node/ArithFNode.java index dafba626..7d219215 100644 --- a/src/main/java/com/seaofnodes/simple/node/ArithFNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ArithFNode.java @@ -7,6 +7,7 @@ import com.seaofnodes.simple.type.TypeFloat; public abstract class ArithFNode extends Node { public ArithFNode(Node lhs, Node rhs) { super(null, lhs, rhs); } + protected ArithFNode(ArithNode n) { super(n); } @Override protected ExprPrinter _print1(ExprPrinter p) { return p.open().n(in(1)).p(glabel()).n(in(2)).close(); diff --git a/src/main/java/com/seaofnodes/simple/node/ArithNode.java b/src/main/java/com/seaofnodes/simple/node/ArithNode.java index 54290330..5f0862b2 100644 --- a/src/main/java/com/seaofnodes/simple/node/ArithNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ArithNode.java @@ -3,15 +3,22 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.Utils; +import java.util.HashMap; +import java.util.IdentityHashMap; -public abstract class ArithNode extends Node { +public abstract class ArithNode extends Node implements ModeNode { // Source location for late reported errors Parser.Lexer _loc; + // Mode is 0 for unknown, 1 for int, 2 for flt + byte _mode; public ArithNode( Parser.Lexer loc, Node lhs, Node rhs) { super(null, lhs, rhs); _loc = loc; } + public ArithNode( Parser.Lexer loc, Node lhs, Node rhs, byte mode) { this(loc,lhs,rhs); _mode=mode; } + abstract String op(); @Override protected ExprPrinter _print1(ExprPrinter p) { @@ -20,20 +27,107 @@ public abstract class ArithNode extends Node { abstract long doOp(long x, long y); abstract TypeInteger doOp(TypeInteger x, TypeInteger y); + double doOp(double x, double y) { throw Utils.TODO(); } + + @Override public byte mode() { return _mode; } + @Override public Node setMode(byte mode) { + assert _mode==0 && (mode==1 || mode==2); + unlock(); // _mode participates in GVN hash and equality + _mode = mode; + return this; + } + static byte mode( Type t1, Type t2 ) { + if( t1 instanceof TypeFloat || t2 instanceof TypeFloat ) return 2; + if( t1 instanceof TypeInteger && t2 instanceof TypeInteger ) return 1; + if( t1 instanceof TypeMemPtr && t2 instanceof TypeMemPtr ) return 1; + if( t1 instanceof TypeFunPtr && t2 instanceof TypeFunPtr ) return 1; + return 0; + } + + // Int-only ops, such as &, | and shifts, can collapse to one input. If + // that input, or the live arms of a Phi input, are still mode-polymorphic + // ops, settle them before returning the value so the replacement remains + // at least as narrow as the int-only op. + static boolean forceInt(Node n) { + if( n instanceof ModeNode mode && mode.mode()==0 ) { + mode.setMode((byte)1); + n.setType(n.compute()); + // Mode changes remove n from GVN even if the caller's remaining + // conditions prevent replacing the int-only operation by n. + CodeGen.CODE.add(n); + } + return true; + } + + // Check that a settled mode agrees with all data inputs. Takes the Node + // instead of a varargs list both to avoid allocation and to keep this ready + // to become shared mode-aware Node behavior later. + static Parser.ParseException nodeErr(Node n, Parser.Lexer loc, String op, byte mode, boolean allowFloat) { + Type bad = null; + for( int i=1; i mode==1 ? null : t; + case TypeMemPtr ti -> mode==1 ? null : t; + case TypeFunPtr ti -> mode==1 ? null : t; + case TypeFloat tf -> mode==2 ? null : t; + default -> t; + }; + if( bad != null ) break; + } + boolean unsupportedFloat = false; + if( !allowFloat ) + for( int i=1; i= 2 && !lhs.inProgress() && - lhs.region()==rhs.region() && !(lhs.region() instanceof FunNode) && - lhs.nIns()>2 && // A 1-input Phi will collapse already // Disallow with self-looping phi; these will collapse - (lhs.in(2)!=lhs && rhs.in(2)!=rhs) ) { - // Profit check: only 1 instance of `this` will remain, all the - // others will fold to constants. - int cnt=0; - for( int i=1; i 2 && lhs.in(2)!=lhs && rhs.nIns() > 2 && rhs.in(2)!=rhs) ) { + Node r = lhs.region(); + if( r==rhs.region() && !(r instanceof FunNode) && + // For loops, the backedge *must* fold away, lest we unroll forever + (!(r instanceof LoopNode) || isCon(lhs,rhs,2)) ) { + // Profit check: only 1 instance of `this` will remain, all the + // others will fold to constants. For loops, *all* must fold away + int cnt=0; + for( int i=1; i _print1(ExprPrinter p) { return p.open().n(in(1)).p(op()).n(in(2)).close(); @@ -26,15 +29,25 @@ abstract public class BoolNode extends Node { public TypeInteger compute() { Type t1 = in(1)._type; Type t2 = in(2)._type; + if( t1==null || t2==null ) + return BOOL; // Exactly equals? if( t1.isHigh() || t2.isHigh() ) return (TypeInteger)BOOL.dual(); if( in(1)==in(2) ) // LT fails, both EQ and LE succeed return this instanceof LT ? FALSE : TRUE; - if( t1 instanceof TypeInteger i1 && - t2 instanceof TypeInteger i2 ) - return doOp(i1,i2); + byte mode = _mode==0 ? ArithNode.mode(t1,t2) : _mode; + if( mode==0 ) + return BOOL; + // Handle integer compares + if( mode==1 ) { + if( t1 instanceof TypeInteger i1 && + t2 instanceof TypeInteger i2 ) + return doOp(i1,i2); + return BOOL; + } + // Floating compare. Mixed inputs are converted in idealize(). if( t1 instanceof TypeFloat f1 && t2 instanceof TypeFloat f2 && f1.isConstant() && f2.isConstant() ) @@ -44,22 +57,41 @@ abstract public class BoolNode extends Node { TypeInteger doOp(TypeInteger t1, TypeInteger t2) { throw Utils.TODO(); } boolean doOp(double lhs, double rhs) { throw Utils.TODO(); } - Node copyF(Node lhs, Node rhs) { return null; } - public boolean isFloat() { return false; } + public boolean isFloat() { assert _mode!=0; return _mode==2; } + @Override public byte mode() { return _mode; } + @Override public Node setMode(byte mode) { + assert _mode==0 && (mode==1 || mode==2); + unlock(); // _mode participates in GVN hash and equality + _mode = mode; + return this; + } @Override public Node idealize() { // Compare of same if( in(1)==in(2) ) - return this instanceof LT ? Parser.ZERO : new ConstantNode(TRUE); + return this instanceof LT ? com.seaofnodes.simple.Parser.ZERO : ConstantNode.make(TRUE); + // Can we decide int vs flt? + Type t1 = in(1)._type, t2 = in(2)._type; + if( t1==null || t2==null ) + return null; + if( _mode==0 ) { + byte mode = ArithNode.mode(t1,t2); + if( mode!=0 ) return setMode(mode).init(); + } + if( _mode==2 ) { + if( t1 instanceof TypeInteger ) return copy(new ToFloatNode(in(1)).peephole(),in(2)); + if( t2 instanceof TypeInteger ) return copy(in(1),new ToFloatNode(in(2)).peephole()); + } + // Equals pushes constant to the right; 5==X becomes X==5. if( this instanceof EQ ) { if( !(in(2) instanceof ConstantNode) ) { // con==noncon becomes noncon==con if( in(1) instanceof ConstantNode || in(1)._nid > in(2)._nid ) - // Equals sorts by NID otherwise: non.high == non.low becomes non.low == non.high - return in(1)._type instanceof TypeFloat ? new EQF(in(2),in(1)) : new EQ(in(2),in(1)); + // Equals sorts by NID otherwise: non.high == non.low becomes non.low == non.high + return new EQ(in(2),in(1)); } // Equals X==0 becomes a !X if( (in(2)._type == ZERO || in(2)._type == Type.NIL) ) @@ -75,82 +107,96 @@ abstract public class BoolNode extends Node { return null; } + @Override public boolean eq( Node n ) { return _mode==((BoolNode)n)._mode; } + @Override public int hash() { return _mode; } + @Override public Parser.ParseException err() { + return ArithNode.nodeErr(this,null,op(),_mode,!(this instanceof ULT)); + } + public static class EQ extends BoolNode { public EQ(Node lhs, Node rhs) { super(lhs,rhs); } - public String op() { return "=="; } + public EQ(Node lhs, Node rhs, byte mode) { super(lhs,rhs, mode); } + public String op() { return "=="; } TypeInteger doOp(TypeInteger i1, TypeInteger i2) { if( i1==i2 && i1.isConstant() ) return TRUE; if( i1._max < i2._min || i1._min > i2._max ) return FALSE; return BOOL; } - Node copy(Node lhs, Node rhs) { return new EQ(lhs,rhs); } - Node copyF() { return new EQF(null,null); } + @Override boolean doOp(double lhs, double rhs) { return lhs==rhs; } + Node copy(Node lhs, Node rhs) { return new EQ(lhs,rhs,_mode); } } public static class NE extends BoolNode { public NE(Node lhs, Node rhs) { super(lhs,rhs); } - public String op() { return "!="; } + public NE(Node lhs, Node rhs, byte mode) { super(lhs,rhs,mode); } + public String op() { return "!="; } TypeInteger doOp(TypeInteger i1, TypeInteger i2) { if( i1==i2 && i1.isConstant() ) return TRUE; if( i1._max < i2._min || i1._min > i2._max ) return FALSE; return BOOL; } - Node copy(Node lhs, Node rhs) { return new NE(lhs,rhs); } - Node copyF() { throw Utils.TODO(); } + @Override boolean doOp(double lhs, double rhs) { return lhs!=rhs; } + Node copy(Node lhs, Node rhs) { return new NE(lhs,rhs,_mode); } } public static class LT extends BoolNode { public LT(Node lhs, Node rhs) { super(lhs,rhs); } - public String op() { return "<" ; } + public LT(Node lhs, Node rhs, byte mode) { super(lhs,rhs, mode); } + public String op() { return "<" ; } TypeInteger doOp(TypeInteger i1, TypeInteger i2) { if( i1._max < i2._min ) return TRUE; if( i1._min >= i2._max ) return FALSE; return BOOL; } - Node copy(Node lhs, Node rhs) { return new LT(lhs,rhs); } - Node copyF() { return new LTF(null,null); } + @Override boolean doOp(double lhs, double rhs) { return lhs= 0 ) return FALSE; + return BOOL; + } + Node copy(Node lhs, Node rhs) { return new ULT(lhs,rhs); } + } public static class EQF extends EQ { - public EQF(Node lhs, Node rhs) { super(lhs,rhs); } + public EQF(Node lhs, Node rhs) { super(lhs,rhs,(byte)2); } @Override boolean doOp(double lhs, double rhs) { return lhs == rhs; } @Override public boolean isFloat() { return true; } @Override Node copy(Node lhs, Node rhs) { return new EQF(lhs,rhs); } } public static class LTF extends LT { - public LTF(Node lhs, Node rhs) { super(lhs,rhs); } + public LTF(Node lhs, Node rhs) { super(lhs,rhs,(byte)2); } @Override boolean doOp(double lhs, double rhs) { return lhs < rhs; } @Override public boolean isFloat() { return true; } @Override Node copy(Node lhs, Node rhs) { return new LTF(lhs,rhs); } } public static class LEF extends LE { - public LEF(Node lhs, Node rhs) { super(lhs,rhs); } + public LEF(Node lhs, Node rhs) { super(lhs,rhs,(byte)2); } @Override boolean doOp(double lhs, double rhs) { return lhs <= rhs; } @Override public boolean isFloat() { return true; } @Override Node copy(Node lhs, Node rhs) { return new LEF(lhs,rhs); } } - // Unsigned less that, for range checks. Not directly user writable. - public static class ULT extends BoolNode { - public ULT(Node lhs, Node rhs) { super(lhs,rhs); } - public String op() { return "u<" ; } - TypeInteger doOp(TypeInteger i1, TypeInteger i2) { - if( Long.compareUnsigned(i1._max,i2._min) < 0 ) return TRUE; - if( Long.compareUnsigned(i1._min,i2._max) >= 0 ) return FALSE; - return BOOL; - } - Node copy(Node lhs, Node rhs) { return new ULT(lhs,rhs); } - } } diff --git a/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java index 7d4f1fb8..8399ed09 100644 --- a/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java +++ b/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java @@ -23,7 +23,7 @@ public class BulkMemPhiNode extends PhiNode { } private BulkMemPhiNode(String label, BitSet aliases, Node... inputs) { - super(label,TypeMem.BOT,inputs); + super(label,inputs); _aliases = aliases; } @@ -31,6 +31,7 @@ public class BulkMemPhiNode extends PhiNode { public BulkMemPhiNode(BulkMemPhiNode phi) { super(phi); _aliases = phi._aliases; } + @Override public boolean isMem() { return true; } @Override public String label() { return "BulkPhi"+_aliases; } @Override diff --git a/src/main/java/com/seaofnodes/simple/node/CallNode.java b/src/main/java/com/seaofnodes/simple/node/CallNode.java index e63b3f62..b2708531 100644 --- a/src/main/java/com/seaofnodes/simple/node/CallNode.java +++ b/src/main/java/com/seaofnodes/simple/node/CallNode.java @@ -35,7 +35,7 @@ public class CallNode extends CFGNode { if( fptr()._type instanceof TypeFunPtr tfp && tfp._isConstant() && tfp.notNull() ) { FunNode fun = CodeGen.CODE._link(tfp); if( fun !=null ) return fun._name; - if( fptr() instanceof ExternNode ex ) return ex._extern; + return CodeGen.CODE.externFunc(tfp); } return null; } diff --git a/src/main/java/com/seaofnodes/simple/node/CastNode.java b/src/main/java/com/seaofnodes/simple/node/CastNode.java index 4e93f3e6..fa7c207d 100644 --- a/src/main/java/com/seaofnodes/simple/node/CastNode.java +++ b/src/main/java/com/seaofnodes/simple/node/CastNode.java @@ -26,6 +26,14 @@ public class CastNode extends Node { @Override public String label() { return "("+_t.str()+")"; } + @Override boolean _upgradeType(java.util.HashMap types) { + Type t = _t.upgradeType(types); + if( t==_t ) return false; + unlock(); + _t = t; + return true; + } + @Override public boolean isConst() { return true; } diff --git a/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/src/main/java/com/seaofnodes/simple/node/ConstantNode.java index 7c330175..8d783be7 100644 --- a/src/main/java/com/seaofnodes/simple/node/ConstantNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ConstantNode.java @@ -18,7 +18,7 @@ import com.seaofnodes.simple.util.SB; */ public class ConstantNode extends Node { - public final Type _con; + public Type _con; public ConstantNode( Type type ) { super(new Node[]{CodeGen.CODE._start}); _con = _type = type; @@ -56,4 +56,12 @@ public class ConstantNode extends Node { @Override public Node idealize() { return null; } @Override public boolean eq(Node n) { return _con==((ConstantNode)n)._con; } @Override int hash() { return _con.hashCode(); } + + @Override boolean _upgradeType(java.util.HashMap types) { + Type next = _con.upgradeType(types); + if( next==_con ) return false; + unlock(); + _con=next; + return true; + } } diff --git a/src/main/java/com/seaofnodes/simple/node/ConvertNode.java b/src/main/java/com/seaofnodes/simple/node/ConvertNode.java new file mode 100644 index 00000000..c5be0123 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/ConvertNode.java @@ -0,0 +1,103 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.type.*; + +import java.util.HashMap; + +// Convert a value to an authoritative destination type. The destination is +// known structurally from a declaration; the source family might sharpen later. +public class ConvertNode extends Node { + private Type _dst; + + public ConvertNode(Type dst, Node val) { + super(null,val); + // Folding this conversion would replace the Return-linked FunPtrNode + // with an unhooked ConstantNode. + assert !(dst instanceof TypeFunPtr && val instanceof FunPtrNode); + _dst = dst; + } + + public Type dst() { return _dst; } + public Node val() { return in(1); } + + @Override public String label() { return "Convert_"+_dst.str(); } + @Override protected ExprPrinter _print1(ExprPrinter p) { + return p.p(label()).open().n(val()).close(); + } + + // The declaration fixes the result family during pessimistic parsing. + // From SCCP onward, preserve dead inputs and stronger facts in that family. + @Override public Type compute() { + Type src = val()._type; + if( CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Opto.ordinal() ) { + if( src.isHigh() ) return Type.TOP; + if( (src.isa(_dst) && src.accessISA(_dst)) ) return src; + } + return _dst; + } + + @Override public Node idealize() { + Type src = val()._type; + if( src==Type.BOTTOM || src==Type.TOP ) return null; + if( (src.isa(_dst) && src.accessISA(_dst)) ) return val(); + + // Managed arrays are represented by an object pointer, while C wants + // the address of the first element. The representation cast itself is + // zero-code; the Add skips the array header. + if( _dst==TypeInteger.BOT && src instanceof TypeMemPtr tmp && tmp._obj.isAry() ) + return new AddNode(new PtrToIntNode(val()).peephole(),Parser.off(tmp._obj,"[]")); + + // Integer (and nil-as-zero) to floating point. + if( (src instanceof TypeInteger || src==Type.NIL) && _dst instanceof TypeFloat ) + return new ToFloatNode(val()); + + // Convert to i64 first, then apply the destination's integer width. + if( src instanceof TypeFloat && _dst instanceof TypeInteger ) + return new ConvertNode(_dst,new ToIntegerNode(val()).peephole()); + + // Narrow integers produce the declared sign/zero extension. + if( src instanceof TypeInteger && _dst instanceof TypeInteger dst ) { + if( dst._min==0 ) + return new AndNode(null,val(),con(dst._max)); + int shift = Long.numberOfLeadingZeros(dst._max)-1; + Node shf = con(shift); + if( shf._type==TypeInteger.ZERO ) return val(); + return new SarNode(null,new ShlNode(null,val(),shf.keep()).peephole(),shf.unkeep()); + } + + // Narrow f64 to f32. + if( src instanceof TypeFloat && _dst instanceof TypeFloat ) + return new RoundF32Node(val()); + + // Wait for more source information, or report an error after Opto. + return null; + } + + + + @Override public Parser.ParseException err() { + if( val()._type==Type.BOTTOM || + val()._type instanceof TypeNil src && src.nullable() && _dst instanceof TypeNil dst && dst.notNull() ) { + Node out = out(0); + if( out instanceof PhiNode phi ) out = phi.out(0); + if( out instanceof StoreNode st ) + return Parser.error( "'" + ((TypeMemPtr)st.ptr()._type)._obj._name + "' is not fully initialized, field '" + st._name + "' is only partially set in the constructor", null ); + } + return Parser.error("Type "+val()._type.str()+" is not of declared type "+_dst.str(),null); + } + + @Override public boolean eq(Node n) { return _dst==((ConvertNode)n)._dst; } + @Override int hash() { return _dst.hashCode(); } + + @Override boolean _upgradeType(java.util.HashMap types) { + Type next = _dst.upgradeType(types); + if( next==_dst ) return false; + unlock(); + _dst=next; + return true; + } +} diff --git a/src/main/java/com/seaofnodes/simple/node/DivFNode.java b/src/main/java/com/seaofnodes/simple/node/DivFNode.java index 0d037a30..df1dae4e 100644 --- a/src/main/java/com/seaofnodes/simple/node/DivFNode.java +++ b/src/main/java/com/seaofnodes/simple/node/DivFNode.java @@ -7,6 +7,7 @@ import java.util.BitSet; public class DivFNode extends ArithFNode { public DivFNode(Node lhs, Node rhs) { super(lhs, rhs); } + public DivFNode(ArithNode n) { super(n); } @Override public String label() { return "DivF"; } diff --git a/src/main/java/com/seaofnodes/simple/node/DivNode.java b/src/main/java/com/seaofnodes/simple/node/DivNode.java index 7b5ae16c..d973ffa7 100644 --- a/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -2,15 +2,18 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.type.TypeFloat; import java.util.BitSet; public class DivNode extends ArithNode { + @Override boolean allowFloat() { return true; } public DivNode(Node lhs, Node rhs) { super(null, lhs, rhs); } + public DivNode(Node lhs, Node rhs, byte mode) { super(null, lhs, rhs, mode); } - @Override public String label() { return "Div"; } - @Override public String op() { return "//"; } + @Override public String op() { return _mode==1 ? "//" : "/"; } - @Override long doOp( long x, long y ) { return y==0 ? 0 : x / y; } + @Override long doOp( long x, long y ) { return y==0 ? 0 : x / y; } + @Override double doOp(double x, double y ) { return x / y; } @Override TypeInteger doOp(TypeInteger x, TypeInteger y) { return TypeInteger.BOT; } @@ -18,11 +21,19 @@ public class DivNode extends ArithNode { @Override public Node idealize() { // Div of 1. - if( in(2)._type == TypeInteger.TRUE && in(1)._type instanceof TypeInteger ) + if( in(2)._type == TypeInteger.TRUE && in(1)._type.isa(compute()) ) return in(1); + + // Div of constant + if( _mode==2 ) { + if( in(2)._type == TypeFloat.constant(1.) && in(1)._type.isa(compute()) ) + return in(1); + if( in(2)._type instanceof TypeFloat f && f.isConstant() ) + return new MulNode(in(1),ConstantNode.make(TypeFloat.constant(1.0/f.value())).peephole(),_mode); + } + return super.idealize(); } - @Override Node copy(Node lhs, Node rhs) { return new DivNode(lhs,rhs); } - @Override Node copyF() { return new DivFNode(null,null); } + @Override Node copy(Node lhs, Node rhs) { return new DivNode(lhs,rhs,_mode); } } diff --git a/src/main/java/com/seaofnodes/simple/node/FldOffNode.java b/src/main/java/com/seaofnodes/simple/node/FldOffNode.java new file mode 100644 index 00000000..7605d74f --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/FldOffNode.java @@ -0,0 +1,24 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.type.*; + +/** A field name whose receiver's shape is not known while parsing. */ +public class FldOffNode extends Node { + public final String _name; + public FldOffNode(Node ptr, String name) { super(null,ptr); _name=name; } + @Override public String label() { return "#"+_name; } + @Override public Type compute() { return TypeInteger.BOT; } + @Override public Node idealize() { + if( in(1)._type instanceof TypeMemPtr ptr && !ptr._obj._open ) { + if( _name.equals("<<") && ptr._obj.isAry() ) return con(ptr._obj.aryScale()); + if( ptr._obj.field(_name)!=null ) return new ConFldOffNode(ptr._obj._name,_name); + } + return null; + } + @Override public Parser.ParseException err() { + return Parser.error("Cannot resolve field '"+_name+"'",null); + } + @Override public boolean eq(Node n) { return _name.equals(((FldOffNode)n)._name); } + @Override int hash() { return _name.hashCode(); } +} diff --git a/src/main/java/com/seaofnodes/simple/node/FunNode.java b/src/main/java/com/seaofnodes/simple/node/FunNode.java index 70e1c200..1ba57e85 100644 --- a/src/main/java/com/seaofnodes/simple/node/FunNode.java +++ b/src/main/java/com/seaofnodes/simple/node/FunNode.java @@ -254,4 +254,12 @@ public class FunNode extends RegionNode { return slotRotate*8; } + + @Override boolean _upgradeType(java.util.HashMap types) { + TypeFunPtr next = (TypeFunPtr)_sig.upgradeType(types); + if( next==_sig ) return false; + unlock(); + _sig=next; + return true; + } } diff --git a/src/main/java/com/seaofnodes/simple/node/GuardMach.java b/src/main/java/com/seaofnodes/simple/node/GuardMach.java new file mode 100644 index 00000000..202b1ab8 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/GuardMach.java @@ -0,0 +1,17 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.Encoding; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.util.SB; + +public class GuardMach extends GuardNode implements MachNode { + public GuardMach(GuardNode guard) { super(guard); } + @Override public String op() { return label(); } + @Override public RegMask regmap(int i) { assert i==1; return RegMask.FULL; } + @Override public RegMask outregmap() { return RegMask.FULL; } + @Override public int twoAddress() { return in(1)._type==Type.BOTTOM ? 0 : 1; } + @Override public void encoding(Encoding enc) { } + @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(in(1))); } +} diff --git a/src/main/java/com/seaofnodes/simple/node/GuardNode.java b/src/main/java/com/seaofnodes/simple/node/GuardNode.java new file mode 100644 index 00000000..f808c276 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/node/GuardNode.java @@ -0,0 +1,66 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.print.ExprPrinter; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeScalar; +import com.seaofnodes.simple.util.Utils; + +import java.util.HashMap; +import java.util.IdentityHashMap; + +// A branch-proven refinement to zero/null or non-zero/non-null. The value +// family is deliberately taken from the input when it becomes known. +public class GuardNode extends Node { + public final boolean _nonZero; + + public GuardNode(boolean nonZero, Node ctrl, Node val) { + super(ctrl,val); + _nonZero = nonZero; + } + + public GuardNode(GuardNode guard) { + super(guard); + _nonZero = guard._nonZero; + } + + + + @Override public String label() { return _nonZero ? "(!=0)" : "(==0)"; } + + @Override protected ExprPrinter _print1(ExprPrinter p) { + return p.p(label()).n(in(1)); + } + + @Override public Type compute() { + // Guard is a pinned, path-local fact. If its control path is not + // executable, the fact is not executable either, regardless of how the + // value input sharpens later. + Type c = in(0)._type; + if( c != Type.CONTROL && c != Type.BOTTOM ) + return Type.TOP; + Type t = in(1)._type; + if( t==Type.BOTTOM || t==Type.TOP ) return t; + // The scalar envelope does not identify a value family. Preserve it + // until SCCP learns whether this is an int, float, pointer, or fptr; + // choosing generic ptr here can be weaker than a later concrete type. + if( t==TypeScalar.BOT || t==TypeScalar.TOP ) return t; + Type t2 = _nonZero ? t.nonZero() : t.makeZero(); + if( t2!=null ) t2 = t.join(t2); + return t2==null ? (_type==null ? Type.TOP : _type) : t2; // No sane answer for e.g. not-zero of a zero + } + + @Override public Node idealize() { + if( in(0)._type != Type.CONTROL && in(0)._type != Type.BOTTOM ) + return null; + if( in(1)._type==Type.BOTTOM || in(1)._type==Type.TOP ) return null; + Type t = in(1)._type; + if( _nonZero ? !t.makeZero().isa(t) : t==t.makeZero() ) return in(1); + if( _type.isHighOrConst() && in(1)._type.isa(_type) ) + return in(1); + return null; + } + + @Override public boolean eq(Node n) { return _nonZero==((GuardNode)n)._nonZero; } + @Override int hash() { return _nonZero ? 1 : 0; } +} diff --git a/src/main/java/com/seaofnodes/simple/node/IfNode.java b/src/main/java/com/seaofnodes/simple/node/IfNode.java index c96993e5..3f21417e 100644 --- a/src/main/java/com/seaofnodes/simple/node/IfNode.java +++ b/src/main/java/com/seaofnodes/simple/node/IfNode.java @@ -60,7 +60,9 @@ public class IfNode extends CFGNode implements MultiNode { // test on either the true or false branch, that side wins. if( !pred()._type.isHighOrConst() ) for( CFGNode dom = idom(), prior=this; dom!=null; prior = dom, dom = dom.idom() ) { - if( addDep(dom) instanceof IfNode iff && addDep(iff.pred())==pred() && prior instanceof CProjNode prj ) { + if( addDep(dom) instanceof IfNode iff && prior instanceof CProjNode prj && + (addDep(iff.pred())==pred() || + (pred() instanceof GuardNode guard && guard._nonZero && iff.pred()==guard.in(1))) ) { setDef(1,con( prj._idx==0 ? 1 : 0 )); return this; } diff --git a/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/src/main/java/com/seaofnodes/simple/node/LoadNode.java index e057ef1c..24a2f9b8 100644 --- a/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -22,8 +22,10 @@ public class LoadNode extends MemOpNode { * @param ptr The ptr to the struct base from where we load a field * @param off The offset inside the struct base */ - public LoadNode(Parser.Lexer loc, String name, int alias, Type glb, Node mem, Node ptr, Node off) { - super(loc, name, alias, true, glb, mem, ptr, off); + public LoadNode(Parser.Lexer loc, String name, int alias, Node mem, Node ptr, Node off) { + // The field declaration will supply storage width once the receiver + // is known. A parser guess must not bound the value of this Load. + super(loc, name, alias, true, Type.BOTTOM, mem, ptr, off); } @Override boolean canDrop(MemOpNode other, Node dep) { @@ -52,37 +54,40 @@ public class LoadNode extends MemOpNode { @Override public Type compute() { - Type tmem = mem()._type; - if( !(tmem instanceof TypeMem mem) ) - return tmem; // No memory yet? Assume TOP/BOT - assert !_declaredType.isFRef(); - // No lifting if ptr might null-check - Type tptr = ptr()._type; - if( err() != null ) - return _declaredType; - if( !(tptr instanceof TypeMemPtr tmp) ) - return tptr; // No pointer yet? Assume TOP/BOT - // Load field from object - Field f = tmp._obj.field(_name); - // No field? Open objects might yet get the field when falling; - // closed objects with missing field are an error. - if( f == null ) - return tmp.isHigh() ? Type.TOP : _declaredType; - Type t = f._t; - // Load member of constant array - if( t instanceof TypeConAry ary ) - t = ary.elem(); // TODO: if offset is known, can peek the constant - // Lift from declared type and memory input - t = t.join(MemMergeNode.contents(mem(),_alias,this)); - if( ptr()._type.isFinal() || _declaredType.isFinal() ) - t = t.makeRO(); // Deep final applied - // Pinch between declared type - t = t.join(_declaredType); - return t; + Type p = ptr()._type; + Type m = mem()._type; + if( p.isHigh() || m.isHigh() ) return TypeScalar.TOP; + if( p==Type.NIL || p==Type.BOTTOM ) return Type.BOTTOM; + if( !(p instanceof TypeMemPtr ptr) ) return TypeScalar.BOT; + if( ptr.nullable() ) return Type.BOTTOM; + Field field = ptr._obj.field(_name); + if( field==null ) return TypeScalar.BOT; + Type t = field._t; + if( t instanceof TypeConAry ary ) t=ary.elem(); + if( _alias!=1 ) t=t.join(MemMergeNode.contents(mem(),_alias,this)); + if( ptr.isFinal() ) t=t.makeRO(); + return t==Type.TOP ? TypeScalar.TOP : t==Type.BOTTOM ? TypeScalar.BOT : t; } @Override public Node idealize() { + if( ptr()._type instanceof TypeMemPtr ptr && !ptr._obj._open ) { + Field field = ptr._obj.field(_name); + // Storage width comes from the declaration, not from a constant + // or range inferred for this particular object's current value. + Type declared = Parser.TYPES.get(ptr._obj._name); + if( declared instanceof TypeMemPtr shape && shape._obj.field(_name)!=null ) + field=shape._obj.field(_name); + Type storage = field==null ? Type.BOTTOM : field._t instanceof TypeConAry ary ? ary.elem() : field._t; + if( field!=null && (_alias!=field._alias || _declaredType!=storage) ) { + unlock(); + _alias=field._alias; + _declaredType=storage; + return this; + } + } + if( _alias==1 ) return null; + if( mem() instanceof CastNode cast ) { setDef(1,cast.in(1)); return this; @@ -128,7 +133,7 @@ public class LoadNode extends MemOpNode { !(ptr instanceof PhiNode p && p.region()==memphi.region()) ? memphi : null; if( find(memphi.in(2),ptr,stop,new BitSet())!=null || (!(memphi.region() instanceof LoopNode) && find(memphi.in(1),ptr,null,new BitSet())!=null) ) { - PhiNode phi = new PhiNode(_name,_declaredType,memphi.region()).keep(); + PhiNode phi = new PhiNode(_name,memphi.region()).keep(); phi.setType(_type); // Backedge searches may return this value Phi. for( int i=1; i types) { + Type next = _declaredType.upgradeType(types); + if( next==_declaredType ) return false; + unlock(); + _declaredType=next; + return true; + } } diff --git a/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java index 787333d9..ba53cfd8 100644 --- a/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java +++ b/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java @@ -9,20 +9,21 @@ public class MemPhiNode extends PhiNode { public final int _alias; public MemPhiNode(String label, int alias, Node... inputs) { - super(label,TypeMem.make(alias,Type.BOTTOM),inputs); + super(label,inputs); assert alias > 1; _alias = alias; } public MemPhiNode(MemPhiNode phi) { super(phi); _alias = phi._alias; } + @Override public boolean isMem() { return true; } @Override public String label() { return "MemPhi_"+_alias; } @Override public Type compute() { assert BulkMemPhiNode.checkMem(this,_alias); if( !(region() instanceof RegionNode r) ) return (region()._type==Type.XCONTROL || region()._type==Type.TOP) ? TypeMem.TOP : _type; - if( r.inProgress() ) return r instanceof LoopNode ? _minType : TypeMem.BOT; + if( r.inProgress() ) return r instanceof LoopNode ? TypeMem.make(_alias,Type.BOTTOM) : TypeMem.BOT; Type t = Type.TOP; for( int i=1; i types) { + TypeMemPtr ptr = (TypeMemPtr)_ptr.upgradeType(types); + if( ptr==_ptr ) return false; + unlock(); + _ptr = ptr; + return true; + } + public Field field(int alias) { for( Field f : _ptr._obj._fields ) if( !f._one && f._alias==alias ) return f; diff --git a/src/main/java/com/seaofnodes/simple/node/Node.java b/src/main/java/com/seaofnodes/simple/node/Node.java index 414530b4..230593a8 100644 --- a/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/src/main/java/com/seaofnodes/simple/node/Node.java @@ -71,7 +71,7 @@ public abstract class Node extends BaseNode implements Cloneable { } // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ" - public abstract String label(); + public String label() { return getClass().getSimpleName().replace("Node",""); } public String p(int depth) { return IRPrinter.prettyPrint(this,depth); } @@ -690,4 +690,16 @@ public abstract class Node extends BaseNode implements Cloneable { for( Node use : _outputs ) if( use != null && (x = use._find(nid,bs)) != null ) return x; return null; } + + boolean _upgradeType(java.util.HashMap types) { return false; } + public final void upgradeType(java.util.HashMap types) { + Type old = _type; + _type = _type.upgradeType(types); + boolean progress = _upgradeType(types); + if( progress || old!=_type ) { + CODE.add(this); + CODE.addAll(_outputs); + moveDepsToWorklist(); + } + } } diff --git a/src/main/java/com/seaofnodes/simple/node/OrNode.java b/src/main/java/com/seaofnodes/simple/node/OrNode.java index d16dd194..618c5340 100644 --- a/src/main/java/com/seaofnodes/simple/node/OrNode.java +++ b/src/main/java/com/seaofnodes/simple/node/OrNode.java @@ -5,7 +5,7 @@ import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; public class OrNode extends ArithNode { - public OrNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); } + public OrNode( Parser.Lexer loc, Node lhs, Node rhs) { super(loc,lhs, rhs, (byte)1); } @Override public String label() { return "Or"; } @Override public String op() { return "|"; } @@ -18,12 +18,13 @@ public class OrNode extends ArithNode { public Node idealize() { Node lhs = in(1); Node rhs = in(2); + Type t1 = lhs._type; Type t2 = rhs._type; // Or of 0. We do not check for (0|x) because this will already // canonicalize to (x|0) - if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && lhs._type instanceof TypeInteger ) + if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && t1.isa(TypeInteger.BOT) && forceInt(lhs) ) return lhs; // Move constants to RHS: con*arg becomes arg*con diff --git a/src/main/java/com/seaofnodes/simple/node/ParmNode.java b/src/main/java/com/seaofnodes/simple/node/ParmNode.java index cc654abb..52fe5704 100644 --- a/src/main/java/com/seaofnodes/simple/node/ParmNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ParmNode.java @@ -10,13 +10,15 @@ import com.seaofnodes.simple.type.TypeMemPtr; public class ParmNode extends PhiNode { // Argument indices are mapped one-to-one on CallNode inputs + private Type _declaredType; public final int _idx; // Argument index public ParmNode(String label, int idx, Type declaredType, Node... inputs) { - super(label,declaredType,inputs); + super(label,inputs); _idx = idx; + _declaredType = declaredType; } - public ParmNode(ParmNode parm) { super(parm, parm._label, parm._minType ); _idx = parm._idx; } + public ParmNode(ParmNode parm) { super(parm, parm._label, parm._declaredType ); _idx = parm._idx; _declaredType = parm._declaredType; } @Override public String label() { return MemOpNode.mlabel(_label); } @@ -37,7 +39,7 @@ public class ParmNode extends PhiNode { @Override public Type compute() { - Type t = super.compute(); + Type t = inProgress() ? _declaredType : super.compute().join(_declaredType); // Recursion is a loop in disguise. A Parm merges the first argument // with those from recursive calls, just as a loop Phi merges its entry // and backedge. Use the widening rule in PhiNode.compute here too. @@ -46,7 +48,7 @@ public class ParmNode extends PhiNode { if( t instanceof TypeInteger ti && t != _type && !ti.isHigh() && !ti.isConstant() && (!(_type instanceof TypeInteger oldi) || ti._widen <= oldi._widen) ) - return ti.same_but_slightly_wider_than(_minType); + return ti.same_but_slightly_wider_than(_declaredType); return t; } @@ -58,10 +60,10 @@ public class ParmNode extends PhiNode { if( fun()._folding ) return super.idealize(); // Can upgrade minType even while in-progress - if( _minType instanceof TypeMemPtr tmp && _minType.isFRef() ) { + if( _declaredType instanceof TypeMemPtr tmp && _declaredType.isFRef() ) { TypeMemPtr tmp2 = (TypeMemPtr) Parser.TYPES.get(tmp._obj._name); - if( tmp2!=null && tmp2 != _minType ) { - _minType = tmp2; + if( tmp2!=null && tmp2 != _declaredType ) { + _declaredType = tmp2; return this; } } @@ -72,4 +74,12 @@ public class ParmNode extends PhiNode { @Override public boolean eq( Node n ) { return ((ParmNode)n)._idx==_idx && super.eq(n); } + + @Override boolean _upgradeType(java.util.HashMap types) { + Type next = _declaredType.upgradeType(types); + if( next==_declaredType ) return false; + unlock(); + _declaredType=next; + return true; + } } diff --git a/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/src/main/java/com/seaofnodes/simple/node/PhiNode.java index 7f2bb1f4..064852e4 100644 --- a/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -11,33 +11,27 @@ public class PhiNode extends Node { public final String _label; - // The Phi type we compute must stay within the domain of the Phi. Example - // Int stays Int, Ptr stays Ptr, Control stays Control, Mem stays Mem. - Type _minType; - int lattice_drop; - public PhiNode(String label, Type minType, Node... inputs) { + public PhiNode(String label, Node... inputs) { super(inputs); _label = label; - assert minType!=null; - _minType = minType; } // Used by ParmNode - public PhiNode(PhiNode phi, String label, Type minType) { super(phi); _label = label; _type = _minType = minType; } + public PhiNode(PhiNode phi, String label, Type type) { super(phi); _label = label; _type = type; } // Used by instruction Selection - public PhiNode(PhiNode phi) { this(phi,phi._label,phi._minType ); } + public PhiNode(PhiNode phi) { this(phi,phi._label,phi._type ); } // Used by the infinite-loop exit breaker public PhiNode(RegionNode r, Node sample) { super(new Node[]{r}); _label = ""; - _minType = sample._type; + _type = sample._type; while( nIns() < r.nIns() ) addDef(sample); } public static PhiNode make(String label, Type type, Node... inputs) { - return type instanceof TypeMem ? new BulkMemPhiNode(label,inputs) : new PhiNode(label,type,inputs); + return type instanceof TypeMem ? new BulkMemPhiNode(label,inputs) : new PhiNode(label,inputs); } @Override public String label() { return "Phi_"+MemOpNode.mlabel(_label); } @@ -52,7 +46,7 @@ public class PhiNode extends Node { } public CFGNode region() { return (CFGNode)in(0); } - @Override public boolean isMem() { return _minType instanceof TypeMem; } + @Override public boolean isMem() { return _type instanceof TypeMem; } boolean isRPC() { return false; } @Override @@ -61,11 +55,9 @@ public class PhiNode extends Node { return region()._type==Type.XCONTROL || region()._type==Type.TOP ? (_type instanceof TypeMem ? TypeMem.TOP : Type.TOP) : _type; // During parsing Phis have to be computed type pessimistically. if( r.inProgress() || in(nIns()-1)==null ) - // Loop-Phis must lift to the declared type, because that is how - // the Parser keeps precise types until the loop finishes parsing. - // Similar, ParmNodes use precise minType until all calls are - // linked (post opto). - return r instanceof LoopNode || (this instanceof ParmNode) ? _minType : Type.BOTTOM; + // The backedge has not been parsed. Declarations belong on + // Convert/Parm nodes; an unfinished value Phi knows no bound. + return Type.BOTTOM; // Set type to local top of the starting type Type t = Type.TOP; for (int i = 1; i < nIns(); i++) { @@ -78,7 +70,7 @@ public class PhiNode extends Node { t = t.meet(in(i)._type); } } - Type newt = t.join( _minType ); + Type newt = t; // Widen loop Phis so integer ranges cannot grow one step per trip forever. // Recursion is a loop in disguise: ParmNode.compute widens arguments, @@ -91,7 +83,7 @@ public class PhiNode extends Node { // Types changed and are falling (the optimistic case, expected to fall forever) newi != _type ) { if( !newi.isHigh() && !newi.isConstant() && (!(_type instanceof TypeInteger oldi) || newi._widen <= oldi._widen) ) - return newi.same_but_slightly_wider_than(_minType); + return newi.same_but_slightly_wider_than(TypeInteger.BOT); } return newt; @@ -101,27 +93,12 @@ public class PhiNode extends Node { public Node idealize() { if( !(region() instanceof RegionNode r ) ) return in(1); // Input has collapse to e.g. starting control. - // Can upgrade minType even while in-progress - if( _minType instanceof TypeMemPtr tmp && _minType.isFRef() ) { - TypeMemPtr tmp2 = (TypeMemPtr) Parser.TYPES.get(tmp._obj._name); - if( tmp2!=null && tmp2._obj != tmp._obj ) { - _minType = tmp.makeFrom(tmp2._obj); - return this; - } - } - if( r.inProgress() || r.nIns()<=1 ) + if( r.inProgress() || inProgress() || r.nIns()<=1 ) return null; // Input is in-progress // If we have only a single unique input, become it. Node live = singleUniqueInput(); - if( live != null ) { - if( live._type.isa(_type) ) - return live; - // Let the bulk input split this alias before collapsing a precise Phi. - if( live instanceof BulkMemPhiNode ) return null; - // Keep the Phi upcast - return new CastNode(_type,null,live); - } + if( live != null ) return live; // No bother if region is going to fold dead paths soon for( int i=1; i>"; } @Override long doOp( long x, long y ) { return x >> y; } @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) { @@ -22,10 +21,11 @@ public class SarNode extends ArithNode { public Node idealize() { Node lhs = in(1); Node rhs = in(2); + Type t2 = rhs._type; // Sar of 0. - if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && lhs._type instanceof TypeInteger ) + if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && forceInt(lhs) && lhs._type instanceof TypeInteger ) return lhs; // TODO: x >> 3 >> (y ? 1 : 2) ==> x >> (y ? 4 : 5) diff --git a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java index 307fc0f9..3c1e9776 100644 --- a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -427,21 +427,14 @@ public class ScopeNode extends Node { pred = pred instanceof NotNode not ? not.in(1) : CodeGen.CODE.add(new NotNode(pred).peephole()); // This is a zero/null test. // Compute the positive test type. - Type tnz = pred._type.nonZero(); - if( tnz!=null ) - _addGuard(tnz,ctrl,pred); - - // Compute the negative test type. - if( pred instanceof NotNode not ) { - Node npred = not.in(1); - Type tzero = npred._type.makeZero(); - _addGuard(tzero,ctrl,npred); - } + _addGuard(true,ctrl,pred); + if( pred instanceof NotNode not ) + _addGuard(false,ctrl,not.in(1)); } // A Phi's value is available at its Region. Other data nodes require all // their inputs, including pinned control, to dominate the proposed guard. - private static boolean availableAt(Node n, CFGNode ctrl, BitSet visit) { + static boolean availableAt(Node n, CFGNode ctrl, BitSet visit) { if( n==null || n instanceof ConstantNode || visit.get(n._nid) ) return true; visit.set(n._nid); if( n instanceof PhiNode phi ) n = phi.region(); @@ -468,17 +461,15 @@ public class ScopeNode extends Node { return null; } - private void _addGuard(Type guard, Node ctrl, Node pred) { - Type tcast = guard.join(pred._type); - if( tcast != pred._type && !tcast.isHigh() ) { - Node cast = new CastNode(tcast,ctrl,pred.keep()).peephole().keep(); - _guards.add(pred); - _guards.add(cast); - replace(pred,cast); - } + private void _addGuard(boolean nonZero, Node ctrl, Node pred) { + Node guard = new GuardNode(nonZero,ctrl,pred.keep()).peephole(); + if( guard==pred ) { pred.unkeep(); return; } + guard.keep(); + _guards.add(pred); + _guards.add(guard); + replace(pred,guard); } - // Remove matching pred/cast pairs from this guarded region. public ScopeNode removeGuards( Node ctrl ) { assert ctrl instanceof CFGNode; diff --git a/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/src/main/java/com/seaofnodes/simple/node/ShlNode.java index e2951784..7d6e8e2e 100644 --- a/src/main/java/com/seaofnodes/simple/node/ShlNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ShlNode.java @@ -1,13 +1,13 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; public class ShlNode extends ArithNode { - public ShlNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); } + public ShlNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs, (byte)1); } - @Override public String label() { return "Shl"; } @Override public String op() { return "<<"; } @Override long doOp( long x, long y ) { return x << y; } @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) { @@ -30,13 +30,13 @@ public class ShlNode extends ArithNode { if( rhs._type instanceof TypeInteger shl && shl.isConstant() ) { // Shl of 0. - if( (shl.value()&63)==0 && lhs._type instanceof TypeInteger ) + if( (shl.value()&63)==0 && forceInt(lhs) && lhs._type instanceof TypeInteger ) return lhs; // (x + c) << i => (x << i) + (c << i) if( lhs instanceof AddNode add && addDep(add).in(2)._type instanceof TypeInteger c && c.isConstant() ) { long sum = c.value() << shl.value(); if( Integer.MIN_VALUE <= sum && sum <= Integer.MAX_VALUE ) - return new AddNode(new ShlNode(_loc,add.in(1),rhs).peephole(), Parser.con(sum) ); + return new AddNode(new ShlNode(_loc,add.in(1),rhs).peephole(), com.seaofnodes.simple.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 0023431f..4ce5f5b4 100644 --- a/src/main/java/com/seaofnodes/simple/node/ShrNode.java +++ b/src/main/java/com/seaofnodes/simple/node/ShrNode.java @@ -5,9 +5,8 @@ import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; public class ShrNode extends ArithNode { - public ShrNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); } + public ShrNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs, (byte)1); } - @Override public String label() { return "Shr"; } @Override public String op() { return ">>>"; } @Override long doOp( long x, long y ) { return x >>> y; } @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) { @@ -21,7 +20,7 @@ public class ShrNode extends ArithNode { Type t2 = rhs._type; // Shr of 0. - if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && lhs._type instanceof TypeInteger ) + if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && forceInt(lhs) && lhs._type instanceof TypeInteger ) return lhs; // TODO: x >>> 3 >>> (y ? 1 : 2) ==> x >>> (y ? 4 : 5) diff --git a/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/src/main/java/com/seaofnodes/simple/node/StoreNode.java index 47f12d82..a4abaa1d 100644 --- a/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -43,6 +43,7 @@ public class StoreNode extends MemOpNode { @Override public Type compute() { + if( _alias==1 ) return TypeMem.BOT; Type val = val()._type; assert !_declaredType.isFRef(); Type t = val.join(_declaredType).meet(MemMergeNode.contents(mem(),_alias,this)); @@ -51,6 +52,15 @@ public class StoreNode extends MemOpNode { @Override public Node idealize() { + if( _alias==1 ) { + if( !(ptr()._type instanceof TypeMemPtr ptr) || ptr._obj._open ) return null; + Field field = ptr._obj.field(_name); + if( field==null ) return null; + Node value = new ConvertNode(field._t,val()).peephole(); + StoreNode store = new StoreNode(_loc,_name,field._alias,field._t,mem(),ptr(),off(),value,_init); + store.setDef(0,in(0)); + return new MemMergeNode(mem(),field._alias,store.peephole()); + } if( mem() instanceof MemMergeNode merge ) { setDef(1,CodeGen.CODE.add(merge.alias(_alias))); return this; @@ -138,8 +148,8 @@ public class StoreNode extends MemOpNode { if( ctrl==null ) return null; Node mem = phi.in(1); - Node init = new LoadNode(_loc,_name,_alias,_declaredType,mem,ptr(),off()).peephole(); - Node value = CodeGen.CODE.add(new PhiNode(_name,_declaredType,loop,init,val()).peephole()); + Node init = new LoadNode(_loc,_name,_alias,mem,ptr(),off()).peephole(); + Node value = CodeGen.CODE.add(new PhiNode(_name,loop,init,val()).peephole()); Node sink = new StoreNode(_loc,_name,_alias,_declaredType,mem,ptr(),off(),value,_init); // Break the memory cycle, killing the old store. Keep the Phi alive // even if that store was its last use, until our caller replaces it. @@ -164,10 +174,11 @@ public class StoreNode extends MemOpNode { public Parser.ParseException err() { Parser.ParseException err = super.err(); if( err != null ) return err; - if( ptr()._type == Type.TOP ) - return null; // This means we have an error input, report elsewhere - TypeMemPtr tmp = (TypeMemPtr)ptr()._type; - if( (tmp._ro || tmp._obj.field(_name)._final) && !_init ) + if( !(ptr()._type instanceof TypeMemPtr tmp) ) + return null; // Report the bad or unresolved receiver at its source. + Field field = tmp._obj.field(_name); + if( field==null ) return Parser.error("Cannot resolve field '"+_name+"'",_loc); + if( (tmp._ro || field._final) && !_init ) return Parser.error("Cannot modify final field '"+_name+"'",_loc); return null; } diff --git a/src/main/java/com/seaofnodes/simple/node/StructNode.java b/src/main/java/com/seaofnodes/simple/node/StructNode.java index 78b20e9f..a91ee5de 100644 --- a/src/main/java/com/seaofnodes/simple/node/StructNode.java +++ b/src/main/java/com/seaofnodes/simple/node/StructNode.java @@ -10,9 +10,18 @@ import com.seaofnodes.simple.util.SB; */ public class StructNode extends Node { - public final TypeStruct _ts; + public TypeStruct _ts; public StructNode(TypeStruct ts) { _ts=ts; assert !ts._open; } + @Override boolean _upgradeType(java.util.HashMap types) { + if( _ts==null ) return false; + TypeStruct ts = (TypeStruct)_ts.upgradeType(types); + if( ts==_ts ) return false; + unlock(); + _ts = ts; + return true; + } + @Override public String label() { return _ts==null ? "STRUCT?" : _ts.str(); } @Override diff --git a/src/main/java/com/seaofnodes/simple/node/SubFNode.java b/src/main/java/com/seaofnodes/simple/node/SubFNode.java index 3fc4f7ca..d6a908ca 100644 --- a/src/main/java/com/seaofnodes/simple/node/SubFNode.java +++ b/src/main/java/com/seaofnodes/simple/node/SubFNode.java @@ -7,6 +7,7 @@ import java.util.BitSet; public class SubFNode extends ArithFNode { public SubFNode(Node lhs, Node rhs) { super(lhs, rhs); } + public SubFNode(ArithNode n) { super(n); } @Override public String label() { return "SubF"; } diff --git a/src/main/java/com/seaofnodes/simple/node/SubNode.java b/src/main/java/com/seaofnodes/simple/node/SubNode.java index 2c4fed39..c47ffb3b 100644 --- a/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -6,12 +6,14 @@ import com.seaofnodes.simple.type.TypeInteger; import java.util.BitSet; public class SubNode extends ArithNode { + @Override boolean allowFloat() { return true; } public SubNode(Node lhs, Node rhs) { super(null, lhs, rhs); } + public SubNode(Node lhs, Node rhs, byte mode) { super(null, lhs, rhs,mode); } - @Override public String label() { return "Sub"; } @Override public String op() { return "-"; } - @Override long doOp( long x, long y ) { return x - y; } + @Override long doOp( long x, long y ) { return x - y; } + @Override double doOp(double x, double y ) { return x - y; } @Override TypeInteger doOp(TypeInteger x, TypeInteger y) { // Sub of same is 0 if( in(1)==in(2) ) @@ -32,8 +34,8 @@ public class SubNode extends ArithNode { Type t1 = lhs._type; Type t2 = rhs._type; - // Keep invalid operands for type checking. - if( t1 instanceof TypeInteger x && t2 instanceof TypeInteger y ) { + // Keep invalid operands and unresolved numeric modes for type checking. + if( _mode==1 && t1 instanceof TypeInteger x && t2 instanceof TypeInteger y ) { if( y.isConstant() && y.value()==0 ) return lhs; if( x.isConstant() && x.value()==0 ) return new MinusNode(rhs); } @@ -49,6 +51,5 @@ public class SubNode extends ArithNode { return super.idealize(); } - @Override Node copy(Node lhs, Node rhs) { return new SubNode(lhs,rhs); } - @Override Node copyF() { return new SubFNode(null,null); } + @Override Node copy(Node lhs, Node rhs) { return new SubNode(lhs,rhs,_mode); } } diff --git a/src/main/java/com/seaofnodes/simple/node/XorNode.java b/src/main/java/com/seaofnodes/simple/node/XorNode.java index 75cea98e..e3dac2d0 100644 --- a/src/main/java/com/seaofnodes/simple/node/XorNode.java +++ b/src/main/java/com/seaofnodes/simple/node/XorNode.java @@ -5,9 +5,8 @@ import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; public class XorNode extends ArithNode { - public XorNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); } + public XorNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs, (byte)1); } - @Override public String label() { return "Xor"; } @Override public String op() { return "^"; } @Override long doOp( long x, long y ) { return x ^ y; } @Override TypeInteger doOp( TypeInteger x, TypeInteger y ) { @@ -18,12 +17,13 @@ public class XorNode extends ArithNode { public Node idealize() { Node lhs = in(1); Node rhs = in(2); + Type t1 = lhs._type; Type t2 = rhs._type; // Xor of 0. We do not check for (0^x) because this will already // canonicalize to (x^0) - if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && lhs._type instanceof TypeInteger ) + if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && forceInt(lhs) && lhs._type instanceof TypeInteger ) return lhs; // Move constants to RHS: con*arg becomes arg*con diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java index e1717cb3..b6d82f09 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java @@ -15,7 +15,7 @@ public class CallARM extends CallNode implements MachNode, RIPRelSize { assert tfp.isConstant(); _tfp = tfp; FunNode fun = CodeGen.CODE.link(tfp); - _name = fun==null ? ((ExternNode)call.fptr())._extern : fun._name; // Can be null for extern calls + _name = fun==null ? CodeGen.CODE.externFunc(tfp) : fun._name; } @Override public String op() { return "call"; } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java index fae98a99..0d5b5440 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java @@ -359,7 +359,7 @@ public class arm extends Machine { String op = "!="; if( iff.in(1) instanceof BoolNode bool ) op = bool.op(); else if( iff.in(1)==null ) op = "=="; // Never-node cutout - else iff.setDef(1, new BoolNode.NE(iff.in(1), new ConstantNode(TypeInteger.ZERO))); + else iff.setDef(1, new BoolNode.NE(iff.in(1), new ConstantNode(TypeInteger.ZERO),(byte)1)); return new BranchARM(iff, op); } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java index f0ad8e88..03bedd0d 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java @@ -16,7 +16,7 @@ public class CallRISC extends CallNode implements MachNode, RIPRelSize { _inputs.pop(); // Pop constant target _tfp = tfp; FunNode fun = CodeGen.CODE.link(tfp); - _name = fun==null ? ((ExternNode)call.fptr())._extern : fun._name; // Can be null for extern calls + _name = fun==null ? CodeGen.CODE.externFunc(tfp) : fun._name; } @Override public String op() { return "call"; } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java index d1a43b83..cd4d2452 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java @@ -15,7 +15,7 @@ public class CallX86 extends CallNode implements MachNode, RIPRelSize { assert tfp.isConstant(); _tfp = tfp; FunNode fun = CodeGen.CODE.link(tfp); - _name = fun==null ? ((ExternNode)call.fptr())._extern : fun._name; // Can be null for extern calls + _name = fun==null ? CodeGen.CODE.externFunc(tfp) : fun._name; } @Override public String op() { return "call"; } @Override public String label() { return op(); } diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java index fcae17f8..f0598422 100644 --- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java +++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java @@ -441,7 +441,7 @@ public class x86_64_v2 extends Machine { String op = "!="; if( iff.in(1) instanceof BoolNode bool ) op = swap ? IfNode.swap(bool.op()) : bool.op(); else if( iff.in(1)==null ) op = "=="; // Never-node cutout - else iff.setDef(1, new BoolNode.NE(iff.in(1), new ConstantNode(TypeInteger.ZERO))); + else iff.setDef(1, new BoolNode.NE(iff.in(1), new ConstantNode(TypeInteger.ZERO),(byte)1)); return new JmpX86(iff, op); } diff --git a/src/main/java/com/seaofnodes/simple/type/Field.java b/src/main/java/com/seaofnodes/simple/type/Field.java index 1f22cb05..1396f1de 100644 --- a/src/main/java/com/seaofnodes/simple/type/Field.java +++ b/src/main/java/com/seaofnodes/simple/type/Field.java @@ -105,7 +105,7 @@ public class Field extends Type { Type glb = _t._glb(true); return (glb== _t && _final ) ? this : make(_fname,glb,_alias,true,_one); } - @Override Field _close() { return makeFrom( _t._close()); } + @Override Field _close(java.util.HashMap types) { return makeFrom( _t._close(types)); } // Override in subclasses int hash() { return _fname.hashCode() ^ _t.hashCode() ^ _alias ^ (_final ? 1024 : 0) ^ (_one ? 2048 : 0); } diff --git a/src/main/java/com/seaofnodes/simple/type/Type.java b/src/main/java/com/seaofnodes/simple/type/Type.java index b9a6a1f3..9fad2767 100644 --- a/src/main/java/com/seaofnodes/simple/type/Type.java +++ b/src/main/java/com/seaofnodes/simple/type/Type.java @@ -57,6 +57,7 @@ public class Type /*implements Cloneable*/ { static final byte TNIL = 4; // low null of all flavors static final byte TXNIL = 5; // high or choice null static final byte TSIMPLE = 6; // End of the Simple Types + static final byte TSCALAR = 6; // All register/field values, excluding control and memory static final byte TPTR = 7; // All nil-able scalar values static final byte TINT = 8; // All Integers; see TypeInteger @@ -97,6 +98,7 @@ public class Type /*implements Cloneable*/ { ts.add(BOTTOM); ts.add(CONTROL); ts.add(NIL); + TypeScalar.gather(ts); TypeNil.gather(ts); TypePtr.gather(ts); TypeInteger.gather(ts); @@ -267,6 +269,8 @@ public class Type /*implements Cloneable*/ { // Constant arrays meet their integer element types. if( this instanceof TypeConAry tcon && t instanceof TypeInteger ti ) return tcon.ymeet(ti); if( t instanceof TypeConAry tcon && this instanceof TypeInteger ti ) return tcon.ymeet(ti); + if( this instanceof TypeScalar scalar0 && t instanceof TypeScalar scalar1 ) + return scalar0.smeet(scalar1); return Type.BOTTOM; // Mixing 2 unrelated types } @@ -280,8 +284,14 @@ public class Type /*implements Cloneable*/ { if( _type==TTOP || t._type==TBOT ) return t; // RHS TypeNil vs NIL/XNIL - if( _type== TNIL ) return t instanceof TypeNil ptr ? ptr.meet0() : (t._type==TXNIL ? TypePtr.PTR : BOTTOM); - if( _type== TXNIL ) return t instanceof TypeNil ptr ? ptr.meetX() : (t._type== TNIL ? TypePtr.PTR : BOTTOM); + if( _type==TNIL || _type==TXNIL ) { + if( t instanceof TypeNil ptr ) return _type==TNIL ? ptr.meet0() : ptr.meetX(); + if( t instanceof TypeScalar ) { + if( t._type==TSCALAR ) return t.isHigh() ? this : t; + return TypeScalar.BOT; + } + return t._type==(_type==TNIL ? TXNIL : TNIL) ? TypePtr.PTR : BOTTOM; + } // 'this' is only {TCTRL,TXCTRL} // Other non-simple RHS things bottom out @@ -487,7 +497,7 @@ public class Type /*implements Cloneable*/ { } Type _glb(boolean mem) { assert is_simple(); return Type.BOTTOM; } - Type _close() { return this; } + Type _close(HashMap types) { return this; } public Type widen() { return this; } // ---------------------------------------------------------- @@ -523,4 +533,11 @@ public class Type /*implements Cloneable*/ { // This is used by error messages, and is a shorted print. public String str() { return STRS[_type]; } + + // Closing declarations changes the meaning of forward shapes. Upgrade + // the whole graph together before starting the pessimistic fixed point. + public final Type upgradeType(HashMap types) { + return recurOpen()._upgradeType(types).recurClose(); + } + Type _upgradeType(HashMap types) { return this; } } diff --git a/src/main/java/com/seaofnodes/simple/type/TypeConAry.java b/src/main/java/com/seaofnodes/simple/type/TypeConAry.java index 0bc264fc..d7ce8503 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeConAry.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeConAry.java @@ -7,7 +7,7 @@ import java.util.ArrayList; /** * Represents a constant array of primitives */ -public abstract class TypeConAry extends Type { +public abstract class TypeConAry extends TypeScalar { boolean _any; byte _widen; // One of byte or int array diff --git a/src/main/java/com/seaofnodes/simple/type/TypeFloat.java b/src/main/java/com/seaofnodes/simple/type/TypeFloat.java index f3de581c..bb00ee91 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeFloat.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeFloat.java @@ -8,7 +8,7 @@ import java.util.ArrayList; /** * Float Type */ -public class TypeFloat extends Type { +public class TypeFloat extends TypeScalar { // - high -64, high -32, con 0, low +32, low +64 public byte _sz; diff --git a/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java b/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java index 50743f69..602aaaae 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java @@ -157,13 +157,13 @@ public class TypeFunPtr extends TypeNil { @Override boolean _isFinal() { return true; } @Override boolean _isGLB(boolean mem) { return true; } @Override TypeFunPtr _glb(boolean mem) { return this; } - @Override TypeFunPtr _close() { + @Override TypeFunPtr _close(java.util.HashMap types) { Type[] sig = new Type[_sig.length]; TypeFunPtr fun = malloc(_nil,false,sig,null,_fidxs); // Now start the recursion - fun._ret = _ret._close(); + fun._ret = _ret._close(types); for( int i=0; i types) { + TypeFunPtr old = (TypeFunPtr)VISIT.get(_uid); + if( old!=null ) return old; + Type[] sig = new Type[_sig.length]; + TypeFunPtr fun = malloc(_nil,_open,sig,null,_fidxs); + VISIT.put(_uid,fun); + fun._ret = _ret._upgradeType(types); + for( int i=0; i types) { + return make(_alias,_t._upgradeType(types)); + } } diff --git a/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java b/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java index 9fc90544..4a28b919 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java @@ -126,7 +126,7 @@ public class TypeMemPtr extends TypeNil { @Override TypeMemPtr _makeRO() { return withAccess(true); } @Override boolean _isGLB(boolean mem) { return _ro && _nil==3 && !_one && _obj.isGLB2(); } @Override TypeMemPtr _glb(boolean mem) { return make((byte)3,_obj.glb2(),false,true); } - @Override TypeMemPtr _close() { return makeFrom(_obj._close()); } + @Override TypeMemPtr _close(HashMap types) { return makeFrom(_obj._close(types)); } // True if this "isa" t up to named structures @@ -174,4 +174,8 @@ public class TypeMemPtr extends TypeNil { if( this== NOTBOT) return sb.p("*void"); return _obj.print(sb.p(x()).p("*"),visit,html).p(q()); } + + @Override Type _upgradeType(java.util.HashMap types) { + return makeFrom((TypeStruct)_obj._upgradeType(types)); + } } diff --git a/src/main/java/com/seaofnodes/simple/type/TypeNil.java b/src/main/java/com/seaofnodes/simple/type/TypeNil.java index 56d73b00..32505aa2 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeNil.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeNil.java @@ -7,7 +7,7 @@ import java.util.ArrayList; /** * Nil-able Scalar types */ -public abstract class TypeNil extends Type { +public abstract class TypeNil extends TypeScalar { // 0 = high-subclass choice nil // 1 = high-subclass no nil // 2 = low -subclass no nil diff --git a/src/main/java/com/seaofnodes/simple/type/TypeScalar.java b/src/main/java/com/seaofnodes/simple/type/TypeScalar.java new file mode 100644 index 00000000..1b41db13 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/type/TypeScalar.java @@ -0,0 +1,31 @@ +package com.seaofnodes.simple.type; + +import com.seaofnodes.simple.util.SB; +import java.util.ArrayList; +import java.util.BitSet; + +/** + * Values that fit in a register or field: integers, floats and pointers. + * These bounds exclude control and memory. An unresolved load can produce a + * scalar without making the parser guess which kind of value it will hold. + */ +public class TypeScalar extends Type { + protected TypeScalar(byte type) { super(type); } + private TypeScalar() { this(TSCALAR); } + public static final TypeScalar BOT = new TypeScalar().intern(); + public static final TypeScalar TOP = BOT.dual(); + public static void gather(ArrayList ts) { ts.add(BOT); } + + final Type smeet(TypeScalar scalar) { + if( _type==TSCALAR ) return isHigh() ? scalar : this; + if( scalar._type==TSCALAR ) return scalar.isHigh() ? this : scalar; + return BOT; + } + @Override Type xmeet(Type t) { return isHigh() ? t : this; } + @Override TypeScalar xdual() { return new TypeScalar(); } + @Override public boolean isHigh() { return this==TOP; } + @Override boolean _isGLB(boolean mem) { return this==BOT; } + @Override Type _glb(boolean mem) { return BOT; } + @Override public String str() { return isHigh() ? "~scalar" : "scalar"; } + @Override SB _print(SB sb, BitSet visit, boolean html) { return sb.p(str()); } +} diff --git a/src/main/java/com/seaofnodes/simple/type/TypeStruct.java b/src/main/java/com/seaofnodes/simple/type/TypeStruct.java index 55942b0e..8eec6dd4 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeStruct.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeStruct.java @@ -90,18 +90,27 @@ public class TypeStruct extends Type { public final TypeStruct close() { - return (TypeStruct)recurOpen()._close().recurClose(); + return close(null); } - @Override TypeStruct _close() { + public final TypeStruct close(HashMap types) { + return (TypeStruct)recurOpen()._close(types).recurClose(); + } + @Override TypeStruct _close(HashMap types) { Object key = isAry() ? _uid : _name; TypeStruct ts = (TypeStruct)VISIT.get(key); if( ts!=null ) return ts; + if( _open && types!=null && types.get(_name) instanceof TypeMemPtr ptr && !ptr._obj._open ) + return ptr._obj._close(types); + // Closing A must not pretend that an undeclared B is an empty + // definition. The root is visited first; self references find it above. + // Keep other empty forward shapes open until their own declaration. + if( _open && _fields.length==0 && VISIT.size()>1 ) return this; ts = recurPre(key,false); Field[] flds = ts._fields; // Now start the recursion for( int i=0; i types) { + Type def = types.get(_name); + if( def instanceof TypeMemPtr ptr && !ptr._obj._open && _open ) + return ptr._obj; + TypeStruct old = (TypeStruct)VISIT.get(_uid); + if( old!=null ) return old; + TypeStruct ts = recurPre(_uid,_open); + for( int i=0; i<_fields.length; i++ ) + ts._fields[i].setType(_fields[i]._t._upgradeType(types)); + return ts; + } } diff --git a/src/main/java/com/seaofnodes/simple/type/TypeTuple.java b/src/main/java/com/seaofnodes/simple/type/TypeTuple.java index df9aba31..27a0a9d9 100644 --- a/src/main/java/com/seaofnodes/simple/type/TypeTuple.java +++ b/src/main/java/com/seaofnodes/simple/type/TypeTuple.java @@ -18,6 +18,7 @@ public class TypeTuple extends Type { // All fields directly listed public static TypeTuple make(Type... types) { TypeTuple tt = malloc(types); + for( Type t : types ) if( !t._terned ) return tt; TypeTuple t2 = tt.intern(); return t2==tt ? tt : t2.free(tt); } @@ -61,6 +62,18 @@ public class TypeTuple extends Type { return malloc(TypeFunPtr.xdual(_types)); } + @Override int nkids() { return _types==null ? 0 : _types.length; } + @Override Type at(int idx) { return _types[idx]; } + @Override void set(int idx, Type t) { _types[idx]=t; } + @Override TypeTuple rdual() { + if( _dual!=null ) return dual(); + TypeTuple d = malloc(new Type[_types.length]); + (_dual=d)._dual=this; + for( int i=0; i<_types.length; i++ ) + d._types[i] = _types[i]._terned ? _types[i].dual() : _types[i].rdual(); + return d; + } + @Override boolean _isConstant() { for( Type t : _types ) if( !t._isConstant() ) @@ -112,8 +125,10 @@ public class TypeTuple extends Type { return true; } - @Override int nkids() { return _types.length; } - @Override Type at( int idx ) { return _types[idx]; } - @Override void set( int idx, Type t ) { _types[idx] = t; } - + @Override Type _upgradeType(java.util.HashMap types) { + if( _types==null ) return this; + Type[] ts = new Type[_types.length]; + for( int i=0; i