summaryrefslogtreecommitdiff
path: root/chapter25/src/main/java/com
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-09-21 22:53:49 -0700
committerCliff Click <cliffc@acm.org>2026-09-21 22:53:49 -0700
commit92e3013a4cab12747af9d5484e7caad6fc454b1e (patch)
treea3137dd95038c3487848c355a0c7053f4a518721 /chapter25/src/main/java/com
parente48d59474230869ba81c1712128c01fd822946f5 (diff)
downloadSimple-92e3013a4cab12747af9d5484e7caad6fc454b1e.tar.gz
Simple-92e3013a4cab12747af9d5484e7caad6fc454b1e.zip
Switch over to the new ELK driver
Interact between compiler & browser now hooks several nbew places.
Diffstat (limited to 'chapter25/src/main/java/com')
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/IterPeeps.java8
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/Parser.java11
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java7
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/Node.java14
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/print/JSViewer.java74
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java130
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());
+ }
+ }
+}