summaryrefslogtreecommitdiff
path: root/chapter25/src/main/java
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-08-20 10:56:30 -0700
committerCliff Click <cliffc@acm.org>2026-08-20 10:56:30 -0700
commit3568f3deed586327f885ac98b73d07ae3ade6e57 (patch)
treefce7865468dc9e2beabeb68342f23bfea5bdae1a /chapter25/src/main/java
parent1cb8279a64f4f79615ac8358927b11237df29bf2 (diff)
downloadSimple-3568f3deed586327f885ac98b73d07ae3ade6e57.tar.gz
Simple-3568f3deed586327f885ac98b73d07ae3ade6e57.zip
Squashed commit of the following:
commit c05d7df4f9c90984d2c04126772be3fee1d81fd1 Author: Cliff Click <cliffc@acm.org> Date: Thu Aug 20 10:52:58 2026 -0700 Fix local frefs, deferred inlining, and sys FileIO support - Defer non-trivial inlining out of CallEnd idealization and run it from IterPeeps after normal peepholes reach a fixed point. - Keep forward refs local to their lexical scope, promote them outward on scope pop, and track created FRefNodes directly for dependency discovery. - Preserve loop scope shape when frefs are first seen inside loop bodies. - Treat frefs as final constants from creation and force lazy self lookups before implicit calls. - Reset imported pointer/struct types conservatively for post-parse Iter. - Pin Start-owned global machine value chains and clone global constants per function during GCM. - Improve late ASM printing before encoding and namespace constant-pool symbols. - Add optional regalloc function-local edge verifier and improve empty-mask splitting for multi-def/fixed-register LRGs. - Add sys array compare, safer array copy bounds, libc open/creat/close/unlink bindings, aryu8.str0, and higher-level sys.io file operations. - Add docs/examples/FileIO.smp and a Chapter25 FileIO test. - Update spill/code-size goldens after the allocator and scheduling changes. commit ef5316979732ac118612d310b75923c8f59ecdc1 Author: Cliff Click <cliffc@acm.org> Date: Fri Aug 14 16:29:56 2026 -0700 Dijkstra uses bitset. TestC.exec for repeated test runs with different stdins. Support &= and |= op-update forms. commit 728a527587f19bb49860f02ae5ac5968ab01ae33 Author: Cliff Click <cliffc@acm.org> Date: Fri Aug 14 12:13:28 2026 -0700 Fix live demo commit e4c069b9ef2cbfafe28d8edfcbff55396cde085e Author: Cliff Click <cliffc@acm.org> Date: Fri Aug 14 09:52:24 2026 -0700 Update Dijkstra.smp commit b8015739b4056e3f7a715a030e846a441021a9f1 Author: Cliff Click <cliffc@acm.org> Date: Fri Aug 14 09:41:17 2026 -0700 Better looking Dijkstra commit 7cce587804c6169b24829e0b5bdc10be94357a31 Author: Cliff Click <cliffc@acm.org> Date: Fri Aug 14 09:33:33 2026 -0700 Fix CmpMemX86 encoding Use sys.aryu8 for number printing commit 310b5d8c9978a3b09622a4b89dac97f0e5e6f3fb Author: Cliff Click <cliffc@acm.org> Date: Fri Aug 14 09:04:58 2026 -0700 Progress dijlstra Fix NotX86 encoding. Allow Not-sinking through Phis; the Nots both take Scalar and might take different types (ptr vs int). Better guard installing during && || opers. Drop the csv parse flavor in sys lib. commit 5917d5322521dd5e762c4698d34e5d7ac9b3a37f Author: Cliff Click <cliffc@acm.org> Date: Thu Aug 13 14:44:12 2026 -0700 missed commit da35ad4d8a330559a21c04ae6326536e0fd62c48 Author: Cliff Click <cliffc@acm.org> Date: Thu Aug 13 14:43:42 2026 -0700 Major cleanup of allocation All the graph-gen is moved to the <ctor> declaration and not at the alloc site. commit c4223216099ccbc526d282bb263972532d48c5b2 Author: Cliff Click <cliffc@acm.org> Date: Thu Aug 13 13:26:01 2026 -0700 Some work from Dykstra (more coming) commit a03bc567f61dc7e4355e453ee4be16ac2528d1ab Author: Cliff Click <cliffc@acm.org> Date: Thu Aug 13 09:32:55 2026 -0700 Spill correctness and improvements Had the callee-save pick-risky backwards vs cloneables. commit 2d306e4430225f916cb7fb999f4926e52f6fb70a Author: Cliff Click <cliffc@acm.org> Date: Wed Aug 12 19:40:04 2026 -0700 Tighter parser handling of missing fields Bug fix add libc.errno, read can return -1, sys.ary exists commit b7eeefda4cafa1e86153170dca6f13d45ef63f69 Author: Cliff Click <cliffc@acm.org> Date: Wed Aug 12 16:22:48 2026 -0700 Printer improvements commit 8b9112226f1d2d1c843b080eecefe0d5ddc1f8cf Author: Cliff Click <cliffc@acm.org> Date: Wed Aug 12 15:30:46 2026 -0700 Remove GLB commit ae566f5c610b9c30831906d3f024901d8930a227 Author: Cliff Click <cliffc@acm.org> Date: Wed Aug 12 14:38:51 2026 -0700 Add Capitalize example Rename Ary to ary. Fix bug || carrying state along. Fix bug load-array removing control. Add sys.char tolower/upper
Diffstat (limited to 'chapter25/src/main/java')
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/IterPeeps.java44
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/Parser.java260
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java65
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java3
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java85
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/IFG.java84
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/Opto.java4
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java29
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java134
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/Serialize.java9
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java203
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/CallNode.java14
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/EscapeNode.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/FRefNode.java7
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/FunNode.java73
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java3
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java3
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/MemOpNode.java8
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ParmNode.java4
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/PhiNode.java9
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ReturnNode.java3
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ScopeNode.java104
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java10
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java4
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java17
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java5
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java43
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/print/IRPrinter.java157
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/Field.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/Type.java25
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypeConAry.java3
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypeFloat.java8
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java7
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypeInteger.java6
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypeMem.java11
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java6
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypeNil.java1
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypePtr.java4
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypeRPC.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypeScalar.java3
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypeStruct.java21
43 files changed, 1031 insertions, 458 deletions
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter25/src/main/java/com/seaofnodes/simple/IterPeeps.java
index 9fac7a99..532f5c70 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/IterPeeps.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/IterPeeps.java
@@ -50,18 +50,58 @@ import java.util.Random;
public class IterPeeps {
public final WorkList<Node> _work;
+ final WorkList<CallEndNode> _workInline;
- public IterPeeps( long seed ) { _work = new WorkList<>(seed); }
+ public IterPeeps( long seed ) {
+ _work = new WorkList<>(seed);
+ _workInline = new WorkList<>(seed);
+ }
@SuppressWarnings("unchecked")
public <N extends Node> N add( N n ) { return (N)_work.push(n); }
public void addAll( Ary<Node> ary ) { _work.addAll(ary); }
+ public void deferInline( CodeGen code, CallEndNode cend ) {
+ if( !code._midAssert )
+ _workInline.push(cend);
+ }
+
/**
- * Iterate peepholes to a fixed point
+ * Iterate peepholes and inlining to a fixed point
*/
public void iterate( CodeGen code ) {
+ CallEndNode cend = null;
+ boolean didInline = false;
+ Ary<Node> defer = new Ary<>(Node.class);
+ while( true ) {
+ // Clean up everything that does not grow the code
+ iteratePeeps(code);
+
+ // Pick an inline candidate, no real heuristic, first come, first served
+ while( (cend=_workInline.pop()) != null) {
+ if( cend.isDead() ) continue;
+ if( cend.inline() == -1 ) defer.add(cend);
+ if( cend.inline() > 0 ) break;
+ }
+ // Found an inline candidate, so inline
+ if( cend != null ) {
+ // Inline, run peeps until clean again
+ cend.doInline();
+ didInline = true;
+ continue;
+ }
+ // No more candidates, check the defer list
+ if( !didInline ) break; // No more progress, so all the "maybe inline after cleanup" do not progress
+ // Some inlining happened, retry all the "try again after cleanup" calls
+ didInline = false;
+ code.addAll(defer);
+ defer.clear();
+ }
+ }
+
+ // Run all the code-reduction and type-lifting peeps as possible
+ private void iteratePeeps( CodeGen code ) {
assert progressOnList(code, _work, true);
int cnt=0;
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/Parser.java b/chapter25/src/main/java/com/seaofnodes/simple/Parser.java
index e964fa5b..98093a77 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/Parser.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/Parser.java
@@ -100,6 +100,7 @@ public class Parser {
ScopeNode _breakScope; // Merge all the while-breaks here
ScopeNode _continueScope; // Merge all the while-continues here
ScopeNode _returnScope; // Merge all the function exits here
+ Ary<FRefNode> _frefs; // Forward refs created during this parse
TypeStruct _ctorOpenStruct; // Open struct being initialized by an allocation constructor block.
// Mapping from a type name to a Type. The string name matches
@@ -164,7 +165,9 @@ public class Parser {
public Ary<FRefNode> parse( CompUnit ref ) {
assert _scope == null && _breakScope == null && _continueScope == null && _returnScope == null;
+ assert _frefs == null;
_lexer = new Lexer(ref._src);
+ _frefs = new Ary<>(FRefNode.class);
// Starting Scope has control, memory, initial arguments
_scope = new ScopeNode();
_scope.define(ScopeNode.CTRL, Type.CONTROL , false, null, _lexer);
@@ -183,12 +186,12 @@ public class Parser {
if( !_lexer.isEOF() ) throw _errorSyntax("unexpected");
- // At the top scope and every new var should be a FRef, and these
+ // All still-unresolved forward refs
// should immediately resolve to a file-based name which is a Simple
// class type.
Ary<FRefNode> frefs = new Ary<>(FRefNode.class);
- for( int i=2; i<_scope._vars._len; i++ ) {
- FRefNode fref = (FRefNode)_scope._inputs.at(i);
+ for( FRefNode fref : _frefs ) {
+ if( fref.nIns() != 1 && fref._con == FRefNode.FREF_TYPE ) continue;
CompUnit cu = ParseAll.findCompUnitOrThrow(_code,_ref,fref._name);
fref._con = TypeMemPtr.make((byte)2,TypeStruct.make(addClzPrefix(cu._cname),true),true);
frefs.push(fref);
@@ -201,6 +204,7 @@ public class Parser {
_xScopes.pop();
_scope.kill();
_scope = null;
+ _frefs = null;
return frefs;
}
@@ -475,7 +479,7 @@ public class Parser {
Field[] fs = new Field[self._fields.length];
for( int i=0; i<fs.length; i++ ) {
Field fld = self._fields[i];
- fs[i] = fld._t instanceof TypeFunPtr ? fld : fld.makeFrom(fld._t.glb(true));
+ fs[i] = fld.makeFrom(fld._t.makeStorage());
}
return TypeStruct.make(self._name,self._open,fs);
}
@@ -680,6 +684,8 @@ public class Parser {
// it sets the second input to the corresponding input from the back
// edge. If the phi is redundant, it is replaced by its sole input.
var exit = _breakScope;
+ head.balanceLoopFRefs(_scope);
+ exit.balanceLoopFRefs(_scope);
head.endLoop(_scope, exit);
head.unkeep().kill();
@@ -812,13 +818,15 @@ public class Parser {
Node rhs = switch( fside ) {
case "else" -> (doRHS=match(fside)) ? parseStatement() : con(lhs._type.makeZero());
case ":" -> (doRHS=match(fside)) ? parseAsgn() : con(lhs._type.makeZero());
- case "&&" -> rhs = pred;
- case "||" -> rhs = parseLogical();
+ case "&&" -> con(lhs._type.makeZero()); // pred && expr_is_ignored
+ case "||" -> parseLogical(); // pred || expr
default -> throw TODO();
};
rhs.keep();
_scope.removeGuards(ifF);
- if( doRHS )
+ // The RHS of `||` is parsed on the false side, so retain its updated
+ // control, memory and locals just as an explicit else arm does.
+ if( doRHS || fside.equals("||") )
fScope = _scope;
pred.unkeep();
@@ -889,7 +897,7 @@ public class Parser {
_returnScope = _scope.dup();
while( lexN+1 < _returnScope.depth() )
_returnScope._pop(); // Pop a nested block scope until we hit the function scope
- _returnScope.define("$expr", expr._type.glb(false), true, expr, null);
+ _returnScope.define("$expr", inferredReturnType(expr._type), true, expr, null);
} else {
// For <init> and <clinit> - ALL FIELDS IN LAST SCOPE are live
@@ -998,6 +1006,34 @@ public class Parser {
return peep(new ConvertNode(t,expr));
}
+ // Inferred `var` declarations describe writable slots, not the exact
+ // value used to initialize them. Explicitly typed locals retain the
+ // user's nullability and numeric-width guarantees; `val` retains the
+ // initializer's precise type.
+ private static Type mutableInferredType(Type t) {
+ return switch( t ) {
+ case TypeInteger ti -> TypeInteger.BOT;
+ case TypeFloat tf -> TypeFloat.F64;
+ case TypeMemPtr tmp -> tmp.makeVar();
+ case TypeFunPtr tfp -> tfp;
+ case TypeConAry<?> a -> a;
+ case TypePtr ptr -> TypePtr.PTR;
+ case TypeScalar ts -> TypeScalar.BOT;
+ default -> t;
+ };
+ }
+
+ // Seed an inferred return merge with the declared numeric family instead
+ // of a transient constant/range. Pointer nullability remains part of the
+ // inferred function result and is combined by the later Phi.
+ private static Type inferredReturnType(Type t) {
+ return switch( t ) {
+ case TypeInteger ti -> TypeInteger.BOT;
+ case TypeFloat tf -> TypeFloat.F64;
+ default -> t;
+ };
+ }
+
private Node widenInt( Node expr, Type t ) {
return (expr._type instanceof TypeInteger || expr._type==Type.NIL) && t instanceof TypeFloat
? peep(new ToFloatNode(expr)) : expr;
@@ -1064,7 +1100,7 @@ public class Parser {
if( expr._type==Type.NIL )
throw error("a not-null/non-zero expression");
t = expr._type;
- if( !xfinal ) t = t.glb(false); // Widen if not final
+ if( !xfinal ) t = mutableInferredType(t);
}
// Final is deep on ptrs
@@ -1074,7 +1110,8 @@ public class Parser {
}
// expr is a constant function
- if( t instanceof TypeFunPtr && expr._type instanceof TypeFunPtr tfp && tfp.isConstant() ) {
+ if( t instanceof TypeFunPtr decl && expr._type instanceof TypeFunPtr tfp && tfp.isConstant() ) {
+ t = decl.makeFrom(tfp.fidx());
FunNode fun = _code.link(tfp);
if( fun != null )
fun.setName(name); // Assign debug name to Simple function
@@ -1158,6 +1195,8 @@ public class Parser {
ts = ctorFld==null
? TypeStruct.make(clzName,false,fld)
: TypeStruct.make(clzName,false,fld,ctorFld);
+ TypeFunPtr alloc = makeAllocator((TypeStruct)TYPES.get(fullName),ret,ctorFld);
+ ts = ts.add(Field.make("<new>",alloc,_code.alias(_ref._cname),true));
TYPES.put(clzName,ts);
// Insert a field in the containing class with the nested class type.
@@ -1169,6 +1208,78 @@ public class Parser {
return require(_code.ZERO,";");
}
+ // Build the declaration-side allocation helper. Allocation sites only
+ // call this function; all layout, initialization and publication choices
+ // are made here, where the complete struct and constructor are known.
+ private TypeFunPtr makeAllocator(TypeStruct ts, ReturnNode initRet, Field ctorFld) {
+ TypeFunPtr ctor = ctorFld==null ? null : (TypeFunPtr)ctorFld._t;
+ int nargs = ctor==null ? 0 : ctor.nargs()-2; // Hide self and selfMem
+ Type[] atypes = new Type[nargs];
+ for( int i=0; i<nargs; i++ ) atypes[i] = ctor.arg(i+2);
+ TypeMemPtr ptr = TypeMemPtr.make(ts);
+ TypeFunPtr sig = TypeFunPtr.make1((byte)2,true,atypes,ptr,_code.fidx(_ref._cname));
+ String name = (ts._name+".<new>").intern();
+
+ int oldUID = _code.UID();
+ FunNode fun = (FunNode)new FunNode(loc(),sig,name,_ref,null,_ref._start).peephole();
+ _code.link(fun);
+ Node rpc = new ParmNode("$rpc",0,TypeRPC.BOT,fun,ConstantNode.seed(TypeRPC.BOT).peephole()).peephole();
+ Node pub = new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,
+ new ProjNode(_ref._start,1,ScopeNode.MEM0).peephole()).peephole();
+ Node[] parms = new Node[nargs];
+ for( int i=0; i<nargs; i++ )
+ parms[i] = new ParmNode("arg"+i,i+2,atypes[i],fun,ConstantNode.seed(atypes[i]).peephole()).peephole();
+
+ NewNode nnn = new NewNode(ts,fun,off(ts," len")).init();
+ Node self = new ProjNode(nnn,0,ts.str()).init();
+ Node priv = new ProjNode(nnn,1,"#selfMem").init();
+ Node[] initArgs = new Node[]{fun,pub,self,priv,
+ new FunPtrNode(initRet.fun().sig(),_code._start,initRet).peephole()};
+ CallEndNode cend = rawCall(initArgs,initRet.fun());
+ Node ctl = new CProjNode(cend,0,ScopeNode.CTRL).peephole();
+ pub = new ProjNode(cend,1,ScopeNode.MEM0).peephole();
+ priv = new ProjNode(cend,2,"#selfMem").peephole();
+
+ if( ctor != null ) {
+ Node[] cargs = new Node[nargs+5];
+ cargs[0]=ctl; cargs[1]=pub; cargs[2]=self; cargs[3]=priv;
+ for( int i=0; i<nargs; i++ ) cargs[i+4]=parms[i];
+ FunNode cfun = _code.link(ctor);
+ cargs[cargs.length-1] = new FunPtrNode(ctor,_code._start,cfun.ret()).peephole();
+ cend = rawCall(cargs,cfun);
+ ctl = new CProjNode(cend,0,ScopeNode.CTRL).peephole();
+ pub = new ProjNode(cend,1,ScopeNode.MEM0).peephole();
+ priv = new ProjNode(cend,2,"#selfMem").peephole();
+ }
+
+ for( Field field : ts._fields ) {
+ Type storage = field._t.makeStorage();
+ Field escaped = field._final ? field : Field.make(field._fname,storage,field._alias,true);
+ pub.keep();
+ Node esc = new EscapeNode(escaped,self,priv,pub).peephole();
+ MemMergeNode merge = new MemMergeNode(false,null,pub);
+ merge.alias(field._alias,esc);
+ Node next = merge.peephole();
+ pub.unkeep();
+ pub = next;
+ }
+ ReturnNode ret = (ReturnNode)new ReturnNode(ctl,pub,self,rpc,fun).peephole();
+ fun.setRet(ret);
+ fun._approxUIDs = _code.UID() - oldUID;
+ _ref.addFun(_code,fun);
+ return sig;
+ }
+
+ private CallEndNode rawCall(Node[] args, FunNode target) {
+ // Establish this known declaration-side edge immediately, but do not
+ // peephole or inline it while the surrounding factory is being built.
+ // IterPeeps therefore starts with the complete initializer caller set.
+ CallNode call = new CallNode(loc(),args).init();
+ CallEndNode cend = new CallEndNode(call,TypeRPC.constant(_code.rpc(_ref._cname))).init();
+ call.link(target);
+ return cend;
+ }
+
// Parse a struct declaration (not an allocation); file-level is a class-init, and scope structs are normal
private ReturnNode parseStruct( boolean isClz, String typeName ) {
// Record & restore global state set during parsing the <init> code
@@ -1816,7 +1927,8 @@ public class Parser {
t = TypeMemPtr.make((byte)2,clz,true);
}
// Define the FRef
- var = _scope.defineFRef(id,t,cu!=null,loc());
+ var = _scope.defineFRef(id,t,true,loc());
+ _frefs.push((FRefNode)_scope.in(var));
}
// Load local value
@@ -1867,7 +1979,7 @@ public class Parser {
int selfx = _scope.kindFcn(var)._lexSize;
// Might be a method call from inside a method, so no explicit "self".
// Pass the in-scope "self"
- return parsePostfixMethod(rvalue,_scope.in(selfx));
+ return parsePostfixMethod(rvalue,_scope.in(_scope.update(_scope.var(selfx),null)));
}
// Assign-update direct into Scope
Node op = opAssign(ch,rvalue, var.type() );
@@ -1897,11 +2009,13 @@ public class Parser {
case '-' -> new SubNode(lhs,rhs);
case '*' -> new MulNode(lhs,rhs);
case '/' -> new DivNode(lhs,rhs);
+ case '|' -> new OrNode(null,lhs,rhs);
+ case '&' -> new AndNode(null,lhs,rhs);
default -> throw TODO();
};
// Convert to float ops, or narrow int types; error if not declared type.
// Also, if postfix LHS is still keep()
- return convertExpr(peep(op),t.glb(false));
+ return convertExpr(peep(op),t);
}
@@ -1931,59 +2045,38 @@ public class Parser {
}
private Node allocStruct(TypeStruct ts) {
- Ary<Node> ctorArgs = constructorArgs(); // CNC TODO- BAD THREADING SELF-MEM
- Node size = off(ts, " len");
-
- // Build a NewNode; takes in ctrl and size.
- // Produces a ptr and a private mem.
- NewNode nnn = new NewNode(ts, ctrl(), size ).init();
- ProjNode self = new ProjNode(nnn,0,ts.str()).init().keep();
- ProjNode smem = new ProjNode(nnn,1,"#selfMem").init();
-
- // Find a "class:XXX" struct, with field "XXX" function ptr as the <init>
- TypeStruct clz = (TypeStruct)TYPES.get(addClzPrefix(ts._name));
+ Ary<Node> ctorArgs = constructorArgs();
+ // A field declaration can leave a short forward type name in TYPES.
+ // Allocation happens in lexical context, so resolve that short name
+ // to the completed nested type before selecting its class helper.
+ String typeName = ts._name.indexOf('.') == -1 ? fullTypeName(ts._name) : ts._name;
+ Type resolved = TYPES.get(typeName);
+ if( resolved instanceof TypeStruct rts ) ts = rts;
+ TypeStruct clz = (TypeStruct)TYPES.get(addClzPrefix(typeName));
if( clz==null )
throw error("Unknown struct type '" + ts._name + "'");
- TypeFunPtr init = (TypeFunPtr)clz.field(initFieldName(ts._name))._t;
-
- // Call construct <init>($ctrl,$mem,NewNode.self,NewNode.#selfMem) and
- // encourage inlining
- Node initSelf = self._type.isa(init.arg(0)) ? self : peep(new CheckCastNode(init.arg(0),ctrl(),self));
- Ary<Node> args = new Ary<>(Node.class){{add(ctrl()); add(mem()); add(initSelf); add(smem); add(ConstantNode.seed(init)); }};
- Node selfMem = functionCall( args );
- // The returned value is a merge of private *Memory* and NOT some Scalar
-
- // Optional user constructor; same as the default constructor; private
- // memory going in and out.
- Field ctor = clz.field("<ctor>");
- if( ctor != null )
- selfMem = constructorCall((TypeFunPtr)ctor._t,self,selfMem,ctorArgs);
- else if( !ctorArgs.isEmpty() )
- throw error("Constructor arguments for '" + ts._name + "' but no constructor is defined");
- if( match("{") )
- throw error("Inline constructor blocks are no longer supported; define a constructor in '" + ts._name + "'");
-
- // Might be TOP if parsing in dead/unreachable code
- if( selfMem._type == Type.TOP ) {
- _code.add(selfMem);
- return self.unkeep();
- }
-
- // Escape all new aliases. EscapeNode inputs are the self pointer, the
- // merged private memory, then all the named public aliases. The
- // output is all the newly merged public aliases - but not actually
- // bulk memory.
- selfMem.keep();
- for( Field fld : ts._fields ) {
- Field fld2 = fld._final ? fld : (Field)fld.glb(true);
- Node prior = mem();
- Node esc = peep(new EscapeNode(fld2,self,selfMem,prior));
- mem(mergeAlias(prior,fld._alias,esc));
- }
-
- if( selfMem.unkeep().isUnused() ) selfMem.kill();
- else _code.add(selfMem);
- return self.unkeep();
+ Field afld = clz.field("<new>");
+ Node alloc;
+ if( afld != null ) {
+ TypeFunPtr tfp = (TypeFunPtr)afld._t;
+ FunNode fun = _code.link(tfp);
+ alloc = fun==null
+ ? new FRefNode((addClzPrefix(typeName)+".<new>").intern(),loc())
+ : new FunPtrNode(tfp,_code._start,fun.ret()).peephole();
+ } else {
+ if( !ts._fref )
+ throw error("Constructor arguments for '" + ts._name + "' but no constructor is defined");
+ Type[] sig = new Type[ctorArgs._len];
+ for( int i=0; i<ctorArgs._len; i++ )
+ sig[i] = ctorArgs.at(i)._type;
+ alloc = new FRefNode((addClzPrefix(typeName)+".<new>").intern(),loc(),
+ TypeFunPtr.make(false,sig,TypeMemPtr.make(ts)));
+ }
+ Ary<Node> args = new Ary<>(Node.class);
+ args.add(ctrl()); args.add(mem());
+ for( Node arg : ctorArgs ) args.add(arg.unkeep());
+ args.add(alloc);
+ return functionCall(args);
}
private Ary<Node> constructorArgs() {
@@ -2000,29 +2093,6 @@ public class Parser {
return args;
}
- // Constructors are called with two extra arguments: self and selfMem.
- private Node constructorCall(TypeFunPtr ctor, Node self, Node selfMem, Ary<Node> ctorArgs) {
- Ary<Node> args = new Ary<>(Node.class);
- Node ctorSelf = self._type.isa(ctor.arg(0))
- ? self
- : peep(new CheckCastNode(ctor.arg(0),ctrl(),self));
- args.add(ctrl());
- args.add(mem());
- args.add(ctorSelf);
- args.add(selfMem);
- for( Node arg : ctorArgs )
- args.add(arg.unkeep());
- args.add(ConstantNode.seed(ctor));
-
- CallNode call = (CallNode)new CallNode(loc(), args.asAry()).peephole();
- CallEndNode cend = (CallEndNode)new CallEndNode(call,TypeRPC.constant(_code.rpc(_ref._cname))).peephole();
- call.peephole();
- cend = (CallEndNode)cend.keep().peephole();
- ctrl(new CProjNode(cend,0,ScopeNode.CTRL).peephole());
- mem (new ProjNode(cend,1,ScopeNode.MEM0).peephole());
- return new ProjNode(cend.unkeep(),2,"#selfMem").peephole();
- }
-
private Node allocArray(TypeStruct ts, Node len) {
Field lenFld = ts.field("#" );
Field bodyFld = ts.field("[]");
@@ -2104,7 +2174,8 @@ public class Parser {
}
private Node defaultSelf() {
- return _scope.in(_scope._kinds.at(_scope.enclosingFuncOrDecl())._lexSize);
+ int selfx = _scope._kinds.at(_scope.enclosingFuncOrDecl())._lexSize;
+ return _scope.in(_scope.update(_scope.var(selfx),null));
}
/**
@@ -2147,8 +2218,18 @@ public class Parser {
}
Field fld = ts==null ? null : ts.field(name);
+ if( fld==null && ts!=null && !startsClzPrefix(ts._name) ) {
+ Type clz0 = TYPES.get(addClzPrefix(ts._name));
+ if( clz0 instanceof TypeStruct clz && (fld = latestStruct(clz).field(name)) != null ) {
+ expr.unkeep();
+ ts = latestStruct(clz);
+ expr = con(TypeMemPtr.make((byte)2,ts,true)).keep();
+ }
+ }
if( fld==null && ts!=null )
fld = fieldOrOpen(ts,name,false);
+ if( fld==null && ts!=null && !latestStruct(ts)._open )
+ throw error("Accessing unknown field '"+name+"' from '*"+ts._name+"'");
// With no field, we will update the bulk memory
int alias = fld==null ? 1 : fld._alias;
Type decl = fld==null ? Type.BOTTOM : fld._t;
@@ -2195,7 +2276,7 @@ public class Parser {
throw error( "'" + ts._name + "' is not fully initialized, field '" + fld._fname + "' needs to be set in a constructor" );
Node op = opAssign(ch,load, decl );
mem.keep();
- Node st = new StoreNode(loc(), name, alias, decl.glb(true), ctrl(), mem, expr.unkeep(), off.unkeep(), op, false).peephole().keep();
+ Node st = new StoreNode(loc(), name, alias, decl, ctrl(), mem, expr.unkeep(), off.unkeep(), op, false).peephole().keep();
storeMem(st,alias,mem);
st.unkeep();
mem.unkeep();
@@ -2416,6 +2497,7 @@ public class Parser {
private char escapedChar(String kind) {
char esc = _lexer.nextChar();
return switch( esc ) {
+ case '0' -> '\u0000';
case 'n' -> '\n';
case 't' -> '\t';
case 'r' -> '\r';
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
index 85d8143b..50f5c33b 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
@@ -77,6 +77,7 @@ public class CodeGen {
// True when fidx is published through a public field of a public class.
// TypeStruct.fidxs() already excludes private (underscore) fields.
public boolean publicFIDX(int fidx) {
+ if( fidx < GlobalBits.RESERVED ) return false;
// A public class initializer is itself an external root; unlike
// constructors and methods, it is not reached through a class field.
FunNode fun = _linker.atX(fidx);
@@ -128,7 +129,7 @@ public class CodeGen {
void freezePublicInterface() {
assert _publicFIDXs==null && _publicAliases==null;
int[] fidxs = XInt.EMPTY;
- for( int fidx=1; fidx<_linker._len; fidx++ )
+ for( int fidx=GlobalBits.RESERVED; fidx<_linker._len; fidx++ )
if( publicFIDX(fidx) )
fidxs = XInt.make(fidxs,fidx);
int[] aliases = XInt.EMPTY;
@@ -268,7 +269,9 @@ public class CodeGen {
if( (dump & (1<<30)) != 0 )
System.err.println("After "+_phase+":");
- System.err.println(IRPrinter.prettyPrint(this));
+ System.err.println(_phase.ordinal() >= Phase.LocalSched.ordinal()
+ ? asm(new SB()).toString()
+ : IRPrinter.prettyPrint(this));
return p2;
}
@@ -444,11 +447,13 @@ public class CodeGen {
// The phase shift closes all parser-in-progress states and freezes
// unknown external inputs. Imported nodes carry post-Opto escape
- // precision, but Iter is a fresh pessimistic solve: non-singleton
- // memory must restart at FULL so newly discovered private escapes can
- // only be removed, never added. Seed the complete graph once.
+ // precision, but Iter is a fresh pessimistic solve: memory escape
+ // summaries must restart at FULL so newly discovered private escapes
+ // can only be removed, never added. Seed the complete graph once.
+ boolean hasImports = _externPaths != null;
_start.walk(n -> {
- n._type = iterEscapeReset(n._type);
+ if( !(n instanceof ConstantNode) )
+ n._type = hasImports ? iterImportReset(n._type) : iterEscapeReset(n._type);
add(n);
return null;
});
@@ -473,6 +478,26 @@ public class CodeGen {
return t;
}
+ private static Type iterImportReset(Type t) {
+ if( t.isHigh() )
+ return t.dual();
+ if( t instanceof TypeMem mem )
+ return TypeMem.make(mem._alias,iterImportReset(mem._t),mem._one,mem._clz,mem._final,XInt.FULL,XInt.FULL);
+ if( t instanceof TypeMemPtr )
+ return t.makeStorage();
+ if( t instanceof TypeStruct )
+ return t.makeStorage();
+ if( t instanceof TypeTuple tt ) {
+ TypeTuple rez = tt;
+ for( int i=0; i<tt._types.length; i++ ) {
+ Type tx = iterImportReset(tt._types[i]);
+ if( tx != tt._types[i] ) rez = rez.makeFrom(i,tx);
+ }
+ return rez;
+ }
+ return t;
+ }
+
// Run ideal optimizations
public CodeGen opto() {
@@ -703,6 +728,7 @@ public class CodeGen {
if( n instanceof MachNode ) {
for( int i=0; i < n.nIns(); i++ )
n._inputs.set(i, _instSelect(n.in(i),map) );
+ pinGlobalValue(n);
return n;
}
@@ -716,6 +742,7 @@ public class CodeGen {
// Walk machine op and replace inputs with mapped inputs
for( int i=0; i < x.nIns(); i++ )
x._inputs.set(i, _instSelect(x.in(i),map) );
+ pinGlobalValue(x);
// Post selection action
if( x instanceof MachNode mach ) {
if( n instanceof ReturnNode ret )
@@ -726,6 +753,28 @@ public class CodeGen {
return x;
}
+
+ // Some machine values are Start-pinned globals or zero-code wrappers around
+ // them, e.g. pointer/function constants and PtrToInt/ReadOnly-style adapters.
+ // Before output edges are rebuilt, make ownership explicit: every global value
+ // chain member is Start-pinned in slot 0 and data-linked in later slots.
+ private void pinGlobalValue(Node n) {
+ if( n instanceof CFGNode || !(n instanceof MachNode mach) || mach.outregmap()==null || n.isPinned() )
+ return;
+ if( n.nIns()==0 )
+ return;
+ if( !(n.in(0)==null || n.in(0)==_start) )
+ return;
+ for( int i=1; i<n.nIns(); i++ ) {
+ Node def = n.in(i);
+ if( def==null ) continue;
+ if( def.isConst() ) continue;
+ if( def.nIns()==0 ) return;
+ if( def.in(0) != _start ) return;
+ }
+ n._inputs.set(0,_start);
+ }
+
// Walk all machine Nodes, and set their output edges
private void _instOuts( Node n, BitSet visit ) {
if( visit.get(n._nid) ) return;
@@ -1020,11 +1069,9 @@ public class CodeGen {
// Debugging helper
@Override public String toString() {
- if( _phase!=null && _phase.ordinal() > Phase.Schedule.ordinal() )
- return IRPrinter.prettyPrint( this );
if( _stop == null )
return "No StopNode";
- return _stop.p(4999);
+ return IRPrinter.prettyPrint(this);
}
// Debugging helper
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java
index 274be838..7af7e5f1 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java
@@ -317,10 +317,11 @@ public class ElfWriter {
}
// Write relocations for the constant pool
+ String cpoolPrefix = _code.entryClinitName().replaceAll("[^A-Za-z0-9_$]", "_") + "$CPOOL$";
for( Encoding.Relo relo : enc._bigCons.values() ) {
boolean ro = !(relo._t instanceof TypeStruct ts) || ts.isConstant();
DataSection data = ro ? rodata : rwdata;
- int symidx = symbols.symbol("CPOOL$"+symbols.gidx(), data._index, SYM_BIND_GLOBAL, SYM_TYPE_FUNC, relo._target, relo._t.size());
+ int symidx = symbols.symbol(cpoolPrefix+symbols.gidx(), data._index, SYM_BIND_GLOBAL, SYM_TYPE_FUNC, relo._target, relo._t.size());
relocations.writeReloOffPlus(relo._opStart+relo._off, symidx, relo._elf );
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
index fa187000..f13e8ae3 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
@@ -24,7 +24,7 @@ public abstract class GlobalCodeMotion {
schedEarly(code);
// Break up shared global constants by functions
- breakUpGlobalConstants(code._start );
+ breakUpGlobalConstants(code._start);
code._visit.clear();
schedLate(code);
@@ -44,32 +44,31 @@ public abstract class GlobalCodeMotion {
}
// Break up shared global constants by functions
- private static void breakUpGlobalConstants( Node start ) {
+ private static void breakUpGlobalConstants( StartNode start ) {
- // For all global constants
+ // For all explicitly Start-pinned global values. Instruction
+ // selection keeps each zero-code/global chain member hooked to Start,
+ // so no later pass has to infer hidden global chains from data inputs.
for( int i=0; i< start.nOuts(); i++ ) {
Node con = start.out(i);
- // Instruction selection can expand one shared TypeNode constant
- // into a constant-valued machine expression. Split either form
- // before scheduling, since separate functions have no CFG LCA.
- if( !(con instanceof CFGNode) &&
- ((con._type.isHighOrConst() && con.isConst()) ||
- con instanceof MachNode mach && mach.isClone() && con._type.isConstant()) ) {
- breakUpGlobalConstantSingle( con );
- i--; // Removed a global constant, re-run same index
+ if( isStartPinnedGlobalValue(con) ) {
+ breakUpGlobalConstantSingle(con);
+ if( con.nIns()==0 || con.in(0) != start )
+ i--; // Removed a global constant, re-run same index
}
}
}
private static void breakUpGlobalConstantSingle( Node con ) {
- // If an immediate global-value user still spans functions, it cannot
- // yet be assigned to one clone. Keeping the constant at Start is a
- // valid schedule; a downstream value with concrete ownership can be
- // split independently.
+ // Split dependent global values first; cloning a later chain member
+ // will recursively clone its Start-pinned inputs on demand.
for( Node use : con.outs() )
- if( use != null && useFun(use)==null )
- return;
+ if( use != null && useFun(use)==null ) {
+ if( !isStartPinnedGlobalValue(use) )
+ return;
+ breakUpGlobalConstantSingle(use);
+ }
// While constant has users in different functions
while( true ) {
// Find a function user, and another function
@@ -82,22 +81,16 @@ public abstract class GlobalCodeMotion {
if( fun==null || fun==fun2 ) fun=fun2;
else { done=false; break; }
}
+ // No direct function users: keep this global value at Start.
+ if( fun==null )
+ return;
// Single function user, so this constant is not shared
if( done ) {
con.setDef(0,fun);
return;
}
// Move function users to a private constant
- Node con2 = con.copy(); // Private constant clone
- // Node copies made through Node(Node) carry unregistered inputs,
- // while some machine copies construct fresh registered edges.
- // Detach correctly in either case before pinning to the function.
- Node old = con2.in(0);
- if( old != null && old._outputs.find(con2) != -1 )
- con2.setDef(0,null);
- else
- con2._inputs.set(0,null);
- con2.setDef(0,fun);
+ Node con2 = cloneGlobalForFun(con,fun,new IdentityHashMap<>());
// Move function users to this private constant
for( int j=0; j<con._outputs._len; j++ ) {
Node use = con.out(j);
@@ -112,6 +105,44 @@ public abstract class GlobalCodeMotion {
}
}
+ private static Node cloneGlobalForFun(Node con, FunNode fun, IdentityHashMap<Node,Node> memo) {
+ Node memoized = memo.get(con);
+ if( memoized != null ) return memoized;
+ Node con2 = con.copy(); // Private constant clone
+ // Node copies made through Node(Node) carry unregistered inputs, while
+ // some machine copies construct fresh registered edges. The default
+ // Node.copy path gives an input-less clone. Normalize all of them to
+ // the original data inputs, pinned to the owning function.
+ memo.put(con,con2);
+ if( con2.nIns()==0 ) {
+ con2.addDef(fun);
+ for( int i=1; i<con.nIns(); i++ )
+ con2.addDef(cloneInputForFun(con.in(i),fun,memo));
+ } else {
+ Node oldCtrl = con2.in(0);
+ if( oldCtrl != null && oldCtrl._outputs.find(con2) != -1 )
+ con2.setDef(0,null);
+ else
+ con2._inputs.set(0,null);
+ con2.setDef(0,fun);
+ for( int i=1; i<con2.nIns(); i++ )
+ con2.setDef(i,cloneInputForFun(con.in(i),fun,memo));
+ }
+ return con2;
+ }
+
+ private static Node cloneInputForFun(Node def, FunNode fun, IdentityHashMap<Node,Node> memo) {
+ return isStartPinnedGlobalValue(def)
+ ? cloneGlobalForFun(def,fun,memo)
+ : def;
+ }
+
+ private static boolean isStartPinnedGlobalValue(Node n) {
+ return n != null && n._type != null && !(n instanceof CFGNode) &&
+ n.nIns() > 0 && n.in(0) instanceof StartNode &&
+ (n.isConst() || n instanceof MachNode mach && mach.outregmap()!=null);
+ }
+
private static FunNode useFun(Node use) {
return useFun(use,new BitSet());
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/IFG.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/IFG.java
index 1d88597c..2b559a38 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/IFG.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/IFG.java
@@ -207,18 +207,11 @@ abstract public class IFG {
assert tlrg.leader();
// Always, tlrg cannot use kills
if( tlrg._mask.overlap(killMask) ) {
- // Disallow clone-ables from killing registers. Just fail
- // them and re-clone closer to target... so no kill.
- // Special case for Intel XOR used to zero.
- Node n = (Node)m;
- CFGNode effUseBlk = n.out(0) instanceof PhiNode phi ? phi.region().cfg(phi._inputs.find(n)) : n.out(0).cfg0();
- if( m.isClone() && // Must be clonable
- (n.nOuts()>1 || // Has many users OR
- // Only 1 user but effective use is remote block
- effUseBlk != n.cfg0() ))
- // Then fail the clonable; it should split or move
- alloc.fail(alloc.lrg((Node)m));
- // Else clonable cannot move
+ Node live = TMP.get(tlrg);
+ // Disallow clone-ables from living across killed registers.
+ // Fail them and let splitting re-clone closer to the target use.
+ if( live instanceof MachNode mach && mach.isClone() )
+ alloc.fail(tlrg);
else if( !tlrg.sub(killMask) )
alloc.fail(tlrg);
}
@@ -328,7 +321,7 @@ abstract public class IFG {
// Pull all lrgs from IFG, in trivial order if possible
while( sptr < color_stack.length ) {
// Swap best color up front
- pickColor(color_stack,sptr,swork);
+ pickColor(color_stack,sptr,swork,alloc);
// Pick a trivial lrg, and (temporarily) remove from the IFG.
LRG lrg = color_stack[sptr++];
@@ -384,10 +377,10 @@ abstract public class IFG {
}
// Pick LRG from color stack
- private static void pickColor(LRG[] color_stack, int sptr, int swork) {
+ private static void pickColor(LRG[] color_stack, int sptr, int swork, RegAlloc alloc) {
// Out of trivial colorable, pick an at-risk to pull
if( sptr==swork )
- swap(color_stack,sptr,pickRisky(color_stack,sptr));
+ swap(color_stack,sptr,pickRisky(color_stack,sptr,alloc));
// When coloring, we'd like to give more choices; so when coloring we'd
// like to see the single-def first (since no choices anyway), then
// non-split related (so more live ranges get colored), then
@@ -422,12 +415,12 @@ abstract public class IFG {
// Pick a live range that hasn't already spilled, or has a single-def-
// single-use that are not adjacent.
- private static int pickRisky( LRG[] color_stack, int sptr ) {
+ private static int pickRisky( LRG[] color_stack, int sptr, RegAlloc alloc ) {
int best=sptr;
- int bestScore = pickRiskyScore(color_stack[best]);
+ int bestScore = pickRiskyScore(color_stack[best],alloc);
for( int i=sptr+1; i<color_stack.length; i++ ) {
if( bestScore == 1000000 ) return best; // Already max score
- int iScore = pickRiskyScore(color_stack[i]);
+ int iScore = pickRiskyScore(color_stack[i],alloc);
if( iScore > bestScore )
{ best = i; bestScore = iScore; }
}
@@ -442,32 +435,55 @@ abstract public class IFG {
//
// Picking a live range that is very close to coloring might allow it to
// color despite being risky.
- private static int pickRiskyScore( LRG lrg ) {
+ private static int pickRiskyScore( LRG lrg, RegAlloc alloc ) {
+ // Callee-save live ranges cover the whole function and are cheap to
+ // split. Rank scheduled area recovered against the loop-scaled cost
+ // of the def- and use-side moves.
+ if( lrg._machDef instanceof CalleeSaveNode )
+ return areaScore(functionArea(lrg._fun,alloc),
+ splitCost((Node)lrg._machDef,(Node)lrg._machUse));
+ if( lrg._splitDef != null && lrg._splitDef. in(1) instanceof CalleeSaveNode &&
+ lrg._splitUse != null && lrg._splitUse.out(0) instanceof ReturnNode )
+ return areaScore(functionArea(lrg._fun,alloc),
+ splitCost(lrg._splitDef,lrg._splitUse));
+
// Pick single-def clonables that are not right next to their single-use.
- // Failing to color these will clone them closer to their uses.
+ // Rank by the area recovered by cloning closer to the use.
if( !lrg._multiDef && lrg._machDef.isClone() && lrg._machUse != null ) {
Node def = ((Node)lrg._machDef);
Node use = ((Node)lrg._machUse);
CFGNode cfg = def.cfg0();
- if( cfg != use.cfg0() || // Different blocks OR
- // Same block, but not close
- cfg._outputs.find(def) < cfg._outputs.find(use)+1 )
- return 1000000;
+ int area = cfg != use.cfg0()
+ ? functionArea(lrg._fun,alloc)
+ : cfg._outputs.find(use)-cfg._outputs.find(def)-1;
+ if( area > 0 )
+ return areaScore(area,loopCost(use));
}
- // Always pick callee-save registers as being very large area recovered
- // and very cheap to spill.
- if( lrg._machDef instanceof CalleeSaveNode )
- return 1000000-2-lrg._mask.firstReg();
- if( lrg._splitDef != null && lrg._splitDef. in(1) instanceof CalleeSaveNode &&
- lrg._splitUse != null && lrg._splitUse.out(0) instanceof ReturnNode )
- return 1000000-1;
-
- // TODO: cost/benefit model. Perhaps counting loop-depth (freq) of def/use for cost
- // and "area" for benefit
+ // Default for ranges without a specific area/cost estimate.
return 1000 + (lrg._multiUse ? -100 : 0);
}
+ private static int functionArea(FunNode fun, RegAlloc alloc) {
+ int area=0;
+ for( CFGNode bb : alloc._code._cfg )
+ if( bb.fun()==fun )
+ area += bb.nOuts();
+ return area;
+ }
+
+ private static int areaScore(int area, int cost) {
+ return 1000 + (area<<10)/cost;
+ }
+
+ private static int splitCost(Node def, Node use) {
+ return loopCost(def) + (use==null ? 0 : loopCost(use));
+ }
+
+ private static int loopCost(Node n) {
+ return 1 << (n.cfg0().loopDepth()*3);
+ }
+
private static short biasColor( RegAlloc alloc, LRG lrg, short reg, RegMask mask ) {
if( mask.size1() ) return reg;
// Check chain of splits up the def-chain. Take first allocated
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Opto.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Opto.java
index b88067c4..5d4c151d 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Opto.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Opto.java
@@ -127,6 +127,10 @@ abstract public class Opto {
private static void linkCG(CodeGen code, TypeFunPtr tfp, CallNode call) {
if( tfp.nargs() != call.nargs() ) return; // Error calls hit this
int[] fidxs = tfp.fidxs();
+ // An unresolved forward call can still carry the infinite, inferred
+ // function set into Opto. There is no finite call graph to link yet;
+ // type checking will report the unresolved reference afterwards.
+ if( XInt.isHigh(fidxs) ) return;
for( int fidx = XInt.next(fidxs,0); fidx >=0; fidx = XInt.next(fidxs,fidx) ) {
// unlinkStart deliberately leaves uncalled functions in a
// temporarily dead limbo. A newly discovered call revives them;
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java
index 191016be..90309328 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java
@@ -114,15 +114,32 @@ public abstract class ParseAll {
code._stop.walk( (Node n) -> {
n.upgradeType(Parser.TYPES);
if( n instanceof FRefNode fref ) {
- TypeStruct clz = (TypeStruct)Parser.TYPES.get(fref._name);
+ boolean alloc = fref._name.endsWith(".<new>");
+ String clzName = alloc
+ ? fref._name.substring(0,fref._name.length()-".<new>".length()).intern()
+ : fref._name;
+ TypeStruct clz = (TypeStruct)Parser.TYPES.get(clzName);
if( clz == null )
return null;
- // Class pointer:
- TypeMemPtr clzptr = TypeMemPtr.make((byte)2,clz,true);
- // Class pointer constant:
- ConstantNode clzCon = code.con(clzptr);
+ Node con;
+ if( alloc ) {
+ Field ctor = clz.field("<new>");
+ if( ctor == null || !(ctor._t instanceof TypeFunPtr tfp) ) {
+ String name = clzName.startsWith(Parser.CLZ)
+ ? clzName.substring(Parser.CLZ.length()) : clzName;
+ int dot = name.lastIndexOf('.');
+ throw Parser.error("Unknown struct type '"+(dot==-1 ? name : name.substring(dot+1))+"'",fref._loc);
+ }
+ FunNode fun = code.link(tfp);
+ if( fun == null ) return null;
+ con = new FunPtrNode(tfp,code._start,fun.ret()).peephole();
+ } else {
+ // Class pointer constant:
+ TypeMemPtr clzptr = TypeMemPtr.make((byte)2,clz,true);
+ con = code.con(clzptr);
+ }
// Replace and optimize
- fref.addDef(clzCon);
+ fref.addDef(con);
code.add(fref);
}
// Adds sort by NIDs, which just got shuffled
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java
index 4c00391c..0d5b9e7b 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java
@@ -61,6 +61,9 @@ public class RegAlloc {
// -----------------------
// Live ranges with self-conflicts or no allowed registers
private final IdentityHashMap<LRG,String> _failed = new IdentityHashMap<>();
+ // Loop Phis get one chance to color after only cold self-conflict splits.
+ // If the same Phi self-conflicts again, split its hot def/backedge also.
+ private final IdentityHashMap<PhiNode,String> _deferredLoopSelf = new IdentityHashMap<>();
void fail( LRG lrg ) {
assert lrg.leader();
_failed.put(lrg,"");
@@ -171,6 +174,8 @@ public class RegAlloc {
for( Node n : bb._outputs )
if( n instanceof NewNode nnn ) nnn.cacheRegs(_code);
}
+ // Expensive assert: useful when chasing cross-function allocator edges.
+ //assert verifyFunctionLocalEdges();
// Top driver: repeated rounds of coloring and splitting.
byte round=0;
@@ -184,6 +189,63 @@ public class RegAlloc {
_done = true;
}
+ private boolean verifyFunctionLocalEdges() {
+ for( CFGNode bb : _code._cfg )
+ for( Node use : bb._outputs ) {
+ FunNode useFun = funOf(use);
+ for( int i=1; i<use.nIns(); i++ ) {
+ Node def = use.in(i);
+ if( def==null || def.isConst() || allowedCrossFunctionEdge(use,i,def) )
+ continue;
+ FunNode defFun = funOf(def);
+ if( defFun != null && useFun != null && defFun != useFun )
+ throw new AssertionError(crossFunctionMessage(def,defFun,use,useFun,i));
+ }
+ }
+ return true;
+ }
+
+ private static String crossFunctionMessage(Node def, FunNode defFun, Node use, FunNode useFun, int idx) {
+ StringBuilder sb = new StringBuilder("Cross-function value edge: ");
+ sb.append(def.uniqueName()).append("(").append(defFun.label()).append(") -> ")
+ .append(use.uniqueName()).append("(").append(useFun.label()).append(") input ").append(idx)
+ .append(" def=").append(def).append(" use=").append(use).append("\n all uses:");
+ for( Node out : def.outs() ) {
+ FunNode outFun = funOf(out);
+ sb.append("\n ").append(out.uniqueName()).append("(")
+ .append(outFun==null ? "?" : outFun.label()).append(")");
+ }
+ return sb.toString();
+ }
+
+ private static boolean allowedCrossFunctionEdge(Node use, int idx, Node def) {
+ // Function metadata/control edges are not value live ranges.
+ if( use instanceof FunNode || use instanceof StopNode || use instanceof StopCUNode )
+ return true;
+ // Return-linked function pointers carry function identity/lifetime, not
+ // an allocator value flowing from the callee body into this function.
+ if( use instanceof FunPtrNode && def instanceof ReturnNode )
+ return true;
+ // Parm defaults and call-linked argument edges are the entry protocol;
+ // BuildLRG also skips Parm input 1.
+ if( use instanceof ParmNode )
+ return true;
+ // Linked CallEnd -> Return edges should be gone before RegAlloc, but
+ // diagnose allocator value edges rather than call graph metadata here.
+ if( use instanceof CallEndNode && def instanceof ReturnNode )
+ return true;
+ return false;
+ }
+
+ private static FunNode funOf(Node n) {
+ if( n instanceof ParmNode parm ) return parm.fun();
+ if( n instanceof ReturnNode ret ) return ret.fun();
+ if( n instanceof FunNode fun ) return fun;
+ if( n instanceof CFGNode cfg ) return cfg.fun();
+ CFGNode cfg = n.cfg0();
+ return cfg==null ? null : cfg.fun();
+ }
+
private boolean graphColor(byte round) {
_failed.clear();
_lrgs.clear();
@@ -227,7 +289,7 @@ public class RegAlloc {
// Register mask when empty; split around defs and uses with limited
// register masks.
- if( lrg._mask.isEmpty() && (!lrg._multiDef || lrg._1regUseCnt==1) ) {
+ if( lrg._mask.isEmpty() ) {
if( lrg._1regDefCnt <= 1 &&
lrg._1regUseCnt <= 1 &&
(lrg._1regDefCnt + lrg._1regUseCnt) > 0 )
@@ -238,6 +300,10 @@ public class RegAlloc {
if( splitEmptyMaskByUse(round,lrg) )
return true;
}
+ if( lrg._mask.isEmpty() && lrg._multiDef && lrg._1regUseCnt > 1 && lrg._1regDefCnt <= 1 ) {
+ if( splitEmptyMaskSingleUses(round,lrg) )
+ return true;
+ }
// Generic split-by-loop depth.
return splitByLoop(round,lrg);
@@ -322,6 +388,26 @@ public class RegAlloc {
return true;
}
+ // Empty-mask live range with several direct single-register uses, often
+ // through a Phi with multiple defs. Split those uses directly instead of
+ // doing a loop-depth split, which might keep isolating the wrong edge.
+ boolean splitEmptyMaskSingleUses( byte round, LRG lrg ) {
+ findAllLRG(lrg);
+ if( _ns._len <= 5 )
+ return false;
+ boolean progress = false;
+ for( Node n : _ns ) {
+ if( !(n instanceof MachNode mach) ) continue;
+ for( int i=1; i<n.nIns(); i++ ) {
+ RegMask use = mach.regmap(i);
+ if( use != null && use.size1() && lrg(n.in(i))==lrg ) {
+ insertBefore(n,i,"use/emptyN",round,lrg);
+ progress = true;
+ }
+ }
+ }
+ return progress;
+ }
// Put use into a register class, perhaps adding a class or perhaps
// narrowing a class (and causing a repeat)
@@ -346,6 +432,19 @@ public class RegAlloc {
Node[] conflicts = lrg._selfConflicts.keySet().toArray(new Node[0]);
Arrays.sort(conflicts, (x,y) -> x._nid - y._nid );
+ // First encounter with a loop Phi: try only cold splits this round.
+ // Remember the Phi across rounds so a persistent self-conflict forces
+ // the hot def/backedge splits on the next encounter.
+ IdentityHashMap<PhiNode,String> deferred = new IdentityHashMap<>();
+ for( Node def : conflicts ) {
+ if( def instanceof PhiNode phi && phi.region() instanceof LoopNode )
+ deferLoopSelf(phi,deferred);
+ for( Node use : def._outputs )
+ if( use instanceof PhiNode phi && phi.region() instanceof LoopNode &&
+ phi._inputs.find(def)==2 )
+ deferLoopSelf(phi,deferred);
+ }
+
// For all conflicts
for( Node def : conflicts ) {
assert lrg(def)==lrg; // Might be conflict use-side
@@ -354,6 +453,7 @@ public class RegAlloc {
for( int i=0; i<def._outputs._len; i++ ) {
Node use = def.out(i);
if( (use instanceof PhiNode phi &&
+ !(deferred.containsKey(phi) && phi.region() instanceof LoopNode && phi._inputs.find(def)==2) &&
!(phi.region() instanceof LoopNode loop && phi.in(2)==def && def.cfg0().idepth() > loop.idepth() ) ) ||
(use instanceof MachNode mach && mach.twoAddress()!=0 && use.in(mach.twoAddress())==def) )
insertBefore( use, use._inputs.find(def), "use/self/use",round,lrg );
@@ -362,10 +462,12 @@ public class RegAlloc {
// Phi slot 1 (and not all inputs), because Phis extend the live range.
// TODO: split before all inputs (except the last; at least 1 split here must be extra)
if( def instanceof PhiNode phi && !(def instanceof ParmNode) ) {
- SplitNode split = makeSplit("def/self",round,lrg);
- insertAfterAndReplace(split,def,false);
- if( split.nOuts()==0 )
- split.killOrdered();
+ if( !deferred.containsKey(phi) ) {
+ SplitNode split = makeSplit("def/self",round,lrg);
+ insertAfterAndReplace(split,def,false);
+ if( split.nOuts()==0 )
+ split.killOrdered();
+ }
insertBefore(phi,1,"use/self/phi",round,lrg);
}
// Split before two-address ops which extend the live range
@@ -375,6 +477,13 @@ public class RegAlloc {
return true;
}
+ private void deferLoopSelf(PhiNode phi, IdentityHashMap<PhiNode,String> deferred) {
+ if( !_deferredLoopSelf.containsKey(phi) ) {
+ _deferredLoopSelf.put(phi,"");
+ deferred.put(phi,"");
+ }
+ }
+
// Generic: split around the outermost loop with non-split def/uses. This
// covers both self-conflicts (once we split deep enough) and register
@@ -401,9 +510,11 @@ public class RegAlloc {
int min = (int)ld;
int max = (int)(ld>>32);
-
- // If the minLoopDepth is less than the maxLoopDepth: for-all defs and
- // uses, if at minLoopDepth or lower, split after def and before use.
+ boolean emptyMulti = lrg._mask.isEmpty() && lrg._multiDef && lrg._1regDefCnt <= 1 &&
+ (lrg._1regUseCnt > 1 || _ns._len > 5);
+ // Multi-def empty-mask conflicts need the hot/deep side isolated from
+ // fixed register uses. Ordinary pressure splits keep the old shallow
+ // boundary behavior.
for( Node n : _ns ) {
if( n instanceof SplitNode && min!=max ) continue; // Ignoring splits; since spilling need to split in a deeper loop
if( n.isDead() ) continue; // Some Cloneable went dead by other spill changes
@@ -414,7 +525,7 @@ public class RegAlloc {
if( lrg(n)==lrg && // This is a LRG def
// At loop boundary, or splitting in inner loop
- (min==max || n.cfg0().loopDepth() <= min) ) {
+ (min==max || (emptyMulti ? n.cfg0().loopDepth() >= max : n.cfg0().loopDepth() <= min)) ) {
// Cloneable constants will be cloned at uses, not after def
if( !(n instanceof MachNode mach && mach.isClone()) &&
// Single user is already a split adjacent
@@ -429,7 +540,7 @@ public class RegAlloc {
// No split in front of a split
if( !(n.in(i) instanceof SplitNode) &&
// splitting in inner loop or at loop border
- (min==max || phi.region().cfg(i).loopDepth() <= min) &&
+ (min==max || (emptyMulti ? phi.region().cfg(i).loopDepth() >= max : phi.region().cfg(i).loopDepth() <= min)) &&
// and not around the backedge of a loop (bad place to force a split, hard to remove)
!(phi.region() instanceof LoopNode && i==2 && (phi.in(i) instanceof PhiNode pp && pp.region()==phi.region())) )
// Split before phi-use in prior block
@@ -441,7 +552,8 @@ public class RegAlloc {
// This is a LRG use
// splitting in inner loop or at loop border
if( lrgSame( n.in( i ), lrg ) &&
- (min == max || (n.in(i) instanceof MachNode mach && mach.isClone()) || n.cfg0().loopDepth() <= min) )
+ (min == max || (n.in(i) instanceof MachNode mach && mach.isClone()) ||
+ (emptyMulti ? n.cfg0().loopDepth() >= max : n.cfg0().loopDepth() <= min)) )
// Split before in this block
insertBefore( n, i, "use/loop/use", round,lrg, false );
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Serialize.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Serialize.java
index 8014bb2c..7c230f9c 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Serialize.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Serialize.java
@@ -177,7 +177,6 @@ abstract public class Serialize {
IdentityHashMap<Node,Integer> anodes = new IdentityHashMap<>();
for( Node n : nodes )
anodes.put(n,anodes.size()+1); // +1 bias, reserve 0 for null
-
// First cut: 1 byte opcode, optional per-node info, includes variable nIns
for( Node n : nodes ) {
baos.packed1(n.serialTag().ordinal());
@@ -367,9 +366,11 @@ abstract public class Serialize {
// MemMerge precise aliases are encoded by their input slot.
// Remap populated file-local slots into this compilation's
// local alias numbering; null holes carry no identity.
- int local = n instanceof MemMergeNode && i >= GlobalBits.RESERVED && def != null
- ? aliases.map(fileAliases,i)
- : i;
+ int local = i;
+ if( n instanceof MemMergeNode && i >= GlobalBits.RESERVED ) {
+ if( def == null ) continue;
+ local = aliases.map(fileAliases,i);
+ }
n.setDefX(local,def);
// Call's output[0] is always the CallEnd
if( n instanceof CallEndNode cend && i==0 ) {
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
index 8e7f47f3..061232c7 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
@@ -16,6 +16,12 @@ public class CallEndNode extends CFGNode implements MultiNode {
// When set true, this Call/CallEnd/Fun/Return is being trivially inlined
boolean _folding;
+ // +2 must clone, so await all cleanup but OK
+ // +1 trivial now,
+ // 0: take a look,
+ // -1: somethings wrong, but might improve
+ // -2: never and stop asking,
+ private byte _inline;
public final TypeRPC _rpc;
public CallEndNode(CallNode call, TypeRPC rpc) {
@@ -51,6 +57,7 @@ public class CallEndNode extends CFGNode implements MultiNode {
public CallNode call() { return (CallNode)in(0); }
boolean folding() { return _folding; }
+ public byte inline() { return _inline; }
@Override public CFGNode idom(Node dep) {
// Folding the idom is the one inlining Return
@@ -101,14 +108,10 @@ public class CallEndNode extends CFGNode implements MultiNode {
// appear later - meanwhile, we use a conservative approx of memory
// effects.
if( (nIns()-1)+externs < tfp.nfcns() ) {
- // If before SCCP, we might call extras or also the unknown target.
- StartNode start = CodeGen.CODE._start;
- addDep(start);
- Type tmem = start._type instanceof TypeTuple tt ? tt._types[1] : (start._type.isHigh() ? TypeMem.TOP : TypeMem.BOT);
- Type ret = tfp.ret();
// If during Opto, assume call won't be called and thus won't return anything.
- if( CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Opto.ordinal() )
- ret = Type.TOP;
+ boolean opto = CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Opto.ordinal();
+ Type tmem = opto ? TypeMem.TOP : TypeMem.BOT;
+ Type ret = opto ? Type.TOP : tfp.ret();
return TypeTuple.make(Type.CONTROL,tmem,ret);
}
@@ -118,6 +121,12 @@ public class CallEndNode extends CFGNode implements MultiNode {
: TypeTuple.STATE.dual();
for( int i=1; i<nIns(); i++ )
state = (TypeTuple)state.meet(in(i)._type);
+ // A reachable call whose presently linked exits are all unreachable
+ // keeps a conservative continuation. In particular, do not let a
+ // transient no-return result tear down the caller during SCCP.
+ if( state.ctl()==Type.XCONTROL &&
+ CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Opto.ordinal() )
+ return TypeTuple.make(Type.CONTROL,TypeMem.TOP,Type.TOP);
// At least as good as the TFP
return state.makeFrom(2,state.ret().join(tfp.ret()));
}
@@ -125,125 +134,112 @@ public class CallEndNode extends CFGNode implements MultiNode {
@Override
public Node idealize() {
- // Worklist-based inlining. Cannot inline if folding, or calling
- // multiple targets or no CallNode (malformed because dying).
- if( !_folding && nIns()==2 && in(0) instanceof CallNode call ) {
- Node fptr = call.fptr();
- if( fptr.isConst() && // We have an immediate call
- // Function is being called, and its not-null
- fptr._type instanceof TypeFunPtr tfp && tfp.notNull() && !tfp.isHigh() &&
- // Arguments are correct
- call.nargs()==tfp.nargs() && goodArgs(call,tfp) ) {
- ReturnNode ret = (ReturnNode)in(1);
- FunNode fun = ret.fun();
- int isTrivial = trivialInlining( fptr, fun );
-
- // Encouraged inlining because small size and constructor.
- int maxSize = fun._name!=null && fun.isInit() && !fun.isClz() ? 200 : 100;
- if( isTrivial==1 && fun._approxUIDs < maxSize ) {
- assert fun.sig().isa(tfp);
- assert !CodeGen.CODE._midAssert; // Triggered inlining
- CodeGen.CODE.add(fun);
- // Remove the existing function linkage
- call.unlink_all();
- // Clone the function body
- FunNode fun2 = fun.copyBody();
- // Call uses the unique new function
- Node fptr2 = new FunPtrNode(fun2.sig(),CodeGen.CODE._start,fun2.ret()).peephole();
- call.setDef(call.nIns()-1,fptr2);
- // Link to the new function
- call.link(fun2);
- fun = fun2;
- assert trivialInlining( fptr2, fun2 )==0;
- isTrivial = 0;
- }
-
- // Trivial inlining: call site calls a single function; single function
- // is only called by this call site.
- if( isTrivial==0 )
- return doTrivialInlining(fun);
-
- } else { // Function not (yet) a constant function pointer
- addDep(fptr);
- }
+ _inline = inlineCandidate();
+ if( _inline == 1 ) {
+ ReturnNode ret = (ReturnNode)in(1);
+ FunNode fun = ret.fun();
+ doTrivialInlining(fun);
+ return this;
}
+ // Delay for cleanup, before cloning
+ if( _inline >= 2 )
+ CodeGen.CODE._iter.deferInline(CodeGen.CODE,this);
return null;
}
- boolean goodArgs( CallNode call, TypeFunPtr tfp ) {
- for( int i=0; i<tfp.nargs(); i++ ) {
- Node arg = call.arg(i+2);
- // The graph's function-pointer type can still contain open forward
- // references here. Compare against its recursively upgraded form,
- // so deep mutability requirements participate before inlining.
- Type formal = tfp.arg(i).upgradeType(Parser.TYPES);
- if( !arg._type.isa(formal) )
- { addDep(arg); return false; }
- }
- return true;
- }
+ // Check if candidate right now, sets no flags
+ private byte inlineCandidate() {
+ if( _inline < -2 ) return -2; // Never
+ if( _folding ) return -2; // No, and will collapse anyways
+ if( !(in(0) instanceof CallNode call) ) return -2; // malformed because mid-death
+ if( nIns()!=2 ) return -1; // Not exactly 1 link, but can change
+
+ // Have exactly 1 linked function
+ ReturnNode ret = (ReturnNode)in(1);
+ FunNode fun = ret.fun();
- // Check for trivial inlining: call only calls fun; fun only called by
- // call. Returns 0 for trivial, +1 for not-trivial because fptr/fun, and
- // -1 for not-trivial because idoms.
- public int trivialInlining( Node fptr, FunNode fun ) {
// Heuristic forced inlining off via name
if( fun._name != null &&
fun._name.endsWith("_noInline") )
- return -1;
-
- if( !fun.sig().isa(fptr._type) )
- return -1; // Stall until these align
+ return -2; // Never, stop asking
// Disallow self-recursive inlining (loop unrolling by another name).
for( int i=1; i < fun.nIns(); i++ )
if( fun.cfg(i).fun()==fun ) // Check for linked call input inside "fun"
- { addDep(fun); return -1; }
-
- CFGNode idom = call(), prior = this;
- while( !(idom instanceof FunNode) ) {
- if( idom==null ) {
- addDep(prior);
- if( prior instanceof RegionNode && !(prior instanceof StartNode) )
- for( int i=1; i<prior.nIns(); i++ )
- addDep(prior.in(i));
- return -1;
- }
- prior = idom;
- idom = idom.idom();
- }
-
+ { addDep(fun); return -1; } // No, but can lose the recursive edge
+
+ // Things that might improve, so not now a candidate
+ Node fptr = call.fptr();
+ // Constant Function is being called, and its not-null
+ if( !(fptr._type instanceof TypeFunPtr tfp && tfp.notNull() && tfp.isConstant()) )
+ { addDep(fptr); return -1; }
+
+ // Arguments are correct
+ if( call.nargs()!=tfp.nargs() || !goodArgs(call,tfp) )
+ { addDep(fptr); return -1; }
+
+ // Encouraged inlining because small size and constructor.
+ int maxSize = fun._name!=null && fun.isInit() && !fun.isClz() ? 200 : 100;
+ if( fun._approxUIDs >= maxSize )
+ return -2; // Try later (if shrinking helps)
+ if( fun.body().cardinality() >= maxSize )
+ return -1; // Try later (if cleanup makes the real body small enough)
+
+ // Can be cloned but not trivial
+ if( fun.nIns() > 2 ) { addDep(fun); return 2; }
+
+ // A candidate right now
+ return 1;
+ }
- // If the *inlined* function is mid-collapse, also do not inline (yet)
- idom = fun.ret();
- while( idom != fun ) {
- if( idom==null ) return -1; // Forced off, half-folded call
- if( idom instanceof FunNode fun2 )
- { addDep(fun2); return -1; }
- if( idom instanceof CallEndNode cend && cend._folding )
- { addDep(cend); return -1; }
- idom = idom.idom();
+ public void doInline() {
+ assert _inline > 0; // Only called when possible to inline
+
+ ReturnNode ret = (ReturnNode)in(1);
+ FunNode fun = ret.fun();
+ CallNode call = call();
+ assert fun.sig().isa(call.fptr()._type);
+
+ // Clone first
+ if( _inline==2 ) {
+ assert !CodeGen.CODE._midAssert; // Triggered inlining
+ // Remove the existing function linkage
+ call.unlink_all();
+ // Clone the function body
+ FunNode fun2 = fun.copyBody();
+ // Call uses the unique new function
+ Node fptr2 = new FunPtrNode(fun2.sig(),CodeGen.CODE._start,fun2.ret()).peephole();
+ call.setDef(call.nIns()-1,fptr2);
+ // Link to the new function
+ call.link(fun2);
+ fun = fun2;
}
- // Only fun user is this call
- if( fun.nIns() > 2 ) { addDep(fun); return 1; }
// Trivial inlining: call site calls a single function; single function
// is only called by this call site.
- assert fun.in(1)==call();
+ doTrivialInlining(fun);
+ }
- return 0;
+ boolean goodArgs( CallNode call, TypeFunPtr tfp ) {
+ for( int i=0; i<tfp.nargs(); i++ ) {
+ Node arg = call.arg(i+2);
+ // The graph's function-pointer type can still contain open forward
+ // references here. Compare against its recursively upgraded form,
+ // so deep mutability requirements participate before inlining.
+ Type formal = tfp.arg(i).upgradeType(Parser.TYPES);
+ if( !arg._type.isa(formal) )
+ { addDep(arg); return false; }
+ }
+ return true;
}
// Do trivial inlining. Inlining does not need to clone code, merely
// triggers folding the Call/Fun and Return/CallEnd away.
- private Node doTrivialInlining( FunNode fun ) {
+ private boolean doTrivialInlining( FunNode fun ) {
assert !CodeGen.CODE._midAssert; // Triggered inlining
- // Add the size heuristic to grow caller, preventing self-recursive
- // functions from endlessly inlining.
- fun()._approxUIDs += fun._approxUIDs;
// Trivial inline: rewrite
_folding = true;
// Rewrite Fun so the normal RegionNode ideal collapses
@@ -260,7 +256,12 @@ public class CallEndNode extends CFGNode implements MultiNode {
// Inlining immediately blows all cache idepth fields past the inline point.
// Bump the global version number invalidating them en-masse.
CodeGen.CODE.invalidateIDepthCaches();
- return this;
+ // More peeps
+ CodeGen.CODE.add(fun);
+ CodeGen.CODE.add(this);
+ CodeGen.CODE.addAll(_outputs);
+ CodeGen.CODE.addAll(_inputs);
+ return true; // Always return true
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/CallNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CallNode.java
index f2825650..90d7e379 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/CallNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CallNode.java
@@ -5,6 +5,7 @@ import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeFunPtr;
import com.seaofnodes.simple.type.TypeMem;
+import com.seaofnodes.simple.type.TypeRPC;
import com.seaofnodes.simple.type.XInt;
import com.seaofnodes.simple.util.BAOS;
import com.seaofnodes.simple.util.Utils;
@@ -129,8 +130,15 @@ public class CallNode extends CFGNode {
assert !linked(fun);
fun.addDef(this);
for( Node use : fun._outputs )
- if( use instanceof ParmNode parm )
- parm.addDef(parm._idx==0 ? ConstantNode.seed(cend()._rpc).peephole() : arg(parm._idx));
+ if( use instanceof ParmNode parm ) {
+ Node def = parm._idx==0 ? ConstantNode.seed(cend()._rpc).peephole() : arg(parm._idx);
+ parm.addDef(def);
+ // Linking can happen during post-SCCP inlining. Keep an RPC
+ // Parm's cached finite set synchronized with its newly added
+ // input instead of waiting for the next worklist visit.
+ if( parm._idx==0 && parm._type instanceof TypeRPC )
+ parm._type = parm._type.meet(def._type);
+ }
// Call end points to function return
CodeGen.CODE.add(cend()).addDef(fun.ret());
return this;
@@ -168,7 +176,7 @@ public class CallNode extends CFGNode {
if( fptr()._type == Type.BOTTOM )
return null; // Wrong, but failing earlier
if( !(fptr()._type instanceof TypeFunPtr tfp) )
- throw Utils.TODO();
+ return Parser.error("Expected a function, but found " + fptr()._type, _loc);
if( !tfp.notNull() )
return Parser.error( "Might be null calling "+tfp, _loc);
if( nargs() != tfp.nargs() )
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/EscapeNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/EscapeNode.java
index df654291..d1ff3f65 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/EscapeNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/EscapeNode.java
@@ -70,7 +70,7 @@ public class EscapeNode extends TypeNode {
Type tpub = mpub._t;
// t is meet of pub & priv. Pub memory can be very weak (e.g. alias 1)
- // and we're no worse than GLB of private
+ // and we're no worse than the declared field type
Type t = tpriv.meet(tpub).join(fld._t);
int[] fidxes = XInt.meet(mpriv._escFs, mpub._escFs);
int[] aliases = XInt.meet(mpriv._escAs, mpub._escAs);
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/FRefNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/FRefNode.java
index 723e0007..698ba755 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/FRefNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/FRefNode.java
@@ -23,7 +23,12 @@ public class FRefNode extends ConstantNode {
public String _name;
public final Parser.Lexer _loc; // Source location
- public FRefNode( String name, Parser.Lexer loc ) { super(FREF_TYPE); _name = name; _loc = loc; }
+ public FRefNode( String name, Parser.Lexer loc ) { this(name,loc,FREF_TYPE); }
+ public FRefNode( String name, Parser.Lexer loc, Type type ) {
+ super(type);
+ _name = name;
+ _loc = loc;
+ }
@Override public String label() { return "FRef"+_name; }
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/FunNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/FunNode.java
index 5018f5d6..419cf083 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/FunNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/FunNode.java
@@ -91,7 +91,7 @@ public class FunNode extends RegionNode {
// Signature can improve over time
public TypeFunPtr sig() { return _sig; }
public void setSig( TypeFunPtr sig ) {
- assert sig.isa(_sig);
+ assert sig.isa(_sig) || resolvesForwardSelf(sig,_sig);
if( _sig != sig ) {
CODE.add(this);
// Changing the signature can allow more inlining
@@ -104,6 +104,20 @@ public class FunNode extends RegionNode {
}
}
+ // A class initializer starts parsing with an open singleton receiver and
+ // closes that same receiver after discovering its fields. Function
+ // arguments are contravariant, so ordinary isa does not describe this
+ // parser-time forward-reference resolution even though every other part
+ // of the signature sharpens normally.
+ private static boolean resolvesForwardSelf(TypeFunPtr sig, TypeFunPtr old) {
+ if( !(sig.arg(0) instanceof TypeMemPtr now) ||
+ !(old.arg(0) instanceof TypeMemPtr prior) ||
+ now._obj._name != prior._obj._name ||
+ !prior._obj._open || now._obj._open )
+ return false;
+ return sig.makeFrom(old.arg(0),0).isa(old);
+ }
+
@Override boolean _upgradeType( HashMap<String,Type> TYPES) {
TypeFunPtr sig = (TypeFunPtr)_sig.upgradeType(TYPES);
if( sig == _sig ) return false;
@@ -323,12 +337,20 @@ public class FunNode extends RegionNode {
ReturnNode ret2 = (ReturnNode)map.get(_ret);
fun2._ret = ret2;
ret2._fun = fun2;
+ BitSet body2 = new BitSet();
+ for( Node n : map.values() )
+ body2.set(n._nid);
+
+ // Remove non-body callers. Body-local call edges were cloned along
+ // with their CallEnds and must stay linked so their cloned types remain
+ // monotonic during the post-Opto Iter.
+ for( int i=1; i<fun2.nIns(); i++ )
+ if( !(fun2.in(i) instanceof CallNode call) || !body2.get(call._nid) )
+ fun2.removeDeadPath(i--);
- // Remove all callers - this is a private copy
- while( fun2.nIns() > 1 )
- if( fun2.in(1) instanceof CallNode call && body.get(call._nid) )
- call.unlink(fun2,1); // Recursive calls unlink
- else fun2.removeDeadPath(1); // Start and external calls can just be removed
+ for( Node old : map.keySet() )
+ if( old instanceof CallNode oldCall )
+ relinkClonedCall(oldCall,(CallNode)map.get(oldCall),map);
// Flip to a new FIDX to avoid confusion with the old one.
// This is a non-monotonic (sideways) type move, only applicable
@@ -338,6 +360,23 @@ public class FunNode extends RegionNode {
return fun2;
}
+ private static void relinkClonedCall( CallNode oldCall, CallNode call, IdentityHashMap<Node,Node> map ) {
+ CallEndNode oldCend = oldCall.cend();
+ if( oldCend==null || oldCend._folding )
+ return;
+ for( int i=1; i<oldCend.nIns(); i++ ) {
+ ReturnNode oldRet = (ReturnNode)oldCend.in(i);
+ FunNode oldFun = oldRet.fun();
+ FunNode fun = (FunNode)map.get(oldFun);
+ if( fun==null ) fun = oldFun;
+ if( !call.linked(fun) )
+ call.link(fun);
+ }
+ CallEndNode cend = call.cend();
+ if( cend.nIns() > 1 )
+ cend._type = cend.compute();
+ }
+
// Clone small function
private void bodyCopy( BitSet visit, BitSet body, IdentityHashMap<Node,Node> map, Node n ) {
if( n==null ) return;
@@ -357,18 +396,20 @@ public class FunNode extends RegionNode {
if( visit.get(n._nid) ) return; // Been there, done that
visit.set(n._nid);
Node m = map.get(n);
- // The cloned Call should not clone any linked edges.
- if( n instanceof CallEndNode cend && !cend._folding ) {
- m.addDef( map.get(cend.call()) );
- } else {
- for( Node e : n._inputs ) {
- Node x = e;
- if( e != null ) {
- x = map.get(e);
- if( x == null ) x = e;
+ for( Node e : n._inputs ) {
+ Node x = e;
+ if( e != null ) {
+ if( n instanceof CallEndNode cend && e != cend.call() &&
+ (!(e instanceof ReturnNode ret) || map.get(ret.fun()) == null) )
+ continue;
+ x = map.get(e);
+ if( x == null ) {
+ if( n instanceof CallEndNode cend && e != cend.call() )
+ continue;
+ x = e;
}
- m.addDef(x);
}
+ m.addDef(x);
}
for( Node x : n._outputs )
bodyEdge(visit,body,map,x);
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java
index f67f2b97..4ff2bd82 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java
@@ -102,8 +102,7 @@ public class LoadNode extends MemOpNode {
// baked into the type system. Loads into arrays DO need the ctrl
// edge, at least until proper range-checking is in place.
if( in(0)!=null && ptr()._type instanceof TypeMemPtr tmp &&
- // Never can be an array
- (!tmp._obj._open || (tmp._obj._fields.length > 1 && tmp._obj._fields[0]._fname != "#")) ) {
+ !tmp._obj.isAry() ) {
setDef(0,null);
return this;
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
index a88748b5..e4e7d607 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
@@ -113,7 +113,8 @@ public class MemMergeNode extends Node {
if( XInt.bit(mem._escAs,i) ) {
Node n = in(i);
if( n != null && !n._type.isHigh() ) {
- TypeMem mem2 = (TypeMem)mem.meet(n._type);
+ TypeMem nmem2 = ((TypeMem)n._type).makeFrom(mem._escFs, mem._escAs);
+ TypeMem mem2 = (TypeMem)mem.meet(nmem2);
// More escape aliases
if( mem2 != mem )
{ mem = mem2; progress = true; }
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
index d824bc3a..59f7c304 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
@@ -43,15 +43,15 @@ public abstract class MemOpNode extends TypeNode {
// Source location for late reported errors
public final Parser.Lexer _loc;
- public MemOpNode(Parser.Lexer loc, String name, int alias, boolean isLoad, Type glb, Node ctrl, Node mem, Node ptr, Node off) {
- super(glb, ctrl, mem, ptr, off);
+ public MemOpNode(Parser.Lexer loc, String name, int alias, boolean isLoad, Type decl, Node ctrl, Node mem, Node ptr, Node off) {
+ super(decl, ctrl, mem, ptr, off);
_name = name;
_alias = alias;
_loc = loc;
_isLoad = isLoad;
}
- public MemOpNode(Parser.Lexer loc, String name, int alias, boolean isLoad, Type glb, Node ctrl, Node mem, Node ptr, Node off, Node value) {
- this(loc, name, alias, isLoad, glb, ctrl, mem, ptr, off);
+ public MemOpNode(Parser.Lexer loc, String name, int alias, boolean isLoad, Type decl, Node ctrl, Node mem, Node ptr, Node off, Node value) {
+ this(loc, name, alias, isLoad, decl, ctrl, mem, ptr, off);
addDef(value);
}
public MemOpNode( Node ideal, MemOpNode mop ) {
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ParmNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ParmNode.java
index 6488b827..51ea28b0 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/ParmNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ParmNode.java
@@ -73,7 +73,9 @@ public class ParmNode extends PhiNode {
@Override
public Node idealize() {
if( !(region() instanceof FunNode) )
- return in(1); // Input has collapse to e.g. starting control.
+ // A dead function can lose every caller before the Parm itself is
+ // removed; there is no replacement input in that transient form.
+ return nIns()==1 ? null : in(1); // Input has collapse to e.g. starting control.
// If function is folding, do all possible peeps
if( fun()._folding ) return super.idealize();
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/PhiNode.java
index 2c57eb8b..2ae3c752 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/PhiNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/PhiNode.java
@@ -76,7 +76,7 @@ public class PhiNode extends Node {
if( !(region() instanceof RegionNode r) )
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() )
+ if( r.inProgress() || in(nIns()-1)==null )
return declaredType();
// Set type to local top of the starting type
Type t = Type.TOP;
@@ -219,7 +219,7 @@ public class PhiNode extends Node {
if( in(i).in(j) != x )
{ needsPhi=true; break; }
if( needsPhi ) {
- x = make(_label,op.in(j)._type.glb(false));
+ x = make(_label,op.in(j)._type);
x.addDef(region());
for( int i=1; i<nIns(); i++ )
x.addDef(in(i).in(j));
@@ -273,13 +273,12 @@ public class PhiNode extends Node {
@Override
public Parser.ParseException err() {
- if( _type != Type.BOTTOM && _type != TypeScalar.BOT ) return null;
+ if( _type != Type.BOTTOM ) return null;
- // Scalar BOT means we mixed scalar families, e.g. int and ptr.
// Global BOTTOM retains the same role for non-scalar families.
for( int i=1; i<nIns(); i++ )
// Already an error, but better error messages come from elsewhere
- if( in(i)._type == Type.BOTTOM || in(i)._type == TypeScalar.BOT )
+ if( in(i)._type == Type.BOTTOM )
return null;
SB sb = new SB().p("No common type amongst ");
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
index b60d27d7..0dc14fd2 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
@@ -67,6 +67,9 @@ public class ReturnNode extends CFGNode {
if( ctrl()._type == Type.CONTROL &&
expr()._type == Type.TOP )
return Parser.error("No defined return type",null);
+ if( ctrl()._type == Type.CONTROL &&
+ expr()._type == TypeScalar.BOT )
+ return Parser.error("Cannot return generic scalar",null);
// With no user constructor, <init> doubles as the public no-arg
// constructor. Required fields remain TOP in its private memory: a
// typed parser poison which deliberately emitted no Store or code.
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
index 58508a1c..dd5fa541 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
@@ -240,20 +240,16 @@ public class ScopeNode extends MemMergeNode {
return true;
}
- // Normal lookup was tried and failed. Insert a forward ref outside any
- // enclosing Kind.Func scope, skipping any nested Blocks or Allocs and
- // return the Var. May insert at the outermost scope, which means this
- // must be defined externally, or it's an error.
+ // Normal lookup was tried and failed. Append a forward ref in the current
+ // lexical scope. Scope pops promote it outwards "as if" the final constant
+ // had been declared before the using scope.
public Var defineFRef( String id, Type t, boolean xfinal, Parser.Lexer loc ) {
- // Kind/Lexical scope index
- int kidx = enclosingFunction();
- int idx = _kinds.at(kidx)._lexSize;
- Var var = new Var(idx,id,t,xfinal,loc,true);
+ Var var = new Var(nIns(),id,t,xfinal,loc,true);
FRefNode fref = new FRefNode(id,loc).init();
fref._type = fref._con = t;
- // Insert in the lex scope just prior to kidx
- insert(var,fref,kidx);
+ _vars.add(var);
+ addDef(fref);
return var;
}
@@ -406,6 +402,18 @@ public class ScopeNode extends MemMergeNode {
}
}
+ public void balanceLoopFRefs( ScopeNode scope ) {
+ for( int i = nIns(); i < scope.nIns(); i++ ) {
+ Var n = scope.var(i);
+ if( n.isFRef() ) { // Loop body has forward refs
+ Var v = new Var(nIns(),n._name,n.type(),n._final,n._loc,true);
+ v._uninit = n._uninit;
+ _vars.add(v);
+ addDef(scope.in(i));
+ }
+ }
+ }
+
// ------------------------------------------------
// peephole the backedge scope into this loop head scope
@@ -426,8 +434,10 @@ public class ScopeNode extends MemMergeNode {
for( int i=1; i<nIns(); i++ ) {
if( var(i)._final ) continue; // Final vars did not get modified in the loop
Node n = in(i);
- if( var(i).type().isHighOrConst() ) { // Cannot lift higher than a constant, so no Phi
- n.subsume(n=ConstantNode.make(var(i).type()).init());
+ if( var(i).type().isHighOrConst() && !(n instanceof PhiNode phi && phi.inProgress()) ) { // Cannot lift higher than a constant, so no Phi
+ Node con = ConstantNode.make(var(i).type()).init();
+ n.subsume(con);
+ n = con;
} else if( back.in(i) != scope ) {
PhiNode phi = (PhiNode)n;
assert phi.region()==scope.ctrl() && phi.in(2)==null;
@@ -441,12 +451,48 @@ public class ScopeNode extends MemMergeNode {
// ------------------------------------------------
// Up-casting: using the results of an If to improve a value.
// E.g. "if( ptr ) ptr.field;" is legal because ptr is known not-null.
+ //
+ // This operation is lexical, not semantic, and thus its behavior changes
+ // the language spec. Optimizations that hide the lexical structure
+ // (e.g. constant folding control flow) can hide what is guarded or how the
+ // guard is applied, making it somewhat fragile. Several good use cases
+ // are covered here, but not everything you might like.
+ // TODO: Make this more robust for shapes like:
+ // ```
+ // if( !ary || !ary# ) exit();
+ // for( ...; ary# ) ...;
+ // ```
+ // Here the guard ends up guarding the whole expression (both ary and ary#
+ // are not-null), but loses that ary is not-null. A better answer might be
+ // a dedicated logical expression node, or some sort of gather-of-checked
+ // values to Guard post-test.
+
public void addGuards( Node ctrl, Node pred, boolean invert ) {
assert ctrl instanceof CFGNode;
_guards.add(ctrl); // Marker to distinguish 0,1,2 guards
// add pred & its cast to the normal input list, with special Vars
if( pred==null || pred.isDead() )
return; // Dead, do not add any guards
+ _addGuards(ctrl,pred,invert);
+ }
+
+ private void _addGuards( Node ctrl, Node pred, boolean invert ) {
+ if( pred==null || pred.isDead() )
+ return;
+ // Short-circuit logic is represented by a Phi. For `a || b` being
+ // false, or `a && b` being true, both individual operands have the
+ // same proven value as the whole expression. The "skipped RHS" path
+ // is the Region input whose Phi value is the controlling If predicate.
+ if( pred instanceof PhiNode phi && phi.nIns()==3 && phi.region() instanceof RegionNode r && r.nIns()==3 )
+ for( int i=1; i<3; i++ )
+ if( r.in(i) instanceof CProjNode prj && prj.ctrl() instanceof IfNode iff ) {
+ Node skip = shortCircuitSkippedPred(phi.in(i),iff.pred(),prj,invert);
+ if( skip != null ) {
+ _addGuards(ctrl,skip,invert);
+ _addGuards(ctrl,phi.in(3-i),invert);
+ break;
+ }
+ }
// Invert the If conditional
if( invert )
pred = pred instanceof NotNode not ? not.in(1) : CodeGen.CODE.add(new NotNode(pred).peephole());
@@ -460,6 +506,20 @@ public class ScopeNode extends MemMergeNode {
_addGuard(false,ctrl,zeroPred);
}
+ private static Node shortCircuitSkippedPred( Node val, Node pred, CProjNode prj, boolean invert ) {
+ if( val == null )
+ return null;
+ if( val == pred )
+ return (prj._idx==0)==invert ? val : null;
+ if( val instanceof NotNode not && not.in(1)==pred )
+ return (prj._idx==1)==invert ? val : null;
+ if( val._type == TypeInteger.TRUE && invert )
+ return prj._idx==0 ? pred : CodeGen.CODE.add(new NotNode(pred).peephole());
+ if( val._type == TypeInteger.FALSE && !invert )
+ return prj._idx==1 ? pred : CodeGen.CODE.add(new NotNode(pred).peephole());
+ return null;
+ }
+
private void _addGuard(boolean nonZero, Node ctrl, Node pred) {
Node guard = new GuardNode(nonZero,ctrl,pred).peephole();
if( guard != pred ) {
@@ -467,7 +527,7 @@ public class ScopeNode extends MemMergeNode {
guard.keep();
_guards.add(pred);
_guards.add(guard);
- replace(pred,guard);
+ replace(pred,guard,nonZero);
}
}
@@ -512,13 +572,27 @@ public class ScopeNode extends MemMergeNode {
}
- private void replace( Node old, Node cast ) {
+ private void replace( Node old, Node cast, boolean nonZero ) {
assert old!=null && old!=cast;
for( int i=0; i<nIns(); i++ )
- if( in(i)==old )
+ if( in(i)==old || nonZero && nullCheckedPhi(in(i),old) )
setDef(i,cast);
}
+ private static boolean nullCheckedPhi( Node n, Node old ) {
+ if( !(n instanceof PhiNode phi) || phi.nIns()!=3 )
+ return false;
+ if( phi.in(1)==null || phi.in(2)==null )
+ return false;
+ int zero = phi.in(1)._type == phi.in(1)._type.makeZero() ? 1 :
+ phi.in(2)._type == phi.in(2)._type.makeZero() ? 2 : -1;
+ if( zero == -1 )
+ return false;
+ Node val = phi.in(3-zero);
+ return val==old ||
+ val instanceof GuardNode guard && guard._nonZero && guard.in(1)==old;
+ }
+
// ------------------------------------------------------------------------
// Insert a top-level public external symbol, "as-if" some #include/import
// was executed at the top of tile.
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java
index c0006511..bd268af2 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java
@@ -28,11 +28,11 @@ public class StoreNode extends MemOpNode {
* @param off The offset inside the struct base
* @param value Value to be stored
*/
- public StoreNode(Parser.Lexer loc, String name, int alias, Type glb, Node ctrl, Node mem, Node ptr, Node off, Node value, boolean init) {
- this(loc,name,alias,glb,ctrl,mem,ptr,off,value,init,(byte)0);
+ public StoreNode(Parser.Lexer loc, String name, int alias, Type decl, Node ctrl, Node mem, Node ptr, Node off, Node value, boolean init) {
+ this(loc,name,alias,decl,ctrl,mem,ptr,off,value,init,(byte)0);
}
- private StoreNode(Parser.Lexer loc, String name, int alias, Type glb, Node ctrl, Node mem, Node ptr, Node off, Node value, boolean init, byte size) {
- super(loc, name, alias, false, glb, ctrl, mem, ptr, off, value);
+ private StoreNode(Parser.Lexer loc, String name, int alias, Type decl, Node ctrl, Node mem, Node ptr, Node off, Node value, boolean init, byte size) {
+ super(loc, name, alias, false, decl, ctrl, mem, ptr, off, value);
_init = init;
_size = size;
}
@@ -235,7 +235,7 @@ public class StoreNode extends MemOpNode {
private static byte storeSize(Type decl) {
// `decl` is the target field declaration, not the stored value. In
// particular a constant zero stored into u32 is still a 4-byte Store;
- // taking the constant's GLB here would incorrectly select i64.
+ // taking the constant's natural width here would incorrectly select i64.
byte size = (byte)(1 << decl.log_size());
assert size==1 || size==2 || size==4 || size==8;
return size;
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java
index ebd6a5ae..100bbd2a 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java
@@ -27,7 +27,9 @@ public class StoreRISC extends MemOpRISC {
if( i==1 ) return null; // mem
if( i==2 ) return riscv.RMASK; // ptr
// 2 - index
- if( i==4 ) return riscv.MEM_MASK; // Wide mask to store GPR and FPR
+ if( i==4 ) return _bytes >= 4
+ ? riscv.MEM_MASK // Word/dword value in GPR or FPR
+ : riscv.RMASK; // Byte/short stores require a GPR
return null; // Anti-dependence
}
@Override public RegMask outregmap() { return null; }
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java
index 24812fea..9d131fc6 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java
@@ -38,7 +38,7 @@ public class CmpMemX86 extends MemOpX86 {
case 0: enc.add1(_imm); break;
case 1: enc.add2(_imm); break;
case 2: enc.add4(_imm); break;
- case 3: enc.add8(_imm); break;
+ case 3: enc.add4(_imm); break;
}
} else {
encVal(enc, _con, ptr, idx, src, _off, _scale, _swap);
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java
index 6069c07b..bfaf7a91 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java
@@ -67,7 +67,7 @@ public abstract class MemOpX86 extends MemOpNode implements MachNode {
if( i==1 ) return null; // Memory
if( i==2 ) return x86_64_v2.RMASK; // base in GPR
if( i==3 ) return x86_64_v2.RMASK; // index in GPR
- if( i==4 ) return _sz >= 2
+ if( i==4 ) return _sz >= '4'
? x86_64_v2.MEM_MASK // value in GPR or XMM
: x86_64_v2.RMASK; // Bytes and shorts in GPR only
return null; // Anti-dependence
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java
index d9056be9..7db80299 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NotX86.java
@@ -17,23 +17,20 @@ public class NotX86 extends MachConcreteNode implements MachNode {
short dst = enc.reg(this );
short src = enc.reg(in(1));
- // Pre-zero using XOR dst,dst; since zero'd will not have a
- // byte-dependency from the setz. Can skip REX is dst is low 8, makes
- // this a 32b xor, which will also zero the high bits.
- if( dst >= 8 ) enc.add1(x86_64_v2.rex(dst, dst, 0));
- enc.add1(0x33); // opcode
- enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, dst));
-
// test rdi,rdi
enc.add1(x86_64_v2.rex(src, src, 0));
enc.add1(0x85);
enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, src, src));
- // setz (sete dil)
- enc.add1(x86_64_v2.rex(dst, 0, 0));
+ // setz dstb; then zero-extend the low byte into the full register.
+ if( dst >= 4 ) enc.add1(x86_64_v2.rex(0, dst, 0, false));
enc.add1(0x0F);
enc.add1(0x94);
- enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, 0));
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, 0, dst));
+ if( dst >= 4 ) enc.add1(x86_64_v2.rex(dst, dst, 0, false));
+ enc.add1(0x0F);
+ enc.add1(0xB6);
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, dst));
}
@Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(this)); }
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
index a1ad17dc..4cabefca 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
@@ -511,7 +511,8 @@ public class x86_64_v2 extends Machine {
// Operands swap in the encoding directly, no need for Set/Jmp to swap `bop`
if( rhs instanceof LoadNode ld && ld.nOuts() == 1 && lhs._type.isa(ld.declaredType()) &&
- (val!=null || bool.op()=="==" || bool.op()=="!=") )
+ (!(lhs instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value())) ||
+ bool.op()=="==" || bool.op()=="!=") )
return new CmpMemX86(bool, address(ld), ld.ptr(), idx, off, scale, imm(lhs), val, true );
// Vs immediate
@@ -667,7 +668,7 @@ public class x86_64_v2 extends Machine {
private int imm( Node xval ) {
assert val == null && imm == 0;
- if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti) {
+ if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti && ti.isConstant() && imm32(ti.value()) ) {
val = null;
imm = (int) ti.value();
assert imm == ti.value(); // In 32-bit range
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java b/chapter25/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java
index 206983a0..e15d7dbe 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java
@@ -14,23 +14,26 @@ public abstract class ASMPrinter {
public static SB print(SB sb, CodeGen code ) {
Encoding enc = code._encoding;
- if( enc == null ) return sb.p("No encoding for ").p(code._srcName);
+ Ary<CFGNode> cfg = enc == null ? code._cfg : enc._cfg;
+ if( cfg == null || cfg._len == 0 ) return sb.p("No scheduled code for ").p(code._srcName);
// instruction address
int iadr = 0;
// Print all functions in order
- for( int i=0; i<enc._cfg._len; i++ )
- if( enc._cfg.at(i) instanceof FunNode fun )
- iadr = print(iadr,sb,code,enc,fun,i);
+ for( int i=0; i<cfg._len; i++ )
+ if( cfg.at(i) instanceof FunNode fun )
+ iadr = print(iadr,sb,code,enc,cfg,fun,i);
// constant pools
- iadr = (iadr+15)&-16; // pad to 16
- if( enc._cpool.size()>0 )
- iadr = printConstantPool(iadr, sb, enc._cpool,enc._bigCons,true ,"Constant Pool");
+ if( enc != null ) {
+ iadr = (iadr+15)&-16; // pad to 16
+ if( enc._cpool.size()>0 )
+ iadr = printConstantPool(iadr, sb, enc._cpool,enc._bigCons,true ,"Constant Pool");
- iadr = (iadr+15)&-16; // pad to 16
- if( enc._sdata.size()>0 )
- iadr = printConstantPool(iadr, sb,enc._sdata,enc._bigCons,false, "Static Data" );
+ iadr = (iadr+15)&-16; // pad to 16
+ if( enc._sdata.size()>0 )
+ iadr = printConstantPool(iadr, sb,enc._sdata,enc._bigCons,false, "Static Data" );
+ }
return sb;
}
@@ -90,7 +93,7 @@ public abstract class ASMPrinter {
}
- private static int print(int iadr, SB sb, CodeGen code, Encoding enc, FunNode fun, int cfgidx) {
+ private static int print(int iadr, SB sb, CodeGen code, Encoding enc, Ary<CFGNode> cfg, FunNode fun, int cfgidx) {
// Function header
sb.nl().p("---");
if( fun._name != null ) sb.p(fun._name).p(" ");
@@ -100,8 +103,8 @@ public abstract class ASMPrinter {
if( fun._frameAdjust != 0 )
iadr = doInst(iadr,sb,code,enc, cfgidx,fun,true,enc!=null);
- while( !(enc._cfg.at(cfgidx) instanceof ReturnNode) )
- iadr = doBlock(iadr,sb,code,enc,fun,cfgidx++);
+ while( !(cfg.at(cfgidx) instanceof ReturnNode) )
+ iadr = doBlock(iadr,sb,code,enc,cfg,fun,cfgidx++);
// Function separator
sb.p("---");
@@ -112,9 +115,9 @@ public abstract class ASMPrinter {
static private final int opWidth = 5;
static private final int argWidth = 30;
- static int doBlock(int iadr, SB sb, CodeGen code, Encoding enc, FunNode fun, int cfgidx) {
+ static int doBlock(int iadr, SB sb, CodeGen code, Encoding enc, Ary<CFGNode> cfg, FunNode fun, int cfgidx) {
final int encWidth = code._mach==null ? 2 : code._mach.defaultOpSize()*2;
- CFGNode bb = enc._cfg.at(cfgidx);
+ CFGNode bb = cfg.at(cfgidx);
if( bb != fun && !(bb instanceof IfNode) && !(bb instanceof CallEndNode) && !(bb instanceof CallNode) && !(bb instanceof CProjNode && bb.in(0) instanceof CallEndNode ))
sb.p(label(bb)).p(":").nl();
if( bb instanceof CallNode ) return iadr;
@@ -167,8 +170,9 @@ public abstract class ASMPrinter {
// need to assume a jump. There's no real hardware op here, yet.
if( n instanceof RegionNode cfg && !(n instanceof FunNode) ) {
if( postEncode ) return iadr; // All jumps inserted already
- while( cfgidx < enc._cfg._len-1 ) {
- CFGNode next = enc._cfg.at(++cfgidx);
+ Ary<CFGNode> blocks = enc == null ? code._cfg : enc._cfg;
+ while( cfgidx < blocks._len-1 ) {
+ CFGNode next = blocks.at(++cfgidx);
if( next == n ) return iadr; // Fall-through, no branch
if( next.nOuts()>1 )
break; // Has code in the block, need to jump around
@@ -243,7 +247,7 @@ public abstract class ASMPrinter {
sb.nl();
// Printing more op bits than fit
- if( isMultiOp != null ) {
+ if( isMultiOp != null && enc != null ) {
// Multiple ops, template style, no RA, no scheduling. Print out
// one-line-per-newline, with encoding bits up front.
int size = enc != null ? enc.opLen(n) : 0;
@@ -267,6 +271,9 @@ public abstract class ASMPrinter {
}
+ } else if( isMultiOp != null ) {
+ sb.p(isMultiOp).nl();
+
} else if( fatEncoding > 0 ) {
// Extra bytes past the default encoding width, all put on a line by
// themselves. X86 special for super long encodings
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/print/IRPrinter.java b/chapter25/src/main/java/com/seaofnodes/simple/print/IRPrinter.java
index 7c0f84f3..8b1c1e2f 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/print/IRPrinter.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/print/IRPrinter.java
@@ -1,7 +1,7 @@
package com.seaofnodes.simple.print;
import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.codegen.Serialize;
+import com.seaofnodes.simple.codegen.CompUnit;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.util.Ary;
import com.seaofnodes.simple.util.SB;
@@ -37,30 +37,133 @@ public abstract class IRPrinter {
// Bulk whole program pretty print
public static String prettyPrint( CodeGen code ) {
- // Loop-tree for nodeOrder
- CFGNode.LoopTree old = code._start._ltree;
- if( old==null )
- code._start.buildLoopTree( code._linker, code._stop);
- Ary<Node> nodes = Serialize.nodeOrder(code);
- if( old == null )
- code._start._ltree = null;
SB sb = new SB();
- Node prior=null;
- for( Node n : nodes ) {
- if( n instanceof FunNode fun ) {
- sb.nl().p("--- ");
- fun.sig().print(sb.p(fun._name==null ? "" : fun._name).p(" "));
- sb.p("----------------------\n");
- }
- if( n instanceof MultiNode || n instanceof RegionNode || n instanceof CallNode ||
+ printLine(code._start,sb);
+
+ // Constants are shared program-wide. Print only actual Start children
+ // here; derived values belong to the functions which use them.
+ ArrayList<Node> globals = new ArrayList<>();
+ for( Node n : code._start._outputs )
+ if( n instanceof ConstantNode ) globals.add(n);
+ globals.sort(Comparator.comparingInt(n -> n._nid));
+ for( Node n : globals ) printLine(n,sb);
+
+ ArrayList<CompUnit> cus = new ArrayList<>(code._compunits.values());
+ cus.sort(Comparator.comparing(cu -> cu._cname==null ? "" : cu._cname));
+ for( CompUnit cu : cus ) {
+ if( cu._start==null || cu._start._inputs==null ) continue;
+ sb.nl().p("=== ").p(cu._cname==null ? "" : cu._cname).p(" ===\n");
+ printLine(cu._start,sb);
+ ArrayList<Node> projs = projections(cu._start);
+ for( Node proj : projs ) printLine(proj,sb);
+
+ ArrayList<FunNode> funs = new ArrayList<>();
+ for( FunNode fun : code._linker )
+ if( fun!=null && fun._inputs!=null && fun._compunit==cu )
+ funs.add(fun);
+ funs.sort(Comparator.comparingInt(n -> n._nid));
+ for( FunNode fun : funs ) printFunction(fun,sb);
+
+ printLine(cu._stop,sb);
+ }
+ printLine(code._stop,sb);
+ return sb.toString();
+ }
+
+ /** Print one function without ever traversing a call-graph linkage. */
+ private static void printFunction(FunNode fun, SB sb) {
+ sb.nl().p("--- ");
+ fun.sig().print(sb.p(fun._name==null ? "" : fun._name).p(" "));
+ sb.p("----------------------\n");
+
+ ArrayList<Node> post = new ArrayList<>();
+ IdentityHashMap<Node,Boolean> visit = new IdentityHashMap<>();
+ functionPost(fun,fun,visit,post);
+ Collections.reverse(post);
+
+ Node prior = null;
+ IdentityHashMap<Node,Boolean> emitted = new IdentityHashMap<>();
+ for( Node n : post ) {
+ if( emitted.containsKey(n) ) continue;
+ if( n instanceof RegionNode || n instanceof MultiNode || n instanceof CallNode ||
(isMultiChild(prior) && !isMultiChild(n)) )
sb.nl();
+ // A Region/Loop and its Phis are one block header. In particular,
+ // do not pull loop-carried Phi inputs up between the header and
+ // the Phi; those values belong down in the loop body/backedge.
+ if( !isMultiChild(n) )
+ emitLocalDefs(n,visit,emitted,sb);
printLine(n,sb);
- if( n instanceof ReturnNode ret )
- sb.p("--- ").p(ret._fun._name==null ? "" : ret._fun._name).p(" ----------------------\n");
+ emitted.put(n,Boolean.TRUE);
prior = n;
}
- return sb.toString();
+ sb.p("--- ").p(fun._name==null ? "" : fun._name).p(" ----------------------\n");
+ }
+
+ private static void emitLocalDefs(Node n,
+ IdentityHashMap<Node,Boolean> owned,
+ IdentityHashMap<Node,Boolean> emitted,
+ SB sb) {
+ if( n._inputs==null ) return;
+ for( Node def : n._inputs ) {
+ if( def==null || def instanceof CFGNode || def instanceof ConstantNode ||
+ def instanceof PhiNode || isMultiChild(def) ||
+ !owned.containsKey(def) || emitted.containsKey(def) )
+ continue;
+ emitLocalDefs(def,owned,emitted,sb);
+ printLine(def,sb);
+ emitted.put(def,Boolean.TRUE);
+ }
+ }
+
+ // Printer-private RPO. This deliberately uses raw edge arrays and local
+ // identity state: no Node visit bits, cached idoms, or loop-tree metadata.
+ private static void functionPost(Node n, FunNode owner,
+ IdentityHashMap<Node,Boolean> visit,
+ ArrayList<Node> post) {
+ if( n==null || n._inputs==null || visit.put(n,Boolean.TRUE)!=null ) return;
+ if( n instanceof FunNode fun && fun!=owner ) return; // Linked callee
+ if( n instanceof ParmNode &&
+ input0(n)!=owner ) return; // Callee parameter
+ if( n instanceof StopCUNode || n instanceof StopNode || n instanceof StartCUNode ) return;
+ if( n instanceof CallEndNode && input0(n) instanceof CallNode call &&
+ !visit.containsKey(call) ) return; // Foreign caller
+
+ ArrayList<Node> uses = new ArrayList<>();
+ // A Return is terminal inside its function. All of its graph users
+ // are ownership/linkage hooks (StopCU, CallEnds, FunPtrs), never CFG
+ // continuation in the function being printed.
+ if( !(n instanceof ReturnNode) ) for( Node use : n._outputs ) {
+ if( use==null || use._inputs==null ) continue;
+ if( n instanceof CallNode && use instanceof FunNode ) continue;
+ if( use instanceof FunNode fun && fun!=owner ) continue;
+ if( use instanceof ParmNode &&
+ input0(use)!=owner ) continue;
+ uses.add(use);
+ }
+ // CFG first produces an RPO-like block order. Stable nid ordering
+ // keeps malformed/multiply-connected graphs deterministic.
+ uses.sort(Comparator
+ .comparingInt((Node use) -> use instanceof CFGNode ? 0 : 1)
+ .thenComparingInt(use -> use._nid));
+ for( Node use : uses ) functionPost(use,owner,visit,post);
+
+ // Keep a multi-head and its projections contiguous.
+ if( isMultiChild(n) ) return;
+ if( isMultiHead(n) ) {
+ ArrayList<Node> ps = projections(n);
+ for( Node p : ps ) visit.put(p,Boolean.TRUE);
+ for( int i=ps.size()-1; i>=0; i-- ) post.add(ps.get(i));
+ }
+ post.add(n);
+ }
+
+ private static ArrayList<Node> projections(Node multi) {
+ ArrayList<Node> ps = new ArrayList<>();
+ for( Node use : multi._outputs )
+ if( use!=null && use._inputs!=null && isMultiChild(use) ) ps.add(use);
+ ps.sort(Comparator.comparingInt(IRPrinter::sortOrder));
+ return ps;
}
private static boolean isMultiHead(Node n) {
@@ -68,7 +171,19 @@ public abstract class IRPrinter {
}
private static boolean isMultiChild(Node n) {
- return n!=null && n.nIns() > 0 && n.in(0) != null && isMultiHead(n.in(0));
+ Node head = input0(n);
+ // Regions group their Phis, and tuple-producing MultiNodes group their
+ // projections. Merely being controlled by a Region/Loop does not
+ // make an ordinary CFG node a grouped child.
+ return (n instanceof PhiNode && head instanceof RegionNode) ||
+ (n instanceof Proj && head instanceof MultiNode);
+ }
+
+ // Raw, bounds-safe graph inspection for the debugger. In particular do
+ // not call an accessor which might assert, sharpen, cache, or lazily kill.
+ private static Node input0(Node n) {
+ return n==null || n._inputs==null || n._inputs.isEmpty()
+ ? null : n._inputs.at(0);
}
@@ -163,7 +278,7 @@ public abstract class IRPrinter {
}
static int sortOrder( Node n ) {
- if( n instanceof ProjNode proj ) return proj._idx;
+ if( n instanceof Proj proj ) return proj.idx();
if( n instanceof PhiNode phi ) return phi._nid;
if( n == null ) return 0;
return n._nid+1000000;
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/Field.java b/chapter25/src/main/java/com/seaofnodes/simple/type/Field.java
index b6ea5843..ed505b19 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/type/Field.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/Field.java
@@ -98,8 +98,6 @@ public class Field extends Type {
@Override boolean _isConstant() { return _t._isConstant(); }
@Override boolean _isFinal() { return _final && _t._isFinal(); }
@Override Field _makeRO() { return _final ? this : make(_fname, _t._makeRO(),_alias,true); }
- @Override boolean _isGLB(boolean mem) { return _t._isGLB(true); }
- @Override public Type _glb(boolean mem) { return make(_fname,_t._glb(true),_alias,true); }
@Override Field _close( String name, HashMap<String, Type> TYPES ) { return makeFrom( _t._close(name, TYPES )); }
@Override Type _upgradeType(HashMap<String,Type> TYPES) {
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/Type.java b/chapter25/src/main/java/com/seaofnodes/simple/type/Type.java
index fd3a7c59..509326bb 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/type/Type.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/Type.java
@@ -480,28 +480,11 @@ public class Type /*implements Cloneable*/ {
}
Type _makeRO() { return this; }
- // Compute greatest lower bound in the lattice. If values are in memory,
- // ints and floats cannot widen.
- public final boolean isGLB(boolean mem) { return recurClose(recurOpen()._isGLB(mem)); };
- boolean _isGLB(boolean mem) {
- return switch(_type) {
- case TBOT -> false;
- case TSCALAR -> this==TypeScalar.BOT;
- case TNIL -> false;
- case TCTRL -> true;
- case TXCTRL -> false;
- case TXNIL -> false;
- case TTOP -> false;
- default -> throw Utils.TODO();
- };
- }
- public final Type glb(boolean mem) {
- if( isGLB(mem) ) return this;
- Type glb = recurOpen()._glb(mem).recurClose();
- assert this.isa(glb);
- return glb;
+ /** Declared shape used when a value is published into mutable memory. */
+ public final Type makeStorage() {
+ return recurOpen()._makeStorage().recurClose();
}
- Type _glb(boolean mem) { assert is_simple(); return Type.BOTTOM; }
+ Type _makeStorage() { assert is_simple(); return BOTTOM; }
// Bulk close-over all recursive types
public static TypeStruct[] closeOver(TypeStruct[] ts, HashMap<String,Type> TYPES) {
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeConAry.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeConAry.java
index 26fa4a67..2a0a817e 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeConAry.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeConAry.java
@@ -48,8 +48,7 @@ public abstract class TypeConAry<A> extends TypeScalar {
@Override public boolean isHigh() { return this==TOP; }
@Override boolean _isConstant() { return true; }
- @Override Type _glb(boolean mem) { return this; }
- @Override boolean _isGLB(boolean mem) { return true; }
+ @Override Type _makeStorage() { return this; }
// Meet-over-elements type
public Type elem() {
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeFloat.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeFloat.java
index f4968f28..1325b707 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeFloat.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeFloat.java
@@ -90,15 +90,13 @@ public class TypeFloat extends TypeScalar {
return _isConstant() ? this : new TypeFloat((byte)-_sz,0); // Constants are a self-dual
}
- @Override boolean _isGLB(boolean mem) { return _glb(mem)==this; }
- @Override Type _glb(boolean mem) {
- if( !mem ) return F64;
+ @Override boolean _isConstant() { return _sz==0; }
+
+ @Override Type _makeStorage() {
if( _isConstant() ) return isF32() ? F32 : F64;
return isHigh() ? dual() : this;
}
- @Override boolean _isConstant() { return _sz==0; }
-
@Override public Type makeZero() { return FZERO; }
// Reserve tags for F64,F32,constant
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java
index e2ec6263..d18ebd9c 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java
@@ -68,7 +68,7 @@ public class TypeFunPtr extends TypeNil {
@Override TypeFunPtr makeFrom( byte nil ) { return nil ==_nil ? this : make( nil,_open,_sig,_ret, _fidxs); }
public TypeFunPtr makeFrom( Type ret ) { return ret ==_ret ? this : make( _nil,_open,_sig, ret, _fidxs); }
- public TypeFunPtr makeFrom( int fidx ) { return make1((byte)2,_open,_sig,_ret,fidx); }
+ public TypeFunPtr makeFrom( int fidx ) { return make1(_nil,_open,_sig,_ret,fidx); }
public TypeFunPtr makeFrom( Type arg, int idx ) {
// Alter named arg
Type[] sig = _sig.clone();
@@ -159,8 +159,7 @@ public class TypeFunPtr extends TypeNil {
@Override boolean _isConstant() { return (_nil==2 && XInt.isConstant(_fidxs)) || (_nil==3 && _fidxs== XInt.EMPTY); }
@Override boolean _isFinal() { return true; }
- @Override boolean _isGLB(boolean mem) { return true; }
- @Override TypeFunPtr _glb(boolean mem) { return this; }
+ @Override TypeFunPtr _makeStorage() { return this; }
@Override TypeFunPtr _close( String name, HashMap<String, Type> TYPES ) {
Type[] sig = new Type[_sig.length];
@@ -213,7 +212,7 @@ public class TypeFunPtr extends TypeNil {
@Override int TAGOFF() { return 8; }
@Override public void packed( BAOS baos, HashMap<String,Integer> strs ) {
assert _open; // Open: Passing extra args is fine, and extras ignored.
- if( _nil==2 && nargs()<6 && nfcns() == 1 ) {
+ if( _nil==2 && nargs()<6 && nfcns() == 1 && fidx() < 64 ) {
baos.write(TAGOFFS[_type] + nargs());
baos.packed2(fidx());
} else {
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeInteger.java
index 25c3fa6f..c981f3d3 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeInteger.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeInteger.java
@@ -170,14 +170,10 @@ public class TypeInteger extends TypeScalar {
}
@Override public Type makeZero() { return ZERO; }
@Override boolean _isConstant() { return _min == _max; }
- @Override boolean _isGLB(boolean mem) { return _glb(mem)==this; }
- @Override Type _glb(boolean mem) {
- if( !mem ) return BOT;
- // GLB is not well-defined in memory; depends on the size of the memory field
+ @Override Type _makeStorage() {
if( _isConstant() ) return BOT;
return isHigh() ? dual() : this;
}
-
// Reserve tags for I64,I32,U32,0,constant,generic
@Override int TAGOFF() { return 7; }
@Override public void packed( BAOS baos, HashMap<String,Integer> strs ) {
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMem.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMem.java
index 3bbdf1df..7658e3fd 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMem.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMem.java
@@ -123,10 +123,10 @@ public class TypeMem extends Type {
int[] fidxs = XInt.meet( _escFs, that._escFs );
int[] aliases= XInt.meet( _escAs, that._escAs );
// Singleton & class facts are kept only when both sides agree.
-
+
// Final is kept if either side is final, merging a final & non-final
// memory can be argued either way here:
-
+
// - Keep it: merging a final & non-final memory means a later Store
// update will be flagged as an error.
// - Lose it: merging a final & non-final memory means a later Load
@@ -152,8 +152,7 @@ public class TypeMem extends Type {
@Override boolean _isConstant() { return _one && _clz && _alias!= 1 && _t._isConstant(); }
@Override public int log_size() { throw Utils.TODO(); }
@Override boolean _isFinal() { return _t._isFinal(); }
- @Override boolean _isGLB(boolean mem) { return _t._isGLB(true); }
- @Override public Type _glb(boolean mem) { return make(_alias,_t._glb(true),_one,_clz,_final,_escFs,_escAs); }
+ @Override TypeMem _makeStorage() { return make(_alias,_t._makeStorage(),_one,_clz,_final,_escFs,_escAs); }
@Override TypeMem _close( String name, HashMap<String, Type> TYPES ) { return malloc(_alias,_t._close(name, TYPES ),_one,_clz,_final,_escFs,_escAs); }
@@ -230,7 +229,7 @@ public class TypeMem extends Type {
private static int[] escapeFIDX( int[] xs, Type t ) {
return switch(t) {
case TypeMemPtr tmp -> escapeFIDX(xs,tmp._obj);
- case TypeStruct ts -> ts._open
+ case TypeStruct ts -> ts._open && !ts.isAry()
? XInt.FULL // Open structs can have any fields
: XInt.meet(xs,ts.fidxs());
case TypeFunPtr tfp -> XInt.meet(xs,tfp.fidxs());
@@ -242,7 +241,7 @@ public class TypeMem extends Type {
private static int[] escapeAlias( int[] xs, Type t ) {
return switch(t) {
case TypeMemPtr tmp -> escapeAlias(xs,tmp._obj);
- case TypeStruct ts -> ts._open
+ case TypeStruct ts -> ts._open && !ts.isAry()
? XInt.FULL // Open structs can have any fields
: XInt.meet(xs,ts.aliases());
case TypeMem mem -> XInt.meet(xs,mem._escAs);
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java
index 67d80591..5a2693c9 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java
@@ -68,6 +68,10 @@ public class TypeMemPtr extends TypeNil {
public TypeMemPtr makeFrom(TypeStruct obj) { return obj==_obj ? this : make(_nil, obj, _one, _pub); }
public TypeMemPtr makeNullable() { return makeFrom((byte)3); }
+ /** Inferred mutable local: nullable, public and non-singleton. */
+ public TypeMemPtr makeVar() { return (TypeMemPtr)makeStorage(); }
+ /** Public-memory shape: nullable and non-singleton, preserving the referent. */
+ @Override TypeMemPtr _makeStorage() { return make((byte)3,_obj._makeStorage(),false,true); }
@Override TypeMemPtr makeFrom(byte nil) { return nil==_nil ? this : make(nil, _obj, _one, _pub); }
public TypeMemPtr makeHigh(byte nil) { return make(nil,_obj.makeHigh(),false); }
@Override public Type nonZero() { return makeFrom((byte)(_nil <= 1 ? 1 : 2)); }
@@ -109,8 +113,6 @@ public class TypeMemPtr extends TypeNil {
@Override boolean _isConstant() { return _one; }
@Override boolean _isFinal() { return _obj._isFinal(); }
@Override TypeMemPtr _makeRO() { return makeFrom(_obj._makeRO()); }
- @Override boolean _isGLB(boolean mem) { return _obj._isGLB(true); }
- @Override TypeMemPtr _glb(boolean mem) { return make((byte)3,_obj.glb2()); }
@Override TypeMemPtr _close( String name, HashMap<String, Type> TYPES ) { return malloc(_nil,_obj._close(name, TYPES ),_one,_pub); }
@Override Type _upgradeType(HashMap<String,Type> TYPES) {
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeNil.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeNil.java
index 32505aa2..2c1c1d76 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeNil.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeNil.java
@@ -51,7 +51,6 @@ public abstract class TypeNil extends TypeScalar {
public boolean nullable() { return _nil==3; }
@Override boolean _isConstant() { return false; }
- @Override Type _glb(boolean mem) { return Type.NIL; }
final String q() { return _nil == 1 || _nil == 2 ? "" : "?"; }
final String x() { return isHigh() ? "~" : ""; }
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypePtr.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypePtr.java
index 3ff9765b..7b27ff2b 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypePtr.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypePtr.java
@@ -50,8 +50,8 @@ public class TypePtr extends TypeNil {
// 0->xscalar, 1->nscalar, 2->nscalar, 3->scalar
@Override Type meetX() { return _nil==0 ? XNIL : (_nil==3 ? PTR : NPTR); }
- boolean _isGLB(boolean mem) { return this==PTR; }
- @Override TypePtr _glb(boolean mem) { return PTR; }
+ @Override TypePtr _makeStorage() { return PTR; }
+
// Reserve 2 tags, PTR and NPTR
@Override int TAGOFF() { return 2; }
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeRPC.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeRPC.java
index a39ebc04..8a12f50a 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeRPC.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeRPC.java
@@ -73,7 +73,7 @@ public class TypeRPC extends Type {
}
@Override boolean _isConstant() { return XInt.isConstant(_rpcs); }
- @Override boolean _isGLB(boolean mem) { return true; }
+ @Override TypeRPC _makeStorage() { return this; }
// Reserve tags for ALL, singleton, generic
@Override int TAGOFF() { return 3; }
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeScalar.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeScalar.java
index 7b5d6317..eaba0a1c 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeScalar.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeScalar.java
@@ -38,8 +38,7 @@ public class TypeScalar extends Type {
@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 Type _makeStorage() { return BOT; }
// Reserve tags for both lattice endpoints. Unlike most concrete scalar
// families, generic scalar TOP can survive into a serialized graph.
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeStruct.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
index f51ab859..2949b015 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
@@ -463,18 +463,8 @@ public class TypeStruct extends Type {
return recurTransform(_name,_name,_open,_fref,true,Type::_makeRO);
}
- // Keeps the same struct, but lower-bounds all fields.
- public TypeStruct glb2() {
- return recurTransform(_uid,_name,_open,false,false,t -> t._glb(true));
- }
-
- @Override boolean _isGLB(boolean mem) {
- if( VISIT.containsKey(_uid) ) return true; // Cycles assume GLB
- VISIT.put(_uid,this);
- for( Field f : _fields )
- if( !f._t._isGLB(mem) )
- return false;
- return true;
+ @Override TypeStruct _makeStorage() {
+ return recurTransform(_uid,_name,_open,false,false,Type::_makeStorage);
}
// log_size for a struct is not defined, unless its exactly some power of
@@ -569,7 +559,12 @@ public class TypeStruct extends Type {
if( isAry() && field("[]")._t instanceof TypeConAry con )
return sb.p(con.str());
sb.p(_name);
- if( html || isAry() )
+ if( isAry() )
+ // Array names deliberately canonicalize mutable and immutable
+ // element variants. The verbose debugger print must retain the
+ // otherwise invisible lattice coordinate.
+ return sb.p(_fields[1]._final ? "[final]" : "[mutable]");
+ if( html )
return sb;
sb.p(" {");
for( Field f : _fields )