diff options
Diffstat (limited to 'chapter25/src/main/java/com')
6 files changed, 170 insertions, 74 deletions
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter25/src/main/java/com/seaofnodes/simple/IterPeeps.java index 145e0e03..a54eca73 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/IterPeeps.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/IterPeeps.java @@ -6,7 +6,6 @@ import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeInteger; import com.seaofnodes.simple.util.Ary; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.print.JSViewer; import java.util.Arrays; import java.util.BitSet; import java.util.IdentityHashMap; @@ -105,6 +104,8 @@ public class IterPeeps { while( (n=_work.pop()) != null ) { if( n.isDead() ) continue; _cnt++; // Useful for debugging, searching which peephole broke things + var obs = code._obs; + if( obs != null ) obs.before(n); Node x = n.peepholeOpt(); if( n instanceof CallEndNode cend ) _workInline.push(cend); @@ -133,14 +134,15 @@ public class IterPeeps { } // If there are distant neighbors, move to worklist n.moveDepsToWorklist(); - JSViewer.show(); // Show again // Very expensive assert. assert !CodeGen.expensiveAssert(_cnt) || (progressOnList(code, _work) && schedulableUses(code)); } - if( n.isUnused() ) { + boolean unused = n.isUnused(); + if( unused ) { assert !(n instanceof StopNode); // StopNodes can die if all code in the compunit dies n.kill(); // Just plain dead } + if( obs != null ) obs.after(n, x!=null ? x : unused ? n : null, true); } assert !CodeGen.expensiveAssert(0) || schedulableUses(code); diff --git a/chapter25/src/main/java/com/seaofnodes/simple/Parser.java b/chapter25/src/main/java/com/seaofnodes/simple/Parser.java index 1f03eda6..ffd8bf64 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/Parser.java @@ -2,7 +2,6 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.print.GraphVisualizer; import com.seaofnodes.simple.type.*; import com.seaofnodes.simple.util.Ary; import com.seaofnodes.simple.node.ScopeNode.Kind; @@ -521,7 +520,6 @@ public class Parser { else if (matchx("continue")) return parseContinue(); else if (matchx("struct") ) return parseStruct(); else if (inStructDeclaration() && matchx("new")) return parseConstructorDeclaration(); - else if (matchx("#showGraph")) return require(showGraph(),";"); else if (matchx(";") ) return _code.ZERO; // Empty statement // Break ambiguity around leading function types and starting a block else if (peek('{') && !isTypeFun() ) { @@ -913,15 +911,6 @@ public class Parser { return expr.unkeep(); } - /** - * Dumps out the node graph - * @return {@code null} - */ - Node showGraph() { - System.out.println(new GraphVisualizer().generateDotOutput(_ref,_scope,_xScopes)); - return null; - } - /** Parse: [name '='] expr */ private Node parseAsgn() { 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 aafaf7ed..f8674d64 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple.codegen; +import com.seaofnodes.graph.GraphObserver; import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.node.*; @@ -41,6 +42,8 @@ public class CodeGen { LastPhase // After last phase } public Phase _phase; + // Null for normal compilation; owned by this compilation, not a global viewer. + public GraphObserver<Node> _obs; /** True when tests deliberately randomize Iter worklist order. */ public static boolean iterSeedOverridden() { return System.getProperty("simple.iter.seed") != null; } @@ -426,7 +429,7 @@ public class CodeGen { } _times[Phase.Parse.ordinal()] = System.currentTimeMillis() - t0; - JSViewer.show(); + if( _obs != null ) _obs.phase(_phase.name()); return this; } @@ -466,6 +469,7 @@ public class CodeGen { assert !expensiveAssert() || Opto.fixedPointCheck(this); _times[Phase.Iter.ordinal()] = System.currentTimeMillis() - t0; + if( _obs != null ) _obs.phase(_phase.name()); return this; } @@ -515,6 +519,7 @@ public class CodeGen { Opto.opto(this); _times[Phase.Opto.ordinal()] = System.currentTimeMillis() - t0; + if( _obs != null ) _obs.phase(_phase.name()); return this; } public <N extends Node> N add( N n ) { return _iter.add(n); } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter25/src/main/java/com/seaofnodes/simple/node/Node.java index e884251c..d113a02a 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/Node.java @@ -5,7 +5,6 @@ import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.codegen.GlobalBits; import com.seaofnodes.simple.print.IRPrinter; -import com.seaofnodes.simple.print.JSViewer; import com.seaofnodes.simple.type.*; import com.seaofnodes.simple.util.*; import java.util.*; @@ -460,12 +459,12 @@ public abstract class Node implements Cloneable { * Always returns some not-null Node (often this). */ public final Node peephole( ) { - if( _type==null ) // Brand-new node, never peeped before - JSViewer.show(); + var obs = CODE._midAssert ? null : CODE._obs; + if( obs != null ) obs.before(this); Node n = peepholeOpt(); - if( n!=null ) // Made progress? - JSViewer.show(); // Show again - return n==null ? this : deadCodeElim(n._nid >= _nid ? n.peephole() : n); // Cannot return null for no-progress + Node rez = n==null ? this : deadCodeElim(n._nid >= _nid ? n.peephole() : n); + if( obs != null ) obs.after(this, n==null ? null : rez, false); + return rez; // Cannot return null for no-progress } /** @@ -637,11 +636,14 @@ public abstract class Node implements Cloneable { // retry the peephole. Track a set of Nodes dependent on `this`, and // revisit them if `this` changes. Ary<Node> _deps; + public int nDeps() { return _deps == null ? 0 : _deps.size(); } + public Node dep(int idx) { return _deps.at(idx); } private boolean addDepImpl( Node dep ) { // Running peepholes during the big assert cannot have side effects // like adding dependencies. if( CODE._midAssert ) return false; + if( CODE._obs != null ) CODE._obs.dep(this, dep); if( dep._deps==null ) dep._deps = new Ary<>(Node.class); if( dep._deps .find(this) != -1 ) return false; // Already on list if( dep._outputs.find(this) != -1 ) return false; diff --git a/chapter25/src/main/java/com/seaofnodes/simple/print/JSViewer.java b/chapter25/src/main/java/com/seaofnodes/simple/print/JSViewer.java index 42f007ea..10c07730 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/print/JSViewer.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/print/JSViewer.java @@ -1,16 +1,14 @@ package com.seaofnodes.simple.print; import com.seaofnodes.graph.GraphJson; +import com.seaofnodes.graph.GraphEvent; +import com.seaofnodes.graph.GraphSnapshot; import com.seaofnodes.simple.codegen.CodeGen; -import com.seaofnodes.simple.node.*; import java.io.IOException; import java.nio.file.Paths; import java.nio.file.Files; import java.nio.file.Path; import java.net.URI; -import java.util.ArrayList; -import java.util.UUID; -import static com.seaofnodes.simple.codegen.CodeGen.CODE; public class JSViewer implements AutoCloseable { // Display programs in an endless loop @@ -20,18 +18,12 @@ public class JSViewer implements AutoCloseable { } } - static boolean SHOW; - // WebSocket to the browser for display - static SimpleWebSocket SERVER; - - static long N; // Snapshot step within this compilation - static String COMP; - static final SimpleGraphAdapter GRAPH = new SimpleGraphAdapter(); + private final SimpleWebSocket _server; JSViewer() throws Exception { // Launch server; handshake - SERVER = new SimpleWebSocket(viewerURI(),12345); + _server = new SimpleWebSocket(viewerURI(),12345); } // The browser application is shared by all chapters. Search upwards so @@ -48,9 +40,9 @@ public class JSViewer implements AutoCloseable { } void run( ) throws Exception { - SERVER.put("!"); + _server.put("!"); while( true ) { - String src = SERVER.get(); + String src = _server.get(); switch( src ) { case null: return; case "null": return; @@ -58,55 +50,31 @@ public class JSViewer implements AutoCloseable { default: System.out.println(src); try { - N=0; - COMP = UUID.randomUUID().toString(); - // Use parser scope, xscope when building views - CodeGen code = new CodeGen(src); - SHOW = true; - show(); - // Parse program, generating views at every parse point - code.parse(); - // No longer user parse internal state when building views - code.opto(); - + compile(src); // Catch and ignore Parser errors } catch(RuntimeException re) { System.err.println(re); } finally { - SHOW = false; - SERVER.put("#"); // Final frame + _server.put("#"); // Final frame } break; } } } - @Override public void close() throws IOException { - SERVER.close(); - SERVER=null; - } - - public static void show() { if( SERVER!=null && SHOW ) _show(); } - private static void _show() { - // Skip util we at least get the Parse object made - if( CODE._phase==null || CODE._phase.ordinal() < CodeGen.Phase.Parse.ordinal() ) - return; - boolean midParse = CODE._phase == CodeGen.Phase.Parse; - var xScopes = midParse ? CODE.P._xScopes : null; - var roots = new ArrayList<Node>(); - roots.add(CODE._stop); - if( midParse ) { - roots.add(CODE.P._scope); - roots.addAll(xScopes); - } - long step = N++; - var snap = GRAPH.snap(COMP, step, roots.toArray(Node[]::new)); - int pos = midParse ? CODE.P.pos() : -1; - + private void compile(String src) { + CodeGen code = new CodeGen(src); + code._obs = new SimpleGraphObserver(code) { + @Override protected void frame(GraphSnapshot snap, int pos, GraphEvent evt) throws IOException { + _server.put(GraphJson.frame(snap, pos, evt)); + } + }; try { - SERVER.put(GraphJson.frame(snap, pos)); - } catch( IOException ioe ) { - try { SERVER.close(); } catch( IOException ignored ) {} + code.parse().opto(); + } finally { + code._obs = null; } } -}
\ No newline at end of file + + @Override public void close() throws IOException { _server.close(); } +} diff --git a/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java new file mode 100644 index 00000000..5d511287 --- /dev/null +++ b/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java @@ -0,0 +1,130 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphEvent; +import com.seaofnodes.graph.GraphObserver; +import com.seaofnodes.graph.GraphSnapshot; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.node.Node; +import java.io.IOException; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.UUID; + +/** Capture events and neighborhoods on the compiler thread. Transport is supplied by the viewer. */ +public abstract class SimpleGraphObserver extends GraphObserver<Node> { + private final CodeGen _code; + private final SimpleGraphAdapter _graph = new SimpleGraphAdapter(); + private final String _comp = UUID.randomUUID().toString(); + private final ArrayList<Peep> _peeps = new ArrayList<>(); + private long _next, _step; + private boolean _off; + + protected SimpleGraphObserver(CodeGen code) { _code = code; } + + protected abstract void frame(GraphSnapshot snap, int pos, GraphEvent evt) throws IOException; + + private static class Peep { + final Node node; + final long id, up; + final String phase; + final int pos; + final BitSet near = new BitSet(); + GraphSnapshot pre; + boolean sent; + + Peep(Node n, long id, long up, String phase, int pos) { + node = n; this.id = id; this.up = up; this.phase = phase; this.pos = pos; + } + } + + @Override public void before(Node n) { + if( _off ) return; + var p = new Peep(n, ++_next, _peeps.isEmpty() ? 0 : _peeps.getLast().id, + _code._phase.name(), pos()); + _peeps.add(p); + near(p, n); + // Buffer until we know that this attempt (or a nested one) made progress. + // Include n explicitly: parse-time nodes need not have any uses yet. + p.pre = snap(n); + } + + @Override public void after(Node n, Node repl, boolean applied) { + if( _off ) return; + Peep p = _peeps.getLast(); + assert p.node == n; + near(p, n); + near(p, repl); + for( Peep up : _peeps ) up.near.or(p.near); + if( repl != null || p.sent ) { + // Emit ancestors first so a nested rewrite always has its context. + for( Peep up : _peeps ) { + if( up.sent ) continue; + emit(up.pre, up.pos, evt(up, GraphEvent.Kind.BEFORE, 0)); + if( _off ) return; + up.pre = null; + up.sent = true; + } + Node rez = repl == null ? n : repl; + emit(snap(rez.isDead() ? null : rez), pos(), + evt(p, applied ? GraphEvent.Kind.APPLY : GraphEvent.Kind.RETURN, + rez.isDead() ? 0 : rez._nid)); + } + _peeps.removeLast(); + } + + @Override public void dep(Node n, Node def) { + if( _off || _peeps.isEmpty() ) return; + Peep p = _peeps.getLast(); + add(p, n); + add(p, def); + } + + @Override public void phase(String phase) { + if( _off ) return; + emit(snap(null), pos(), new GraphEvent(GraphEvent.Kind.PHASE, 0, 0, phase, 0, 0, new int[0])); + } + + private GraphEvent evt(Peep p, GraphEvent.Kind kind, int repl) { + return new GraphEvent(kind, p.id, p.up, p.phase, p.node._nid, repl, p.near.stream().toArray()); + } + + private void add(Peep p, Node n) { if( n != null ) p.near.set(n._nid); } + + private void near(Peep p, Node n) { + if( n == null ) return; + add(p, n); + for( Node def : n._inputs ) add(p, def); + for( Node use : n._outputs ) add(p, use); + // These are wake-up dependents, not all nodes inspected by a rule. + // Read before the compiler drains the list; never move/clear it here. + for( int i = 0; i < n.nDeps(); i++ ) add(p, n.dep(i)); + } + + private int pos() { return _code._phase == CodeGen.Phase.Parse ? _code.P.pos() : -1; } + + private GraphSnapshot snap(Node extra) { + var roots = new ArrayList<Node>(); + roots.add(_code._stop); + if( _code._phase == CodeGen.Phase.Parse ) { + roots.add(_code.P._scope); + roots.addAll(_code.P._xScopes); + } + for( Peep p : _peeps ) if( !p.node.isDead() ) roots.add(p.node); + roots.add(extra); + return _graph.snap(_comp, 0, roots.toArray(Node[]::new)); + } + + private void emit(GraphSnapshot snap, int pos, GraphEvent evt) { + if( _off ) return; + // Assign steps on emission; attempts without progress do not consume frames. + snap = new GraphSnapshot(snap.ver(), _comp, _step++, snap.roots(), snap.nodes()); + try { + frame(snap, pos, evt); + } catch( IOException e ) { + // A disconnected display must not interrupt optimization. + _off = true; + _code._obs = null; + System.err.println("Graph viewer disconnected: " + e.getMessage()); + } + } +} |
