package com.seaofnodes.simple; import com.seaofnodes.simple.IterPeeps.WorkList; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.*; import java.util.*; public abstract class GlobalCodeMotion { // Arrange that the existing isCFG() Nodes form a valid CFG. The // Node.use(0) is always a block tail (either IfNode or head of the // following block). There are no unreachable infinite loops. public static void buildCFG( StartNode start, StopNode stop ) { schedEarly(start); breakUpGlobalConstants(start); schedLate(stop); } // After early scheduling, every global chain member has Start in slot 0. // Give each function one private copy of the entire dependency graph. private static void breakUpGlobalConstants( StartNode start ) { var globals = new IdentityHashMap(); var cons = new ArrayList(); for( Node con : start._outputs ) if( con!=null && !(con instanceof CFGNode) && con.isConst() ) { globals.put(con,true); cons.add(con.keep()); // Preserve original inputs while rewiring users. } var copies = new IdentityHashMap>(); for( Node con : cons ) for( Node use : con._outputs.asAry() ) { if( use==null || globals.containsKey(use) ) continue; for( int i=0; i new IdentityHashMap<>()); use.setDef(i,cloneGlobal(con,fun,globals,local)); } } for( Node con : cons ) con.unkill(); } private static Node cloneGlobal(Node con, FunNode fun, IdentityHashMap globals, IdentityHashMap copies) { Node copy = copies.get(con); if( copy!=null ) return copy; copies.put(con,copy=con.copyEmpty()); copy.addDef(fun); for( int i=1; i rpo = new ArrayList<>(); BitSet visit = new BitSet(); _rpo_cfg(null, start, visit, rpo); // Reverse Post-Order on CFG for( int j=rpo.size()-1; j>=0; j-- ) { CFGNode cfg = rpo.get(j); cfg.loopDepth(); for( Node n : cfg._inputs ) _schedEarly(n,visit); // In dead infinite loops, entire code blocks may be unreachable // from below. Reach down from the CFG to their Phis so their // inputs are scheduled too. if( cfg instanceof RegionNode ) for( Node phi : cfg._outputs ) if( phi instanceof PhiNode ) _schedEarly(phi,visit); } } // Post-Order of CFG private static void _rpo_cfg(CFGNode def, Node use, BitSet visit, ArrayList rpo) { if( !(use instanceof CFGNode cfg) || visit.get(cfg._nid) ) return; // Been there, done that if( def instanceof CallNode && cfg instanceof FunNode ) return; // Ignore linked function calls visit.set(cfg._nid); for( Node useuse : cfg._outputs ) _rpo_cfg(cfg,useuse,visit,rpo); rpo.add(cfg); } private static void _schedEarly(Node n, BitSet visit) { if( n==null || visit.get(n._nid) ) return; // Been there, done that assert !(n instanceof CFGNode); visit.set(n._nid); // Schedule inputs first, except Phis: following their backedges would // enter a data cycle before its control has been scheduled. for( Node def : n._inputs ) if( def!=null && !(def instanceof PhiNode) ) _schedEarly(def,visit); // An existing edge 0 already supplies control (or a Phi/Proj binding). if( n.in(0)==null ) { // Schedule at deepest input CFGNode early = Parser.START; // Maximally early, lowest idepth for( int i=1; i early.idepth() ) early = n.in(i).cfg0(); // Latest/deepest input n.setDef(0,early); // First place this can go } } // ------------------------------------------------------------------------ private static void schedLate( StopNode stop) { CFGNode[] late = new CFGNode[Node.UID()]; Node[] ns = new Node[Node.UID()]; // Record Load NIDs at all their CFG block choices, then check against // Store block choices to force a Load above an anti-dependent Store. int[] anti = new int[Node.UID()]; // Breadth-first scheduling breadth(stop,ns,late,anti); // Copy the best placement choice into the control slot for( int i=0; i work = new WorkList<>(); work.push(stop); Node n; outer: while( (n = work.pop()) != null ) { assert late[n._nid]==null; // No double visit // These I know the late schedule of, and need to set early for loops if( n instanceof CFGNode cfg ) late[n._nid] = cfg.blockHead() ? cfg : cfg.cfg(0); else if( n instanceof PhiNode phi ) late[n._nid] = phi.region(); else if( n instanceof ProjNode && n.in(0) instanceof CFGNode cfg ) late[n._nid] = cfg; else { // All uses done? for( Node use : n._outputs ) if( use!=null && late[use._nid]==null ) continue outer; // Nope, await all uses done // Loads need their memory inputs' uses also done if( n instanceof LoadNode ld ) for( Node memuse : ld.antiDeps() ) if( late[memuse._nid]==null ) continue outer; // All uses done, schedule _doSchedLate(n,ns,late,anti); } // A use just finished; reconsider its inputs and waiting loads, // even when the shared memory input was already scheduled. for( Node def : n._inputs ) { if( def==null ) continue; if( late[def._nid]==null ) work.push(def); wakeLoads(def,late,work,new BitSet()); } if( n instanceof LoopNode loop ) for( Node phi : loop._outputs ) if( phi instanceof PhiNode && late[phi._nid]==null ) work.push(phi); } } private static void _doSchedLate(Node n, Node[] ns, CFGNode[] late, int[] anti) { // Walk uses, gathering the LCA (Least Common Ancestor) of uses CFGNode early = n.in(0) instanceof CFGNode cfg ? cfg : n.in(0).cfg0(); assert early != null; CFGNode lca = null; for( Node use : n._outputs ) if( use != null ) lca = use_block(n,use, late).domLCA(lca,null); // Loads may need anti-dependencies, raising their LCA if( n instanceof LoadNode load ) lca = find_anti_dep(lca,load,early,late,anti); // Walk up from the LCA to the early, looking for best place. This is // the lowest execution frequency, approximated by least loop depth and // deepest control flow. CFGNode best = lca; lca = lca.idom(); // Already found best for starting LCA for( ; lca != early.idom(); lca = lca.idom() ) if( better(lca,best) ) best = lca; assert !(best instanceof IfNode); ns [n._nid] = n; late[n._nid] = best; } // Block of use. Normally from late[] schedule, except for Phis, which go // to the matching Region input. private static CFGNode use_block(Node n, Node use, CFGNode[] late) { if( !(use instanceof PhiNode phi) ) return late[use._nid]; CFGNode found=null; for( int i=1; i best.idepth() || best instanceof IfNode; } // An allocation may consume a load's memory through a partial aggregate. private static void wakeLoads(Node def, CFGNode[] late, WorkList work, BitSet visit) { if( visit.get(def._nid) ) return; visit.set(def._nid); for( Node out : def._outputs ) if( out instanceof LoadNode ld && late[ld._nid]==null ) work.push(ld); if( def instanceof MemMergeNode ) for( int i=1; iearly, flagging Load's block location choices for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() ) anti[cfg._nid] = load._nid; // Walk load->mem uses, looking for Stores causing an anti-dep for( Node mem : load.antiDeps() ) { switch( mem ) { case StoreNode st: assert late[st._nid]!=null; lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st,anti); break; case CallNode st: assert late[st._nid]!=null; lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st,anti); break; case PhiNode phi: // Repeat anti-dep for matching Phi inputs. // No anti-dep edges but may raise the LCA. for( int i=1; i