From dc77490c735104497851ed8c12ae5c713ab05b04 Mon Sep 17 00:00:00 2001 From: Cliff Click Date: Sun, 30 Jun 2024 01:58:08 +0100 Subject: Chapter 11: Global Code Motion Co-authored-by: Dibyendu Majumdar Co-authored-by: Thalia Archibald Co-authored-by: Robert Obkircher --- .../com/seaofnodes/simple/GlobalCodeMotion.java | 238 +++++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java (limited to 'src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java') diff --git a/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java b/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java new file mode 100644 index 00000000..14540d46 --- /dev/null +++ b/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java @@ -0,0 +1,238 @@ + package com.seaofnodes.simple; + +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( StopNode stop ) { + fixLoops(stop); + schedEarly(); + Parser.SCHEDULED = true; + schedLate(Parser.START); + } + + // ------------------------------------------------------------------------ + // Backwards walk on the CFG only, looking for unreachable code - which has + // to be an infinite loop. Insert a bogus never-taken exit to Stop, so the + // loop becomes reachable. Also, set loop nesting depth + private static void fixLoops(StopNode stop) { + // Backwards walk from Stop, looking for unreachable code + BitSet visit = new BitSet(); + HashSet unreach = new HashSet<>(); + unreach.add(Parser.START); + for( Node ret : stop._inputs ) + ((ReturnNode)ret).walkUnreach(visit,unreach); + if( unreach.isEmpty() ) return; + + // Forwards walk from unreachable, looking for loops with no exit test. + visit.clear(); + for( CFGNode cfg : unreach ) + walkInfinite(cfg,visit,stop); + // Set loop depth on remaining graph + unreach.clear(); + visit.clear(); + for( Node ret : stop._inputs ) + ((ReturnNode)ret).walkUnreach(visit,unreach); + assert unreach.isEmpty(); + } + + + // Forwards walk over previously unreachable, looking for loops with no + // exit test. + static private void walkInfinite( CFGNode n, BitSet visit, StopNode stop ) { + if( visit.get(n._nid) ) return; // Been there, done that + visit.set(n._nid); + if( n instanceof LoopNode loop ) + loop.forceExit(stop); + for( Node use : n._outputs ) + if( use instanceof CFGNode cfg ) + walkInfinite(cfg,visit,stop); + } + + // ------------------------------------------------------------------------ + private static void schedEarly() { + ArrayList rpo = new ArrayList<>(); + BitSet visit = new BitSet(); + _rpo_cfg(Parser.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); + // Strictly for dead infinite loops, we can have entire code blocks + // not reachable from below - so we reach down, from above, one + // step. Since _schedEarly modifies the output arrays, the normal + // region._outputs ArrayList iterator throws CME. The extra edges + // are always *added* after any Phis, so just walk the Phi prefix. + if( cfg instanceof RegionNode region ) { + int len = region.nOuts(); + for( int i=0; i rpo) { + if( !(n instanceof CFGNode cfg) || visit.get(cfg._nid) ) + return; // Been there, done that + visit.set(cfg._nid); + for( Node use : cfg._outputs ) + _rpo_cfg(use,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 + visit.set(n._nid); + // Schedule not-pinned not-CFG inputs before self. Since skipping + // Pinned, this never walks the backedge of Phis (and thus spins around + // a data-only loop, eventually attempting relying on some pre-visited- + // not-post-visited data op with no scheduled control. + for( Node def : n._inputs ) + if( def!=null && !def.isPinned() ) + _schedEarly(def,visit); + // If not-pinned (e.g. constants, projections, phi) and not-CFG + if( !n.isPinned() ) { + // 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(StartNode start) { + CFGNode[] late = new CFGNode[Node.UID()]; + Node[] ns = new Node[Node.UID()]; + _schedLate(start,ns,late); + for( int i=0; i best.idepth() || best instanceof IfNode); + } + + // Skip iteration if a backedge + private static boolean isForwardsEdge(Node use, Node def) { + return use != null && def != null && + !(use.nIns()>2 && use.in(2)==def && (use instanceof LoopNode || (use instanceof PhiNode phi && phi.region() instanceof LoopNode))); + } + + private static CFGNode find_anti_dep(CFGNode lca, LoadNode load, CFGNode early, CFGNode[] late) { + // We could skip final-field loads here. + // Walk LCA->early, flagging Load's block location choices + for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() ) + cfg._anti = load._nid; + // Walk load->mem uses, looking for Stores causing an anti-dep + for( Node mem : load.mem()._outputs ) { + switch( mem ) { + case StoreNode st: + lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st); + 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