From 7279862d57c97063eb46997804b5efb6c0b07189 Mon Sep 17 00:00:00 2001 From: Cliff Click Date: Thu, 24 Sep 2026 10:47:39 -0700 Subject: Bunch better layout options --- .../java/com/seaofnodes/graph/GraphGroups.java | 118 +++++++++++++++++---- .../main/java/com/seaofnodes/graph/GraphJson.java | 1 + .../java/com/seaofnodes/graph/GraphSnapshot.java | 6 +- 3 files changed, 105 insertions(+), 20 deletions(-) (limited to 'graph/src/main/java') diff --git a/graph/src/main/java/com/seaofnodes/graph/GraphGroups.java b/graph/src/main/java/com/seaofnodes/graph/GraphGroups.java index 75bb90e0..7e46f809 100644 --- a/graph/src/main/java/com/seaofnodes/graph/GraphGroups.java +++ b/graph/src/main/java/com/seaofnodes/graph/GraphGroups.java @@ -10,7 +10,7 @@ final class GraphGroups { static ArrayList build(ArrayList nodes) { var result = new ArrayList(); boolean any=false; - for( Node n : nodes ) if( n.kind()==Kind.FUN || n.kind()==Kind.LOOP ) { any=true; break; } + for( Node n : nodes ) if( n.kind()==Kind.FUN || n.kind()==Kind.LOOP || n.label().equals("If") ) { any=true; break; } if( !any ) return result; int len = nodes.getLast().id()+1; Node[] ns = new Node[len]; @@ -49,13 +49,13 @@ final class GraphGroups { // Natural loops: walk backwards from the backedge, stopping at the // header. An unfinished loop has only its header until it closes. - var loops = new ArrayList(); + var parts = new ArrayList(); BitSet[] body = new BitSet[len]; for( Node n : nodes ) if( n.kind()==Kind.LOOP ) { int id = n.id(), back = def(n,2); BitSet set = body[id] = new BitSet(); set.set(id); - loops.add(n); + parts.add(n); if( back==0 || ns[back]==null ) continue; head=tail=0; if( back!=id ) { set.set(back); work[tail++]=back; } @@ -71,14 +71,42 @@ final class GraphGroups { } if( outside || !closed ) { set.clear(); set.set(id); } } - // Outer loops first. Simple's structured CFG makes these sets nested - // or disjoint. - loops.sort((a,b) -> Integer.compare(body[b.id()].cardinality(),body[a.id()].cardinality())); + // Look for a closed If/Region diamond. Reachable joins are considered + // nearest first; a candidate must have no other CFG entry or exit. + for( Node n : nodes ) if( n.kind()==Kind.CTRL && n.label().equals("If") && uses[n.id()].length==2 ) { + BitSet seen = new BitSet(); + head=tail=0; + work[tail++]=n.id(); seen.set(n.id()); + while( head Integer.compare(body[b.id()].cardinality(),body[a.id()].cardinality())); int[] par = new int[len], home = new int[len]; Arrays.fill(home,-1); for( Node n : nodes ) if( cfg(n) ) home[n.id()] = fun[n.id()]; - for( Node n : loops ) { + var accepted = new ArrayList(); + BitSet gids = new BitSet(); + for( Node n : parts ) { int id=n.id(); + boolean fits=true; + for( Node p : accepted ) if( body[id].intersects(body[p.id()]) ) { + BitSet rest=(BitSet)body[id].clone(); + rest.andNot(body[p.id()]); + if( !rest.isEmpty() || body[id].get(p.id()) ) { fits=false; break; } + } + if( !fits ) continue; + accepted.add(n); + gids.set(id); par[id]=home[id]; for( int i=body[id].nextSetBit(0); i>=0; i=body[id].nextSetBit(i+1) ) home[i]=id; } @@ -92,32 +120,51 @@ final class GraphGroups { anchor!=0 && ns[anchor]!=null && cfg(ns[anchor]) ) home[id]=home[anchor]; } - // Floating values belong to the common enclosing group of their uses. - // A Phi's input is used on the corresponding predecessor, not at the - // header (especially the entry input of a loop Phi). + // Compute input and output ownership independently, so a floating chain + // between two regions cannot pull itself into either end. Null slots and + // parser/lifetime associations do not nominate a region. Phi uses refer + // to the Phi itself, not to its incoming CFG path. BitSet fixed = new BitSet(); for( Node n : nodes ) if( home[n.id()]>=0 ) fixed.set(n.id()); + int[] ins=home.clone(), outs=home.clone(); boolean changed; do { changed=false; for( Node n : nodes ) for( Edge e : n.edges() ) { int id=e.def(); - if( id==0 || ns[id]==null || fixed.get(id) || e.role()==Role.ASSOC ) continue; - int site=n.id(); - if( n.kind()==Kind.PHI && e.idx()>0 ) { - Node reg=ns[def(n,0)]; - site=reg==null ? 0 : def(reg,e.idx()); + if( id==0 || ns[id]==null || e.role()==Role.ASSOC ) continue; + if( !fixed.get(n.id()) && ins[id]>=0 ) { + int next=lca(ins[n.id()],ins[id],par); + if( next!=ins[n.id()] ) { ins[n.id()]=next; changed=true; } + } + if( !fixed.get(id) && outs[n.id()]>=0 ) { + int next=lca(outs[id],outs[n.id()],par); + if( next!=outs[id] ) { outs[id]=next; changed=true; } } - int grp=site==0 ? 0 : home[site]; - if( grp<0 ) continue; - int next=lca(home[id],grp,par); + } + } while( changed ); + for( Node n : nodes ) if( !fixed.get(n.id()) ) { + home[n.id()]=owner(ins[n.id()],outs[n.id()],par); + } + // Unattached floating users may have no directional anchor. Check the + // actual neighbors too, widening unsupported claims until they settle. + // Only moving outward avoids self-supporting cycles of floating nodes. + do { + changed=false; + Arrays.fill(ins,-1); Arrays.fill(outs,-1); + for( Node n : nodes ) for( Edge e : n.edges() ) if( e.def()!=0 && e.role()!=Role.ASSOC ) { + ins[n.id()]=lca(ins[n.id()],home[e.def()],par); + outs[e.def()]=lca(outs[e.def()],home[n.id()],par); + } + for( Node n : nodes ) if( !fixed.get(n.id()) ) { + int id=n.id(), next=lca(home[id],owner(ins[id],outs[id],par),par); if( next!=home[id] ) { home[id]=next; changed=true; } } } while( changed ); // Projections and their MultiNode are one display box. for( Node n : nodes ) if( n.proj()!=null && n.proj().par()!=0 ) home[n.id()]=home[n.proj().par()]; - for( Node n : nodes ) if( n.kind()==Kind.FUN || n.kind()==Kind.LOOP ) { + for( Node n : nodes ) if( n.kind()==Kind.FUN || gids.get(n.id()) ) { int id=n.id(), size=0; for( Node m : nodes ) if( home[m.id()]==id ) size++; int[] ids = new int[size]; @@ -128,6 +175,30 @@ final class GraphGroups { return result; } + private static BitSet diamond(int entry, int exit, Node[] ns, int[][] preds, int[][] uses) { + BitSet set=new BitSet(); + int[] todo=new int[ns.length]; + int head=0, tail=0; + todo[tail++]=entry; set.set(entry); + while( head=0; id=set.nextSetBit(id+1) ) if( id!=entry ) + for( int p : preds[id] ) if( !set.get(p) ) return null; + // Reject trapped cycles as well as paths to another exit. + BitSet reaches=new BitSet(); + head=tail=0; + todo[tail++]=exit; reaches.set(exit); + while( head