package com.seaofnodes.simple; import com.seaofnodes.simple.node.*; import java.util.*; /** A debugger's ordering, independent of compiler scheduling and cached dominators. */ final class DebugSchedule { static Node input0(Node n) { return n==null || n._inputs==null || n._inputs.isEmpty() ? null : n._inputs.get(0); } static boolean head(Node n) { return n instanceof RegionNode || n instanceof MultiNode; } static boolean projection(Node n) { return n instanceof ProjNode || n instanceof CProjNode; } static boolean cfg(Node n) { return n instanceof CFGNode; } static boolean gap(Node n) { return cfg(n) && !projection(n); } static boolean child(Node n) { Node h = input0(n); return (n instanceof PhiNode && h instanceof RegionNode) || (projection(n) && h instanceof MultiNode); } private static Node group(Node n, IdentityHashMap selected) { return child(n) && selected.containsKey(input0(n)) ? input0(n) : n; } private static int order(Node n) { if( n instanceof ProjNode proj ) return proj._idx; if( n instanceof CProjNode proj ) return proj._idx; return n._nid; } // Use identity, not node equality (GVN), for all debugger bookkeeping. static ArrayList schedule(Node base, int depth) { ArrayList nodes = new ArrayList<>(); IdentityHashMap distance = new IdentityHashMap<>(); add(base,0,nodes,distance); for( int i=0; i orderNodes(ArrayList nodes) { IdentityHashMap selected = new IdentityHashMap<>(); for( Node n : nodes ) selected.put(n,Boolean.TRUE); IdentityHashMap> members = new IdentityHashMap<>(); ArrayList groups = new ArrayList<>(); for( Node n : nodes ) { Node g = group(n,selected); if( !members.containsKey(g) ) { members.put(g,new ArrayList<>()); groups.add(g); } if( n!=g ) members.get(g).add(n); } // Reconstruct uses from inputs: incomplete reverse edges must not // hide nodes. Ignore loop backedges for ordering, but still print them. IdentityHashMap> uses = new IdentityHashMap<>(); IdentityHashMap incoming = new IdentityHashMap<>(); for( Node g : groups ) uses.put(g,new ArrayList<>()); for( Node n : nodes ) { if( n._inputs==null ) continue; Node to = group(n,selected); for( int i=0; i=2 && (n instanceof LoopNode || n instanceof PhiNode && input0(n) instanceof LoopNode) ) continue; Node from = group(def,selected); if( from!=to ) { uses.get(from).add(to); incoming.put(to,Boolean.TRUE); } } } Comparator cfgFirst = Comparator .comparingInt((Node n) -> cfg(n) ? 0 : 1).thenComparingInt(n -> n._nid); for( ArrayList us : uses.values() ) us.sort(cfgFirst); groups.sort(Comparator.comparingInt(n -> n._nid)); IdentityHashMap seen = new IdentityHashMap<>(); ArrayList post = new ArrayList<>(); for( Node g : groups ) if( !incoming.containsKey(g) ) walk(g,uses,seen,post); // A broken graph may consist entirely of cycles, with no roots. for( Node g : groups ) walk(g,uses,seen,post); Collections.reverse(post); post=nestLoops(nodes,groups,selected,uses,post); ArrayList result = new ArrayList<>(); for( Node g : post ) { result.add(g); sortChildren(members.get(g)); result.addAll(members.get(g)); } return result; } // Projection lists are short. Select the next idx directly, including // -1, sparse indices, and deterministic nid order for duplicate indices. static void sortChildren(ArrayList children) { for( int i=0; i children(Node head) { ArrayList children = new ArrayList<>(); Set seen = Collections.newSetFromMap(new IdentityHashMap<>()); if( head!=null && head._outputs!=null ) for( Node use : head._outputs ) if( child(use) && input0(use)==head && seen.add(use) ) children.add(use); sortChildren(children); return children; } // Printer-local natural loops. Never ask idom()/loopDepth() to build or // repair compiler metadata while inspecting a partly constructed graph. private static final class Region { final Node head; final Set cfg = Collections.newSetFromMap(new IdentityHashMap<>()); Region parent; final ArrayList nodes = new ArrayList<>(); ArrayList rpo; Region(Node head) { this.head = head; } } private record Use(Node node, int input) {} private static ArrayList predecessors(Node n) { ArrayList preds = new ArrayList<>(); if( !cfg(n) || n._inputs==null || (n instanceof StartNode || n instanceof StopNode) ) return preds; if( n instanceof RegionNode ) { for( int i=1; i path = Collections.newSetFromMap(new IdentityHashMap<>()); for( Region r=a; r!=null; r=r.parent ) path.add(r); for( Region r=b; r!=null; r=r.parent ) if( path.contains(r) ) return r; throw new AssertionError("disjoint printer regions"); } private static Region enclosing(Node cfg, ArrayList loops, Region root) { Region best = root; for( Region loop : loops ) if( loop.cfg.contains(cfg) && (best==root || loop.cfg.size() owner) { Region r = owner.get(n); if( r==scope ) return n; while( r!=null && r.parent!=scope ) r=r.parent; return r==null ? null : r.head; } private static ArrayList nestLoops(ArrayList nodes, ArrayList groups, IdentityHashMap selected, IdentityHashMap> uses, ArrayList post) { Region root = new Region(null); ArrayList loops = new ArrayList<>(); for( Node n : groups ) { if( !(n instanceof LoopNode) || n instanceof StartNode ) continue; Region loop = new Region(n); loop.cfg.add(n); ArrayList work = new ArrayList<>(); Node back = n._inputs==null || n._inputs.size()<3 ? null : n._inputs.get(2); Node entry = n._inputs==null || n._inputs.size()<2 ? null : n._inputs.get(1); if( cfg(back) && selected.containsKey(back) && loop.cfg.add(back) ) work.add(back); boolean closed = back==n; for( int i=0; iloop.cfg.size() && outer.cfg.containsAll(loop.cfg) && (loop.parent==root || outer.cfg.size() owner = new IdentityHashMap<>(); IdentityHashMap> users = new IdentityHashMap<>(); for( Node g : groups ) { users.put(g,new ArrayList<>()); if( cfg(g) ) owner.put(g,enclosing(g,loops,root)); else if( cfg(input0(g)) ) owner.put(g,enclosing(input0(g),loops,root)); else if( g instanceof ConstantNode ) owner.put(g,root); } for( Node use : nodes ) if( use._inputs!=null ) for( int i=0; i=0; i-- ) { Node g = post.get(i); if( owner.containsKey(g) ) continue; Region place=null; boolean ready=true; for( Use use : users.get(g) ) { Region r=owner.get(group(use.node(),selected)); if( r==null ) { ready=false; break; } if( use.node() instanceof PhiNode && input0(use.node()) instanceof LoopNode && r.head==input0(use.node()) && use.input()==1 ) r=r.parent; place=place==null ? r : common(place,r); } if( ready ) { owner.put(g,place==null ? root : place); progress=true; } } } while( progress ); // Unresolved data cycles do not prevent the rest of the graph printing. for( Node g : groups ) owner.putIfAbsent(g,root); for( Region loop : loops ) owner.put(loop.head,loop); for( Node g : groups ) owner.get(g).nodes.add(g); for( Region loop : loops ) loop.parent.nodes.add(loop.head); ArrayList regions = new ArrayList<>(loops); regions.add(root); for( Region scope : regions ) { IdentityHashMap> edges = new IdentityHashMap<>(); IdentityHashMap incoming = new IdentityHashMap<>(); for( Node n : scope.nodes ) edges.put(n,new ArrayList<>()); for( Node from : groups ) { Node a=unit(from,scope,owner); if( a==null ) continue; for( Node to : uses.get(from) ) { Node b=unit(to,scope,owner); if( b!=null && a!=b ) { edges.get(a).add(b); incoming.put(b,Boolean.TRUE); } } } scope.nodes.sort(Comparator.comparingInt(n -> n._nid)); for( ArrayList es : edges.values() ) es.sort(Comparator.comparingInt((Node n) -> cfg(n) ? 0 : 1).thenComparingInt(n -> n._nid)); IdentityHashMap seen = new IdentityHashMap<>(); scope.rpo = new ArrayList<>(); if( scope.head!=null ) walk(scope.head,edges,seen,scope.rpo); for( Node n : scope.nodes ) if( !incoming.containsKey(n) ) walk(n,edges,seen,scope.rpo); for( Node n : scope.nodes ) walk(n,edges,seen,scope.rpo); Collections.reverse(scope.rpo); // Disconnected floating definitions still follow the loop header. if( scope.head!=null ) { scope.rpo.removeIf(n -> n==scope.head); scope.rpo.add(0,scope.head); } } // Expand nested loop vertices without recursive Java calls. IdentityHashMap byHead = new IdentityHashMap<>(); for( Region loop : loops ) byHead.put(loop.head,loop); ArrayList result = new ArrayList<>(); ArrayDeque scopes = new ArrayDeque<>(); ArrayDeque> positions = new ArrayDeque<>(); scopes.push(root); positions.push(root.rpo.iterator()); while( !scopes.isEmpty() ) { if( !positions.peek().hasNext() ) { scopes.pop(); positions.pop(); continue; } Node n=positions.peek().next(); Region nested=byHead.get(n); if( nested!=null && nested.parent==scopes.peek() ) { scopes.push(nested); positions.push(nested.rpo.iterator()); } else result.add(n); } return result; } private static void add(Node n, int d, ArrayList nodes, IdentityHashMap distance) { if( n!=null && !distance.containsKey(n) ) { distance.put(n,d); nodes.add(n); } } // An explicit DFS stack handles long chains as well as malformed cycles. private static void walk(Node root, IdentityHashMap> uses, IdentityHashMap seen, ArrayList post) { if( seen.put(root,Boolean.TRUE)!=null ) return; ArrayDeque stack = new ArrayDeque<>(); ArrayDeque> edges = new ArrayDeque<>(); stack.push(root); edges.push(uses.get(root).iterator()); while( !stack.isEmpty() ) { if( edges.peek().hasNext() ) { Node use = edges.peek().next(); if( seen.put(use,Boolean.TRUE)==null ) { stack.push(use); edges.push(uses.get(use).iterator()); } } else { post.add(stack.pop()); edges.pop(); } } } }