summaryrefslogtreecommitdiff
path: root/chapter18/src/main/java
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2024-12-02 12:38:24 -0800
committerCliff Click <cliffc@acm.org>2025-01-15 15:07:30 -0800
commit61a06caa60b4c446f45dcb699a1546981413786d (patch)
tree2eee77a015dfe3bd9aade41f2503c3d5e716fc34 /chapter18/src/main/java
parentfac0c965b11a103a1973153d40347557d9bf68d9 (diff)
downloadSimple-61a06caa60b4c446f45dcb699a1546981413786d.tar.gz
Simple-61a06caa60b4c446f45dcb699a1546981413786d.zip
Progress JSView
Larger programs, better layouts. Missing e.g. types, & program text & the inter peeps phase
Diffstat (limited to 'chapter18/src/main/java')
-rw-r--r--chapter18/src/main/java/com/seaofnodes/simple/Ary.java12
-rw-r--r--chapter18/src/main/java/com/seaofnodes/simple/GraphVisualizer.java13
-rw-r--r--chapter18/src/main/java/com/seaofnodes/simple/JSViewer.java349
-rw-r--r--chapter18/src/main/java/com/seaofnodes/simple/Parser.java7
-rw-r--r--chapter18/src/main/java/com/seaofnodes/simple/SB.java74
-rw-r--r--chapter18/src/main/java/com/seaofnodes/simple/SimpleWebSocket.java19
-rw-r--r--chapter18/src/main/java/com/seaofnodes/simple/node/CProjNode.java3
-rw-r--r--chapter18/src/main/java/com/seaofnodes/simple/node/MultiUse.java3
-rw-r--r--chapter18/src/main/java/com/seaofnodes/simple/node/Node.java14
-rw-r--r--chapter18/src/main/java/com/seaofnodes/simple/node/ProjNode.java3
-rw-r--r--chapter18/src/main/java/com/seaofnodes/simple/node/StopNode.java1
11 files changed, 429 insertions, 69 deletions
diff --git a/chapter18/src/main/java/com/seaofnodes/simple/Ary.java b/chapter18/src/main/java/com/seaofnodes/simple/Ary.java
index 8654d41e..d37c284f 100644
--- a/chapter18/src/main/java/com/seaofnodes/simple/Ary.java
+++ b/chapter18/src/main/java/com/seaofnodes/simple/Ary.java
@@ -57,18 +57,6 @@ public class Ary<E> extends AbstractList<E> implements List<E> {
return true;
}
- ///** Slow, linear-time, element insert. Preserves order.
- // * @param i index to insert at, between 0 and _len inclusive.
- // * @param e Element to insert
- // */
- //public void insert( int i, E e ) {
- // if( i < 0 || i>_len )
- // throw new ArrayIndexOutOfBoundsException(""+i+" >= "+_len);
- // if( _len >= _es.length ) _es = Arrays.copyOf(_es,Math.max(1,_es.length<<1));
- // System.arraycopy(_es,i,_es,i+1,(_len++)-i);
- // _es[i] = e;
- //}
-
/** Fast, constant-time, element removal. Does not preserve order
* @param i element to be removed
* @return element removed */
diff --git a/chapter18/src/main/java/com/seaofnodes/simple/GraphVisualizer.java b/chapter18/src/main/java/com/seaofnodes/simple/GraphVisualizer.java
index ec326a6f..eb8d8e23 100644
--- a/chapter18/src/main/java/com/seaofnodes/simple/GraphVisualizer.java
+++ b/chapter18/src/main/java/com/seaofnodes/simple/GraphVisualizer.java
@@ -27,7 +27,7 @@ public class GraphVisualizer {
// Since the graph has cycles, we need to create a flat list of all the
// nodes in the graph.
- Collection<Node> all = findAll(stop, scope);
+ Collection<Node> all = findAll(xScopes, stop, scope);
StringBuilder sb = new StringBuilder();
sb.append("digraph chapter18 {\n");
sb.append("/*\n");
@@ -263,20 +263,19 @@ public class GraphVisualizer {
/**
* Finds all nodes in the graph.
*/
- private Collection<Node> findAll(Node stop, Node scope) {
+ public static Collection<Node> findAll(Stack<ScopeNode> ss, Node... ns) {
final HashMap<Integer, Node> all = new HashMap<>();
- for( Node n : stop._inputs )
+ for( Node n : ns )
+ walk(all, n);
+ for( Node n : ss )
walk(all, n);
- if( scope != null )
- for( Node n : scope._inputs )
- walk(all, n);
return all.values();
}
/**
* Walk a subgraph and populate distinct nodes in the all list.
*/
- private void walk(HashMap<Integer,Node> all, Node n) {
+ private static void walk(HashMap<Integer,Node> all, Node n) {
if(n == null ) return;
if (all.get(n._nid) != null) return; // Been there, done that
all.put(n._nid, n);
diff --git a/chapter18/src/main/java/com/seaofnodes/simple/JSViewer.java b/chapter18/src/main/java/com/seaofnodes/simple/JSViewer.java
index da361afa..48776fd5 100644
--- a/chapter18/src/main/java/com/seaofnodes/simple/JSViewer.java
+++ b/chapter18/src/main/java/com/seaofnodes/simple/JSViewer.java
@@ -1,50 +1,331 @@
package com.seaofnodes.simple;
+import com.seaofnodes.simple.node.*;
+
import java.nio.file.Paths;
+import java.io.IOException;
+import java.util.Stack;
+import java.util.Collection;
-public class JSViewer {
+public class JSViewer implements AutoCloseable {
+ // Display programs in an endless loop
public static void main( String[] args ) throws Exception {
- // Launch server
- try( SimpleWebSocket server = new SimpleWebSocket(Paths.get("docs/index.html").toUri(),12345) ) {
- int N=0;
- while( true ) {
- String src = server.get();
- if( src==null || src.equals("null") )
- break;
+ try(var js = new JSViewer()) {
+ js.run();
+ }
+ }
+
+ static boolean SHOW;
+
+ // WebSocket to the browser for display
+ static SimpleWebSocket SERVER;
+
+ static int N; // Dot frames
+ static Parser P; //
+ static StopNode STOP; //
+
+ JSViewer() throws Exception {
+ // Launch server; hand shake
+ SERVER = new SimpleWebSocket(Paths.get("docs/index.html").toUri(),12345) ;
+ }
+
+ void run( ) throws Exception {
+ while( true ) {
+ String src = SERVER.get();
+ switch( src ) {
+ case null: return;
+ case "null": return;
+ case "+": break; // Client requests more frames, but we send them all anyways
+ default:
System.out.println(src);
- // Client requests another frame
- if( src.charAt(0)=='+' ) {
- if( N<2 ) {
- StringBuilder sb = new StringBuilder();
- sb.append("digraph jsviewer_1 {\n");
- sb.append("\tnode [shape=circle, style=filled];\n");
- sb.append("\tSRC [label=\"").append("next frame").append("\", shape=oval];\n");
- sb.append("}");
- server.put(sb.toString());
- System.out.println("Sent graph "+N);
- N++;
- } else {
- // no more frames, send nothing
+ try {
+ N=0;
+ // Use parser scope, xscope when building views
+ P = new Parser(src);
+ STOP = P.STOP;
+ SHOW = true;
+ show();
+ // Parse program, generating views at every parse point
+ P.parse();
+ // No longer user parse internal state when building views
+ P = null;
+
+
+
+ // Catch and ignore Parser errors
+ } catch(RuntimeException re) {
+ } finally {
+ SHOW = false;
+ 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() {
+ Collection<Node> all = GraphVisualizer.findAll(P==null ? null : P._xScopes, STOP, P==null ? null : P._scope);
+ SB sb = new SB();
+ sb.p("digraph view_").p(N++).p(" {\n").ii();
+
+ // To keep the Scopes below the graph and pointing up into the graph we
+ // need to group the Nodes in a subgraph cluster, and the scopes into a
+ // different subgraph cluster. THEN we can draw edges between the
+ // scopes and nodes. If we try to cross subgraph cluster borders while
+ // still making the subgraphs DOT gets confused.
+ sb.i().p("rankdir=BT;\n"); // Force Nodes before Scopes
+
+ // CNC - turned off Apr/8/2024, gives more flex in the layout and
+ // removes some of the more ludicrous layout choices.
+ // Preserve node input order
+ //sb.append("ordering=\"in\";\n");
+
+ // Merge multiple edges hitting the same node. Makes common shared
+ // nodes much prettier to look at.
+ sb.i().p("concentrate=\"true\";\n");
+
+ // Force nested scopes to order
+ sb.i().p("compound=\"true\";\n");
+
+ // Just the Nodes first, in a cluster no edges
+ nodes(sb, all);
+
+ // Now the scopes, in a cluster no edges
+ if( P!=null )
+ for( ScopeNode xscope : P._xScopes )
+ if( !xscope.isDead() )
+ scope( sb, xscope );
+
+ // Walk the Node edges
+ nodeEdges(sb, P!=null && !P._xScopes.empty() ? P._xScopes.peek() : null, all);
+
+ // Walk the active Scope edges
+ if( P!=null )
+ for( ScopeNode xscope : P._xScopes )
+ if( !xscope.isDead() )
+ scopeEdges( sb, xscope );
+
+
+ sb.p("}\n").di();
+ // Tell client another DOT frame
+ String dot = sb.toString();
+ System.out.println(dot);
+ try {
+ SERVER.put(dot);
+ } catch( IOException ioe ) {
+ try { SERVER.close();} catch( IOException ignored ){}
+ }
+ }
+
+
+
+ private static void nodes(SB sb, Collection<Node> all) {
+ // Just the Nodes first, in a cluster no edges
+ sb.i().p("subgraph cluster_Nodes {\n").ii(); // Magic "cluster_" in the subgraph name
+ for (Node n : all) {
+ if( n instanceof ProjNode || n instanceof CProjNode || n instanceof ScopeMinNode || n==Parser.XCTRL )
+ continue; // Do not emit, rolled into MultiNode or Scope cluster already
+ sb.i().p(n.uniqueName()).p(" [ ");
+ String lab = n.glabel();
+ if( n instanceof MultiNode ) {
+ // Make a box with the MultiNode on top, and all the projections on the bottom
+ sb. p("shape=plaintext label=<\n").ii();
+ sb.i().p("<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\" CELLPADDING=\"4\">\n");
+ // Row over single cell for node label
+ cell(sb.i().p("<TR>"), lab, null, true, false).p("</TR>\n");
+ // Row over single cell, for nested table
+ sb.i().p("<TR><TD>\n").ii();
+ sb.i().p("<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">").p("\n");
+ sb.i().p("<TR>");
+ n._outputs.sort((x,y) -> x instanceof ProjNode xp && y instanceof ProjNode yp ? (xp._idx - yp._idx) : ((x==null ? 99999 : x._nid) - (y==null ? 99999 : y._nid)));
+ boolean empty_row=true;
+ for( Node use : n._outputs )
+ if( use instanceof MultiUse muse ) {
+ cell(sb,use.glabel(),"p"+muse.idx(),use instanceof CFGNode,use.isMem());
+ empty_row=false;
}
- } else {
+ // At least one cell on row
+ if( empty_row )
+ cell(sb,"",null,false,false);
+ sb. p("</TR>").p("\n");
+ sb.i().p("</TABLE>").p("\n").di();
+ sb.i().p("</TD></TR>\n");
+ sb.i().p("</TABLE>>\n").di();
+ sb.i().p("];\n");
- StringBuilder sb = new StringBuilder();
- sb.append("digraph jsviewer_0 {\n");
- sb.append("\tnode [shape=circle, style=filled];\n");
- sb.append("\tSRC [label=\"").append(src).append("\", shape=oval];\n");
- sb.append("}");
- server.put(sb.toString());
- System.out.println("Sent graph "+N);
- N++;
+ } else {
+ // control nodes have box shape
+ // other nodes are ellipses, i.e. default shape
+ if( n instanceof CFGNode ) sb.p("shape=box style=filled fillcolor=yellow ");
+ else if (n instanceof PhiNode) sb.p("style=filled fillcolor=lightyellow ");
+ sb.p("label=\"").p(lab).p("\" ];\n");
+ }
+ }
+ // Force Region & Phis to line up
+ for (Node n : all) {
+ if (n instanceof RegionNode region) {
+ sb.i().p("{ rank=same; ");
+ sb.p(region.uniqueName()).p(";");
+ for (Node phi : region._outputs)
+ if (phi instanceof PhiNode) sb.p(phi.uniqueName()).p(";");
+ sb.p("}\n");
+ }
+ }
+ sb.di().i().p("}").nl(); // End Node cluster
+ }
+
+ // Build a nested scope display, walking the _prev edge
+ private static void scope( SB sb, ScopeNode scope ) {
+ sb.i().p("node [shape=plaintext];\n");
+ int last = scope.nIns();
+ int max = scope._lexSize.size();
+ for( int i = 0; i < max; i++ ) {
+ int level = max-i-1;
+ String scopeName = makeScopeName(scope, level);
+ sb.i().p("subgraph cluster_").p(scopeName).p(" {\n").ii(); // Magic "cluster_" in the subgraph name
+ sb.i().p(scopeName).p(" [label=<\n").ii();
+ sb.i().p("<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">\n");
+ // Add the scope level
+ sb.i().p("<TR><TD BGCOLOR=\"cyan\">").p(level).p("</TD>");
+ int lexStart=scope._lexSize.at(level);
+ // Special for memory ScopeMinNode
+ ScopeMinNode n = scope.nIns()>1 ? scope.mem() : null;
+ if( level==0 && n!=null && n.nIns()>2 ) {
+ sb. p("<TD BGCOLOR=\"blue\" BORDER=0 colspan=\"").p(last-lexStart-1).p("\">\n");
+ sb.i().p("\t<TABLE><TR>");
+ for( int m=2; m<n.nIns(); m++ ) {
+ sb.p("<TD>").p(m).p("</TD>");
+ throw Utils.TODO(); // Need port names for edges
}
+ sb.p("</TR></TABLE>\n");
+ sb.i().p("</TD>");
+ }
+ sb. p("</TR>\n"); // End scope level
+ sb.i().p("<TR>\n");
+ for( int j=lexStart; j<last; j++ ) {
+ var v = scope._vars.at(j);
+ cell(sb.i(),v._name,makePortName(scopeName, v._name),j==0,j==1).nl();
+ }
+ last = lexStart;
+ sb.i().p("</TR>\n");
+ sb.i().p("</TABLE>>];\n").di();
+ }
+ // Scope clusters nest, so the graphics shows the nested scopes, so
+ // they are not closed as they are printed; so they just keep nesting.
+ // We close them all at once here.
+ for( int i=0; i<max; i++ )
+ sb.di().i().p("}\n");
+ }
- //// Client reset, send new program
- //server.put("!");
+ // Append a cell, with color
+ private static SB cell(SB sb, String text, String port, boolean isCFG, boolean isMem) {
+ sb.p("<TD ");
+ if( port!=null ) sb.p("PORT=\"").p(port).p("\"");
+ if( isCFG ) sb.p(" BGCOLOR=\"yellow\"");
+ if( isMem ) sb.p(" BGCOLOR=\"blue\"");
+ sb.p(">");
+ if( isMem ) sb.p("<FONT color=\"white\">");
+ sb.p(text);
+ if( isMem ) sb.p("</FONT>");
+ sb.p("</TD>");
+ return sb;
+ }
+
+ private static String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; }
+ private static String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; }
+
+ // Walk the node edges
+ private static void nodeEdges(SB sb, ScopeNode scope, Collection<Node> all) {
+ // All them edge labels
+ String scopeName = scope!=null ? makeScopeName(scope, 0) : null;
+ sb.i().p("edge [ fontname=Helvetica, fontsize=8 ];\n");
+ for( Node n : all ) {
+ // Do not display the Constant->Start edge;
+ if( n instanceof ConstantNode ||
+ n instanceof XCtrlNode ||
+ // ProjNodes handled by Multi;
+ n instanceof ProjNode ||
+ n instanceof CProjNode ||
+ // ScopeNodes are done separately
+ n instanceof ScopeNode ||
+ n instanceof ScopeMinNode
+ )
+ continue;
+ if( n.isDead() )
+ throw Utils.TODO();
+ // Normal edges
+ for( int i=0; i<n.nIns(); i++ ) {
+ Node def = n.in(i);
+ if( def==null ) continue;
+ sb.i().p(n.uniqueName()).p(" -> ");
+ if( n instanceof PhiNode && def instanceof RegionNode ) {
+ // Draw a dotted use->def edge from Phi to Region.
+ sb.p(def.uniqueName());
+ sb.p(" [style=dotted taillabel=").p(i).p("];\n");
+ } else {
+ // Most edges land here use->def
+ if( def instanceof CProjNode proj ) {
+ String mname = proj.ctrl().uniqueName();
+ sb.p(mname).p(":p").p(proj._idx);
+ } else if( def instanceof ProjNode proj ) {
+ String mname = proj.in(0).uniqueName();
+ sb.p(mname).p(":p").p(proj._idx);
+ } else sb.p(def.uniqueName());
+ // Number edges, so we can see how they track
+ sb.p("[taillabel=").p(i);
+ // The edge from New to ctrl is just for anchoring the New
+ if ( n instanceof NewNode )
+ sb.p(" color=green");
+ // control edges are colored red
+ else if( def instanceof CFGNode )
+ sb.p(" color=red");
+ else if( def.isMem() )
+ sb.p(" color=blue");
+ // Backedges do not add a ranking constraint
+ if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) )
+ sb.p(" constraint=false");
+ sb.p("];\n");
+ }
+ }
+
+ // Bonus edge if hooked by parser
+ if( (n.iskeep() || n.isUnused()) && scopeName != null ) {
+ sb.i().p(scopeName).p(" -> ").p(n.uniqueName()).p(" [ stype=dotted color=grey]\n");
}
- System.out.println("Client requested shutdown");
- server.close();
}
+ }
+ // Walk the scope edges
+ private static void scopeEdges( SB sb, ScopeNode scope ) {
+ sb.i().p("edge [style=dashed color=cornflowerblue];\n");
+ int level=0;
+ for( var v : scope._vars ) {
+ if( v._idx==1 ) continue; // Memory done seperately
+ Node def = scope.in(v._idx);
+ while( def instanceof ScopeNode lazy )
+ def = lazy.in(v._idx);
+ if( def==null ) continue;
+ while( level < scope._lexSize.size() && v._idx >= scope._lexSize.at(level) )
+ level++;
+ String scopeName = makeScopeName(scope, level-1);
+ sb.i()
+ .p(scopeName).p(":")
+ .p('"').p(makePortName(scopeName, v._name)).p('"') // wrap port name with quotes because $ctrl is not valid unquoted
+ .p(" -> ");
+ if( def instanceof CProjNode proj ) {
+ sb.p(def.in(0).uniqueName()).p(":p").p(proj._idx);
+ } else if( def instanceof ProjNode proj ) {
+ sb.p(def.in(0).uniqueName()).p(":p").p(proj._idx);
+ } else sb.p(def.uniqueName());
+ sb.p(";\n");
+ }
}
+
}
diff --git a/chapter18/src/main/java/com/seaofnodes/simple/Parser.java b/chapter18/src/main/java/com/seaofnodes/simple/Parser.java
index 94575559..123841e8 100644
--- a/chapter18/src/main/java/com/seaofnodes/simple/Parser.java
+++ b/chapter18/src/main/java/com/seaofnodes/simple/Parser.java
@@ -82,7 +82,6 @@ public class Parser {
add("while");
}};
-
/**
* We clone ScopeNodes when control flows branch; it is useful to have
* a list of all active ScopeNodes for purposes of visualization of the SoN graph
@@ -100,6 +99,7 @@ public class Parser {
// Mapping from a type name to the constructor for a Type.
public final HashMap<String, StructNode> INITS;
+ public Parser(String source) { this(source, TypeInteger.BOT); }
public Parser(String source, TypeInteger arg) {
Node.reset();
@@ -117,10 +117,6 @@ public class Parser {
INITS = new HashMap<>();
}
- public Parser(String source) {
- this(source, TypeInteger.BOT);
- }
-
@Override
public String toString() { return _lexer.toString(); }
@@ -162,6 +158,7 @@ public class Parser {
mem.addDef( new ProjNode(START, 1, ScopeNode.MEM0).peephole());
_scope.define(ScopeNode.MEM0, TypeMem.TOP , false, mem .peephole());
_scope.define(ScopeNode.ARG0, TypeInteger.BOT, false, new ProjNode(START, 2, ScopeNode.ARG0).peephole());
+ JSViewer.show();
// Parse whole program
parseBlock(false);
diff --git a/chapter18/src/main/java/com/seaofnodes/simple/SB.java b/chapter18/src/main/java/com/seaofnodes/simple/SB.java
new file mode 100644
index 00000000..6f5a29ee
--- /dev/null
+++ b/chapter18/src/main/java/com/seaofnodes/simple/SB.java
@@ -0,0 +1,74 @@
+package com.seaofnodes.simple;
+
+/** Tight/tiny StringBuilder wrapper.
+ * Short short names on purpose; so they don't obscure the printing.
+ * Can't believe this wasn't done long, long ago. */
+public final class SB {
+ private final StringBuilder _sb;
+ private int _indent = 0;
+ public SB( ) { _sb = new StringBuilder( ); }
+ public SB(String s) { _sb = new StringBuilder(s); }
+ public SB p( String s ) { _sb.append(s); return this; }
+ public SB p( float s ) {
+ if( Float.isNaN(s) )
+ _sb.append( "Float.NaN");
+ else if( Float.isInfinite(s) ) {
+ _sb.append(s > 0 ? "Float.POSITIVE_INFINITY" : "Float.NEGATIVE_INFINITY");
+ } else _sb.append(s);
+ return this;
+ }
+ public SB p( double s ) {
+ if( Double.isNaN(s) )
+ _sb.append("Double.NaN");
+ else if( Double.isInfinite(s) ) {
+ _sb.append(s > 0 ? "Double.POSITIVE_INFINITY" : "Double.NEGATIVE_INFINITY");
+ } else _sb.append(s);
+ return this;
+ }
+ public SB p( char s ) { _sb.append(s); return this; }
+ public SB p( int s ) { _sb.append(s); return this; }
+ public SB p( long s ) { _sb.append(s); return this; }
+ public SB p( boolean s) { _sb.append(s); return this; }
+ // Not spelled "p" on purpose: too easy to accidentally say "p(1.0)" and
+ // suddenly call the autoboxed version.
+ public SB pobj( Object s ) { _sb.append(s.toString()); return this; }
+ public SB i( int d ) { for( int i=0; i<d+_indent; i++ ) p(" "); return this; }
+ public SB i( ) { return i(0); }
+ public SB ip(String s) { return i().p(s); }
+ public SB s() { _sb.append(' '); return this; }
+
+ // Increase indentation
+ public SB ii( int i) { _indent += i; return this; }
+ public SB ii() { return ii(1); }
+ // Decrease indentation
+ public SB di( int i) { _indent -= i; return this; }
+ public SB di() { return di(1); }
+
+ public SB nl( ) { return p(System.lineSeparator()); }
+ // Last printed a nl
+ public boolean was_nl() {
+ int len = _sb.length();
+ String nl = System.lineSeparator();
+ int nlen = nl.length();
+ if( len < nlen ) return false;
+ for( int i=0; i<nlen; i++ )
+ if( _sb.charAt(len-nlen+i)!=nl.charAt(i) )
+ return false;
+ return true;
+ }
+
+ // Delete last char. Useful when doing string-joins and JSON printing and an
+ // extra separater char needs to be removed:
+ //
+ // sb.p('[');
+ // for( Foo foo : foos )
+ // sb.p(foo).p(',');
+ // sb.unchar().p(']'); // remove extra trailing comma
+ //
+ public SB unchar() { return unchar(1); }
+ public SB unchar(int x) { _sb.setLength(_sb.length()-x); return this; }
+
+ public SB clear() { _sb.setLength(0); return this; }
+ public int len() { return _sb.length(); }
+ @Override public String toString() { return _sb.toString(); }
+}
diff --git a/chapter18/src/main/java/com/seaofnodes/simple/SimpleWebSocket.java b/chapter18/src/main/java/com/seaofnodes/simple/SimpleWebSocket.java
index 86ae7c9b..114bfddd 100644
--- a/chapter18/src/main/java/com/seaofnodes/simple/SimpleWebSocket.java
+++ b/chapter18/src/main/java/com/seaofnodes/simple/SimpleWebSocket.java
@@ -60,7 +60,7 @@ class SimpleWebSocket extends ServerSocket {
_len = _in.read(_ins); _x=0;
int op = rawget();
if( (op&0x80)!=0x80 ) // FIN bit not set?
- throw Utils.TODO("Multi-part from client: "+op); // Multi-part message
+ throw Utils.TODO("Multi-part from client: "+op); // Multipart message
op &= 0x7F; // Strip FIN bit
switch( op&15) {
case 1: {
@@ -89,9 +89,20 @@ class SimpleWebSocket extends ServerSocket {
// Classic put to browser client. No encoding.
public void put(String msg) throws IOException {
- assert msg.length() < 125;
- _out.write(129); // FIN
- _out.write(msg.length());
+ int len = msg.length();
+ if( len <= 125 ) {
+ _out.write(129); // FIN, opcode 1 - whole text message
+ _out.write(len);
+ for( int i=0; i<msg.length(); i++ )
+ _out.write(msg.charAt(i));
+ return;
+ }
+ if( len > 65535 )
+ throw Utils.TODO("MOAR LENGTH");
+ _out.write(129); // FIN, opcode 1 - whole text message
+ _out.write(126); // Length is 16b
+ _out.write(len>>>8);
+ _out.write(len&255);
for( int i=0; i<msg.length(); i++ )
_out.write(msg.charAt(i));
}
diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/CProjNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/CProjNode.java
index d2f1bbe1..0b9d89d2 100644
--- a/chapter18/src/main/java/com/seaofnodes/simple/node/CProjNode.java
+++ b/chapter18/src/main/java/com/seaofnodes/simple/node/CProjNode.java
@@ -6,7 +6,7 @@ import com.seaofnodes.simple.type.TypeMem;
import com.seaofnodes.simple.type.TypeTuple;
import java.util.BitSet;
-public class CProjNode extends CFGNode {
+public class CProjNode extends CFGNode implements MultiUse {
// Which slice of the incoming multipart value
public final int _idx;
@@ -21,6 +21,7 @@ public class CProjNode extends CFGNode {
}
@Override public String label() { return _label; }
+ @Override public int idx() { return _idx; }
@Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(_label); }
diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/MultiUse.java b/chapter18/src/main/java/com/seaofnodes/simple/node/MultiUse.java
new file mode 100644
index 00000000..e4548dfd
--- /dev/null
+++ b/chapter18/src/main/java/com/seaofnodes/simple/node/MultiUse.java
@@ -0,0 +1,3 @@
+package com.seaofnodes.simple.node;
+
+public interface MultiUse { abstract int idx(); }
diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter18/src/main/java/com/seaofnodes/simple/node/Node.java
index 3827fdac..040e3a0e 100644
--- a/chapter18/src/main/java/com/seaofnodes/simple/node/Node.java
+++ b/chapter18/src/main/java/com/seaofnodes/simple/node/Node.java
@@ -1,9 +1,6 @@
package com.seaofnodes.simple.node;
-import com.seaofnodes.simple.Ary;
-import com.seaofnodes.simple.IRPrinter;
-import com.seaofnodes.simple.IterPeeps;
-import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.*;
import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeFloat;
import java.util.*;
@@ -306,7 +303,7 @@ public abstract class Node implements OutNode {
return this; // Peephole optimizations turned off
}
Node n = peepholeOpt();
- return n==null ? this : deadCodeElim(n.peephole()); // Cannot return null for no-progress
+ return n==null ? this : deadCodeElim(n._nid >= _nid ? n.peephole() : n); // Cannot return null for no-progress
}
/**
@@ -328,6 +325,13 @@ public abstract class Node implements OutNode {
* </ul>
*/
public final Node peepholeOpt( ) {
+ JSViewer.show();
+ Node n = _peepholeOpt();
+ if( n==null ) return null;
+ JSViewer.show();
+ return n;
+ }
+ private final Node _peepholeOpt( ) {
ITER_CNT++;
// Compute initial or improved Type
Type old = setType(compute());
diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/ProjNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/ProjNode.java
index 73c1fa15..525b5df0 100644
--- a/chapter18/src/main/java/com/seaofnodes/simple/node/ProjNode.java
+++ b/chapter18/src/main/java/com/seaofnodes/simple/node/ProjNode.java
@@ -6,7 +6,7 @@ import com.seaofnodes.simple.type.TypeTuple;
import java.util.BitSet;
-public class ProjNode extends Node {
+public class ProjNode extends Node implements MultiUse {
// Which slice of the incoming multipart value
public final int _idx;
@@ -21,6 +21,7 @@ public class ProjNode extends Node {
}
@Override public String label() { return _label; }
+ @Override public int idx() { return _idx; }
@Override
StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(_label); }
diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/StopNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/StopNode.java
index 867225cb..269c2f6c 100644
--- a/chapter18/src/main/java/com/seaofnodes/simple/node/StopNode.java
+++ b/chapter18/src/main/java/com/seaofnodes/simple/node/StopNode.java
@@ -15,6 +15,7 @@ public class StopNode extends CFGNode {
public StopNode(String src) {
super();
_src = src;
+ _type = compute();
}
@Override