From 736c658c274404084b15b450b2ab72d0e7569586 Mon Sep 17 00:00:00 2001 From: Dibyendu Majumdar Date: Fri, 3 Nov 2023 21:21:54 +0000 Subject: Backport changes in Chapter 5 to previous chapters and update Chapter5 documentation (#34) * Backport changes from chapter 5 to older chapters (#30) * Fix wrong indent * Backport to chapter 4 * Backport to chapter 3 * Backport to chapter 2 * Backport to chapter 1 * Add if statement to comment (#32) * Update visuals for chapters 1,2 & 3. Reenable drawing of edges from constants to Start for these chapters. * Update visuals for chapters 4. Revise text to explain the labeling of edges in graphs and that we omit edge from Constant to Start nodes from chapter 4. * Add code snippets for if statement parsing and the duplication and merging of ScopeNodes. Explain how scopeNodes are duplicated and merged. * Add constant check to minus node (#31) * Fix closing root block with } (#33) * Backport fix closing root block with } to chapter 4 and 3 (#35) * Show use-def edge from Phi to Region as dotted edge. Add example of Phi peephole. Update visuals. Add more examples. * Fix visuals in chapter 1 and README in chapter 4. * Remove subheading * Adjust spaces --------- Co-authored-by: XmiliaH <45106915+XmiliaH@users.noreply.github.com> --- chapter01/README.md | 4 +- chapter01/docs/01-graph.gv | 14 +- chapter01/docs/01-graph.svg | 47 +-- .../main/java/com/seaofnodes/simple/Parser.java | 88 ++--- .../com/seaofnodes/simple/node/ConstantNode.java | 6 +- .../java/com/seaofnodes/simple/node/Control.java | 9 - .../main/java/com/seaofnodes/simple/node/Node.java | 32 +- .../com/seaofnodes/simple/node/ReturnNode.java | 12 +- .../java/com/seaofnodes/simple/node/StartNode.java | 5 +- .../java/com/seaofnodes/simple/Chapter01Test.java | 41 +- chapter02/README.md | 5 +- chapter02/docs/02-post-peephole-ex1.gv | 16 +- chapter02/docs/02-post-peephole-ex1.svg | 47 +-- chapter02/docs/02-pre-peephole-ex1.gv | 50 +-- chapter02/docs/02-pre-peephole-ex1.svg | 161 ++++---- .../com/seaofnodes/simple/GraphVisualizer.java | 76 ++-- .../main/java/com/seaofnodes/simple/Parser.java | 69 ++-- .../java/com/seaofnodes/simple/node/AddNode.java | 20 +- .../com/seaofnodes/simple/node/ConstantNode.java | 15 +- .../java/com/seaofnodes/simple/node/Control.java | 9 - .../java/com/seaofnodes/simple/node/DivNode.java | 25 +- .../java/com/seaofnodes/simple/node/MinusNode.java | 15 +- .../java/com/seaofnodes/simple/node/MulNode.java | 24 +- .../main/java/com/seaofnodes/simple/node/Node.java | 106 ++--- .../com/seaofnodes/simple/node/ReturnNode.java | 9 +- .../java/com/seaofnodes/simple/node/StartNode.java | 16 +- .../java/com/seaofnodes/simple/node/SubNode.java | 20 +- .../java/com/seaofnodes/simple/type/TypeBot.java | 4 +- .../com/seaofnodes/simple/type/TypeInteger.java | 4 +- .../java/com/seaofnodes/simple/Chapter02Test.java | 41 +- chapter03/docs/03-graph1.gv | 46 ++- chapter03/docs/03-graph1.svg | 93 ++--- chapter03/docs/03-graph2.gv | 46 ++- chapter03/docs/03-graph2.svg | 105 ++--- chapter03/docs/03-graph3.gv | 49 +-- chapter03/docs/03-graph3.svg | 105 ++--- .../com/seaofnodes/simple/GraphVisualizer.java | 85 ++-- .../main/java/com/seaofnodes/simple/Parser.java | 114 ++++-- .../java/com/seaofnodes/simple/node/AddNode.java | 20 +- .../com/seaofnodes/simple/node/ConstantNode.java | 12 +- .../java/com/seaofnodes/simple/node/Control.java | 9 - .../java/com/seaofnodes/simple/node/DivNode.java | 25 +- .../java/com/seaofnodes/simple/node/MinusNode.java | 15 +- .../java/com/seaofnodes/simple/node/MulNode.java | 24 +- .../main/java/com/seaofnodes/simple/node/Node.java | 111 +++--- .../com/seaofnodes/simple/node/ReturnNode.java | 9 +- .../java/com/seaofnodes/simple/node/ScopeNode.java | 32 +- .../java/com/seaofnodes/simple/node/StartNode.java | 16 +- .../java/com/seaofnodes/simple/node/SubNode.java | 20 +- .../java/com/seaofnodes/simple/type/TypeBot.java | 4 +- .../com/seaofnodes/simple/type/TypeInteger.java | 4 +- .../java/com/seaofnodes/simple/Chapter03Test.java | 68 +++- chapter04/README.md | 27 +- chapter04/docs/04-post-peephole.gv | 32 +- chapter04/docs/04-post-peephole.svg | 82 ++-- chapter04/docs/04-pre-peephole.gv | 42 +- chapter04/docs/04-pre-peephole.svg | 116 +++--- .../com/seaofnodes/simple/GraphVisualizer.java | 59 +-- .../main/java/com/seaofnodes/simple/Parser.java | 123 ++++-- .../java/com/seaofnodes/simple/node/AddNode.java | 80 ++-- .../java/com/seaofnodes/simple/node/BoolNode.java | 5 +- .../java/com/seaofnodes/simple/node/Control.java | 9 - .../java/com/seaofnodes/simple/node/DivNode.java | 11 +- .../java/com/seaofnodes/simple/node/MinusNode.java | 11 +- .../java/com/seaofnodes/simple/node/MulNode.java | 17 +- .../java/com/seaofnodes/simple/node/MultiNode.java | 8 +- .../main/java/com/seaofnodes/simple/node/Node.java | 47 +-- .../java/com/seaofnodes/simple/node/ProjNode.java | 4 +- .../com/seaofnodes/simple/node/ReturnNode.java | 8 +- .../java/com/seaofnodes/simple/node/ScopeNode.java | 27 +- .../java/com/seaofnodes/simple/node/StartNode.java | 16 +- .../java/com/seaofnodes/simple/node/SubNode.java | 9 +- .../com/seaofnodes/simple/type/TypeInteger.java | 2 +- .../java/com/seaofnodes/simple/type/TypeTuple.java | 4 +- .../java/com/seaofnodes/simple/Chapter04Test.java | 113 ++++-- chapter05/README.md | 220 ++++++++++- chapter05/docs/05-graph2.gv | 9 +- chapter05/docs/05-graph2.svg | 196 +++++----- chapter05/docs/05-graph3.gv | 15 +- chapter05/docs/05-graph3.svg | 176 +++++---- chapter05/docs/05-graph4.gv | 63 +++ chapter05/docs/05-graph4.svg | 216 +++++++++++ chapter05/docs/05-graph5.gv | 60 +++ chapter05/docs/05-graph5.svg | 196 ++++++++++ chapter05/docs/05-graph6.gv | 58 +++ chapter05/docs/05-graph6.svg | 176 +++++++++ chapter05/docs/05-graph7.gv | 72 ++++ chapter05/docs/05-graph7.svg | 269 +++++++++++++ chapter05/docs/05-graph8.gv | 129 +++++++ chapter05/docs/05-graph8.svg | 430 +++++++++++++++++++++ .../com/seaofnodes/simple/GraphVisualizer.java | 16 +- .../main/java/com/seaofnodes/simple/Parser.java | 3 +- .../java/com/seaofnodes/simple/node/MinusNode.java | 2 +- .../java/com/seaofnodes/simple/node/ScopeNode.java | 12 + .../java/com/seaofnodes/simple/Chapter05Test.java | 83 +++- 95 files changed, 3772 insertions(+), 1483 deletions(-) delete mode 100644 chapter01/src/main/java/com/seaofnodes/simple/node/Control.java delete mode 100644 chapter02/src/main/java/com/seaofnodes/simple/node/Control.java delete mode 100644 chapter03/src/main/java/com/seaofnodes/simple/node/Control.java delete mode 100644 chapter04/src/main/java/com/seaofnodes/simple/node/Control.java create mode 100644 chapter05/docs/05-graph4.gv create mode 100644 chapter05/docs/05-graph4.svg create mode 100644 chapter05/docs/05-graph5.gv create mode 100644 chapter05/docs/05-graph5.svg create mode 100644 chapter05/docs/05-graph6.gv create mode 100644 chapter05/docs/05-graph6.svg create mode 100644 chapter05/docs/05-graph7.gv create mode 100644 chapter05/docs/05-graph7.svg create mode 100644 chapter05/docs/05-graph8.gv create mode 100644 chapter05/docs/05-graph8.svg diff --git a/chapter01/README.md b/chapter01/README.md index e09b61a3..f5f31f82 100644 --- a/chapter01/README.md +++ b/chapter01/README.md @@ -164,5 +164,7 @@ return 1; ![Sea of Nodes Visual](./docs/01-graph.svg) -* Control nodes appear as square boxes +* Control nodes appear as square boxes with yellow background * Control edges are in bold red +* The edges from Constants to Start are shown in dotted lines as these are not true control edges +* We label each edge with its position in the `_inputs` array, thus `0` means the edge is `_inputs[0]`. diff --git a/chapter01/docs/01-graph.gv b/chapter01/docs/01-graph.gv index 82d1b40a..df256f52 100644 --- a/chapter01/docs/01-graph.gv +++ b/chapter01/docs/01-graph.gv @@ -1,11 +1,17 @@ digraph chapter01 { +/* +return 1; +*/ rankdir=BT; + ordering="in"; + concentrate="true"; subgraph cluster_Nodes { Start1 [ shape=box style=filled fillcolor=yellow label="Start" ]; - Con12 [ label="Con1" ]; + Con_2 [ label="#1" ]; Return3 [ shape=box style=filled fillcolor=yellow label="Return" ]; } - Con12 -> Start1 [style=dotted]; - Return3 -> Start1 [color=red]; - Return3 -> Con12; + edge [ fontname=Helvetica, fontsize=8 ]; + Con_2 -> Start1[taillabel=0 style=dotted]; + Return3 -> Start1[taillabel=0 color=red]; + Return3 -> Con_2[taillabel=1]; } diff --git a/chapter01/docs/01-graph.svg b/chapter01/docs/01-graph.svg index 8e478f4d..7f511966 100644 --- a/chapter01/docs/01-graph.svg +++ b/chapter01/docs/01-graph.svg @@ -4,50 +4,53 @@ - + chapter01 - + cluster_Nodes - + Start1 - -Start + +Start - + -Con12 - -Con1 +Con_2 + +#1 - + -Con12->Start1 - - +Con_2->Start1 + + +0 Return3 - -Return + +Return Return3->Start1 - - + + +0 - + -Return3->Con12 - - +Return3->Con_2 + + +1 diff --git a/chapter01/src/main/java/com/seaofnodes/simple/Parser.java b/chapter01/src/main/java/com/seaofnodes/simple/Parser.java index a71def25..e2c1ca71 100644 --- a/chapter01/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter01/src/main/java/com/seaofnodes/simple/Parser.java @@ -1,20 +1,16 @@ package com.seaofnodes.simple; -import com.seaofnodes.simple.node.ConstantNode; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.node.ReturnNode; -import com.seaofnodes.simple.node.StartNode; +import com.seaofnodes.simple.node.*; /** - * The Parser converts a Simple source program to the Sea of Nodes intermediate representation - * directly in one pass. There is no intermediate Abstract Syntax Tree structure. - * + * The Parser converts a Simple source program to the Sea of Nodes intermediate + * representation directly in one pass. There is no intermediate Abstract + * Syntax Tree structure. + *

* This is a simple recursive descent parser. All lexical analysis is done here as well. */ public class Parser { - private final Lexer _lexer; - /** * A Global Static, unique to each compilation. This is a public, so we * can make constants everywhere without having to thread the StartNode @@ -24,6 +20,10 @@ public class Parser { */ public static StartNode START; + // The Lexer. Thin wrapper over a byte[] buffer with a cursor. + private final Lexer _lexer; + + public Parser(String source) { _lexer = new Lexer(source); Node.reset(); @@ -31,21 +31,21 @@ public class Parser { } public ReturnNode parse() { + require("return"); return parseReturn(); } /** - * Parses return statement. + * Parses a return statement; "return" already parsed. * *

-     *     return expr ;
+     *     'return' expr ;
      * 
+ * @return an expression {@link Node}, never {@code null} */ private ReturnNode parseReturn() { - require("return"); - var returnExpr = parseExpression(); - require(";"); - return new ReturnNode(START, returnExpr); + var expr = require(parseExpression(), ";"); + return new ReturnNode(START, expr); } /** @@ -54,6 +54,7 @@ public class Parser { *
      *     expr : primaryExpr
      * 
+ * @return an expression {@link Node}, never {@code null} */ private Node parseExpression() { return parsePrimary(); @@ -81,25 +82,28 @@ public class Parser { * */ private ConstantNode parseIntegerLiteral() { - return new ConstantNode(_lexer.parseNumber(), START); - } - - static RuntimeException error(String errorMessage) { - return new RuntimeException(errorMessage); + return new ConstantNode(_lexer.parseNumber()); } ////////////////////////////////// // Utilities for lexical analysis // Return true and skip if "syntax" is next in the stream. - private boolean match(String syntax) { - return _lexer.match(syntax); - } + private boolean match(String syntax) { return _lexer.match(syntax); } // Require an exact match - private void require(String syntax) { - if (match(syntax)) return; - throw error("Syntax error, expected " + syntax + ": " + _lexer.getAnyNextToken()); + private void require(String syntax) { require(null, syntax); } + private Node require(Node n, String syntax) { + if (match(syntax)) return n; + throw errorSyntax(syntax); + } + + RuntimeException errorSyntax(String syntax) { + return error("Syntax error, expected " + syntax + ": " + _lexer.getAnyNextToken()); + } + + static RuntimeException error(String errorMessage) { + return new RuntimeException(errorMessage); } //////////////////////////////////// @@ -152,8 +156,7 @@ public class Parser { * Return the next non-white-space character */ private void skipWhiteSpace() { - while (isWhiteSpace()) - _position++; + while (isWhiteSpace()) _position++; } @@ -163,8 +166,7 @@ public class Parser { boolean match(String syntax) { skipWhiteSpace(); int len = syntax.length(); - if (_position + len > _input.length) - return false; + if (_position + len > _input.length) return false; for (int i = 0; i < len; i++) if ((char) _input[_position + i] != syntax.charAt(i)) return false; @@ -175,20 +177,13 @@ public class Parser { // Used for errors String getAnyNextToken() { if (isEOF()) return ""; - if (isIdentifierStart(peek())) - return parseIdentifier(); - if (isPunctuation(peek())) - return parsePunctuation(); + if (isIdStart(peek())) return parseId(); + if (isPunctuation(peek())) return parsePunctuation(); return String.valueOf(peek()); } - boolean isNumber() { - return isNumber(peek()); - } - - boolean isNumber(char ch) { - return Character.isDigit(ch); - } + boolean isNumber() {return isNumber(peek());} + boolean isNumber(char ch) {return Character.isDigit(ch);} private long parseNumber() { int start = _position; @@ -200,18 +195,18 @@ public class Parser { } // First letter of an identifier - private boolean isIdentifierStart(char ch) { + private boolean isIdStart(char ch) { return Character.isAlphabetic(ch) || ch == '_'; } // All characters of an identifier, e.g. "_x123" - private boolean isIdentifierLetter(char ch) { + private boolean isIdLetter(char ch) { return Character.isLetterOrDigit(ch) || ch == '_'; } - private String parseIdentifier() { + private String parseId() { int start = _position; - while (isIdentifierLetter(nextChar())) ; + while (isIdLetter(nextChar())) ; return new String(_input, start, --_position - start); } @@ -222,8 +217,7 @@ public class Parser { private String parsePunctuation() { int start = _position; - if (isPunctuation(nextChar())) ; - return new String(_input, start, --_position - start); + return new String(_input, start, 1); } } diff --git a/chapter01/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter01/src/main/java/com/seaofnodes/simple/node/ConstantNode.java index 7644daf2..21f65084 100644 --- a/chapter01/src/main/java/com/seaofnodes/simple/node/ConstantNode.java +++ b/chapter01/src/main/java/com/seaofnodes/simple/node/ConstantNode.java @@ -1,5 +1,7 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.simple.Parser; + /** * A Constant node represents a constant value. At present, the only constants * that we allow are integer literals; therefore Constants contain an integer @@ -16,8 +18,8 @@ public class ConstantNode extends Node { public final long _value; - public ConstantNode(long value, StartNode startNode) { - super(startNode); + public ConstantNode(long value) { + super(Parser.START); _value = value; } } diff --git a/chapter01/src/main/java/com/seaofnodes/simple/node/Control.java b/chapter01/src/main/java/com/seaofnodes/simple/node/Control.java deleted file mode 100644 index 48cf2b7a..00000000 --- a/chapter01/src/main/java/com/seaofnodes/simple/node/Control.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.seaofnodes.simple.node; - - -/** - * Control interface is a marker interface to be implemented - * by Control nodes. - */ -public interface Control { -} diff --git a/chapter01/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter01/src/main/java/com/seaofnodes/simple/node/Node.java index b6ce3160..cf465e94 100644 --- a/chapter01/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter01/src/main/java/com/seaofnodes/simple/node/Node.java @@ -1,7 +1,6 @@ package com.seaofnodes.simple.node; -import java.util.ArrayList; -import java.util.Collections; +import java.util.*; /** * All Nodes in the Sea of Nodes IR inherit from the Node class. @@ -22,8 +21,7 @@ public abstract class Node { *

* Generally fixed length, ordered, nulls allowed, no unused trailing space. * Ordering is required because e.g. "a/b" is different from "b/a". - * The first input (offset 0) is often a Control node. - * @see Control + * The first input (offset 0) is often a {@link #isCFG} node. */ public final ArrayList _inputs; @@ -45,17 +43,16 @@ public abstract class Node { * */ private static int UNIQUE_ID = 1; - protected Node(Node ...inputs) { + protected Node(Node... inputs) { _nid = UNIQUE_ID++; // allocate unique dense ID _inputs = new ArrayList<>(); Collections.addAll(_inputs,inputs); _outputs = new ArrayList<>(); - for( Node n : _inputs ) { + for( Node n : _inputs ) if( n != null ) n._outputs.add( this ); - } } - + /** * Gets the ith input node * @param i Offset of the input node @@ -65,23 +62,16 @@ public abstract class Node { public int nIns() { return _inputs.size(); } - /** - * Gets the ith output node - * @param i Offset of the output node - * @return Output node (not null) - */ - public Node out(int i) { return _outputs.get(i); } - public int nOuts() { return _outputs.size(); } + public boolean isUnused() { return nOuts() == 0; } + + public boolean isCFG() { return false; } + + /** * Used to allow repeating tests in the same JVM. This just resets the * Node unique id generator, and is done as part of making a new Parser. */ - public static void reset() { - UNIQUE_ID = 1; - } - /* - * hashCode and equals implementation to be added in later chapter. - */ + public static void reset() { UNIQUE_ID = 1; } } diff --git a/chapter01/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter01/src/main/java/com/seaofnodes/simple/node/ReturnNode.java index a2321e2b..5e083cf2 100644 --- a/chapter01/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ b/chapter01/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -9,17 +9,15 @@ package com.seaofnodes.simple.node; *

* The Return's output is the value from the data node. */ -public class ReturnNode extends Node implements Control { +public class ReturnNode extends Node { public ReturnNode(Node ctrl, Node data) { super(ctrl, data); } - public Node ctrl() { - return in(0); - } + public Node ctrl() { return in(0); } + public Node expr() { return in(1); } - public Node expr() { - return in(1); - } + @Override public boolean isCFG() { return true; } + } diff --git a/chapter01/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter01/src/main/java/com/seaofnodes/simple/node/StartNode.java index c29ba53c..04bd6bb4 100644 --- a/chapter01/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ b/chapter01/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -5,9 +5,12 @@ package com.seaofnodes.simple.node; * yet accept parameters. When we add parameters the value of Start will be a tuple, and will require Projections to extract the values. * We discuss this in detail in Chapter 9: Functions and Calls. */ -public class StartNode extends Node implements Control { +public class StartNode extends Node { public StartNode(/*arguments go here*/) { super(); } + + @Override public boolean isCFG() { return true; } + } diff --git a/chapter01/src/test/java/com/seaofnodes/simple/Chapter01Test.java b/chapter01/src/test/java/com/seaofnodes/simple/Chapter01Test.java index ce8c2822..cb5a8a69 100644 --- a/chapter01/src/test/java/com/seaofnodes/simple/Chapter01Test.java +++ b/chapter01/src/test/java/com/seaofnodes/simple/Chapter01Test.java @@ -1,15 +1,11 @@ package com.seaofnodes.simple; -import com.seaofnodes.simple.node.ConstantNode; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.node.ReturnNode; -import com.seaofnodes.simple.node.StartNode; +import com.seaofnodes.simple.node.*; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; public class Chapter01Test { @@ -21,11 +17,11 @@ public class Chapter01Test { Parser parser = new Parser("return 1;"); ReturnNode ret = parser.parse(); StartNode start = Parser.START; - + assertEquals(start, ret.ctrl()); Node expr = ret.expr(); - if (expr instanceof ConstantNode con) { - assertEquals(start, con.in(0)); + if( expr instanceof ConstantNode con ) { + assertEquals(start,con.in(0)); assertEquals(1, con._value); } else { fail(); @@ -44,16 +40,22 @@ public class Chapter01Test { @Test public void testBad1() { - exceptionRule.expect(RuntimeException.class); - exceptionRule.expectMessage("Syntax error, expected return: ret"); - new Parser("ret").parse(); + try { + new Parser("ret").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Syntax error, expected return: ret",e.getMessage()); + } } @Test public void testBad2() { - exceptionRule.expect(RuntimeException.class); - exceptionRule.expectMessage("Syntax error: integer values cannot start with '0'"); - new Parser("return 0123;").parse(); + try { + new Parser("return 0123;").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Syntax error: integer values cannot start with '0'",e.getMessage()); + } } @Test @@ -65,9 +67,12 @@ public class Chapter01Test { @Test public void testBad4() { - exceptionRule.expect(RuntimeException.class); - exceptionRule.expectMessage("Syntax error, expected ;:"); - new Parser("return 100").parse(); + try { + new Parser("return 100").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Syntax error, expected ;: ",e.getMessage()); + } } // Negative numbers require unary operator support that is not in scope diff --git a/chapter02/README.md b/chapter02/README.md index b89a3059..e1ee19aa 100644 --- a/chapter02/README.md +++ b/chapter02/README.md @@ -117,9 +117,10 @@ The following visual shows how the graph looks like pre-peephole optimization: ![Example Visual](./docs/02-pre-peephole-ex1.svg) -* Control nodes appear as square boxes +* Control nodes appear as square boxes with yellow background * Control edges are in bold red -* The edges from Start to Constants are shown in dotted lines as these are not true def-use edges +* The edges from Constants to Start are shown in dotted lines as these are not true control edges +* We label each edge with its position in the node's list of inputs. ## Post-peephole diff --git a/chapter02/docs/02-post-peephole-ex1.gv b/chapter02/docs/02-post-peephole-ex1.gv index 290a1178..6c2b23ae 100644 --- a/chapter02/docs/02-post-peephole-ex1.gv +++ b/chapter02/docs/02-post-peephole-ex1.gv @@ -1,11 +1,17 @@ digraph chapter02 { +/* +return 1+2*3+-5; +*/ rankdir=BT; + ordering="in"; + concentrate="true"; subgraph cluster_Nodes { Start1 [ shape=box style=filled fillcolor=yellow label="Start" ]; - Con213 [ label="Con2" ]; + Con_13 [ label="#2" ]; Return14 [ shape=box style=filled fillcolor=yellow label="Return" ]; } - Con213 -> Start1 [style=dotted]; - Return14 -> Start1 [color=red]; - Return14 -> Con213; -} \ No newline at end of file + edge [ fontname=Helvetica, fontsize=8 ]; + Con_13 -> Start1[taillabel=0 style=dotted]; + Return14 -> Start1[taillabel=0 color=red]; + Return14 -> Con_13[taillabel=1]; +} diff --git a/chapter02/docs/02-post-peephole-ex1.svg b/chapter02/docs/02-post-peephole-ex1.svg index b3c42c7d..db27e30d 100644 --- a/chapter02/docs/02-post-peephole-ex1.svg +++ b/chapter02/docs/02-post-peephole-ex1.svg @@ -4,50 +4,53 @@ - + chapter02 - + cluster_Nodes - + Start1 - -Start + +Start - + -Con213 - -Con2 +Con_13 + +#2 - + -Con213->Start1 - - +Con_13->Start1 + + +0 Return14 - -Return + +Return Return14->Start1 - - + + +0 - + -Return14->Con213 - - +Return14->Con_13 + + +1 diff --git a/chapter02/docs/02-pre-peephole-ex1.gv b/chapter02/docs/02-pre-peephole-ex1.gv index c2eab457..411de5ee 100644 --- a/chapter02/docs/02-pre-peephole-ex1.gv +++ b/chapter02/docs/02-pre-peephole-ex1.gv @@ -1,28 +1,34 @@ digraph chapter02 { +/* +return 1+2*3+-5; +*/ rankdir=BT; + ordering="in"; + concentrate="true"; subgraph cluster_Nodes { Start1 [ shape=box style=filled fillcolor=yellow label="Start" ]; - Con12 [ label="Con1" ]; - Con23 [ label="Con2" ]; - Con34 [ label="Con3" ]; - Mul5 [ label="Mul" ]; - Con56 [ label="Con5" ]; - Minus7 [ label="Minus" ]; - Add8 [ label="Add" ]; - Add9 [ label="Add" ]; + Con_2 [ label="#1" ]; + Con_3 [ label="#2" ]; + Con_4 [ label="#3" ]; + Mul5 [ label="*" ]; + Con_6 [ label="#5" ]; + Minus7 [ label="-" ]; + Add8 [ label="+" ]; + Add9 [ label="+" ]; Return10 [ shape=box style=filled fillcolor=yellow label="Return" ]; } - Con12 -> Start1 [style=dotted]; - Con23 -> Start1 [style=dotted]; - Con34 -> Start1 [style=dotted]; - Mul5 -> Con23; - Mul5 -> Con34; - Con56 -> Start1 [style=dotted]; - Minus7 -> Con56; - Add8 -> Mul5; - Add8 -> Minus7; - Add9 -> Con12; - Add9 -> Add8; - Return10 -> Start1 [color=red]; - Return10 -> Add9; -} \ No newline at end of file + edge [ fontname=Helvetica, fontsize=8 ]; + Con_2 -> Start1[taillabel=0 style=dotted]; + Con_3 -> Start1[taillabel=0 style=dotted]; + Con_4 -> Start1[taillabel=0 style=dotted]; + Mul5 -> Con_3[taillabel=1]; + Mul5 -> Con_4[taillabel=2]; + Con_6 -> Start1[taillabel=0 style=dotted]; + Minus7 -> Con_6[taillabel=1]; + Add8 -> Mul5[taillabel=1]; + Add8 -> Minus7[taillabel=2]; + Add9 -> Con_2[taillabel=1]; + Add9 -> Add8[taillabel=2]; + Return10 -> Start1[taillabel=0 color=red]; + Return10 -> Add9[taillabel=1]; +} diff --git a/chapter02/docs/02-pre-peephole-ex1.svg b/chapter02/docs/02-pre-peephole-ex1.svg index 8a1ef457..29c76bd8 100644 --- a/chapter02/docs/02-pre-peephole-ex1.svg +++ b/chapter02/docs/02-pre-peephole-ex1.svg @@ -4,152 +4,165 @@ - + chapter02 - + cluster_Nodes - + Start1 - -Start + +Start - + -Con12 - -Con1 +Con_2 + +#1 - + -Con12->Start1 - - +Con_2->Start1 + + +0 - + -Con23 - -Con2 +Con_3 + +#2 - + -Con23->Start1 - - +Con_3->Start1 + + +0 - + -Con34 - -Con3 +Con_4 + +#3 - + -Con34->Start1 - - +Con_4->Start1 + + +0 Mul5 - -Mul + +* - + -Mul5->Con23 - - +Mul5->Con_3 + + +1 - + -Mul5->Con34 - - +Mul5->Con_4 + + +2 - + -Con56 - -Con5 +Con_6 + +#5 - + -Con56->Start1 - - +Con_6->Start1 + + +0 Minus7 - -Minus + +- - + -Minus7->Con56 - - +Minus7->Con_6 + + +1 Add8 - -Add + ++ Add8->Mul5 - - + + +1 Add8->Minus7 - - + + +2 Add9 - -Add + ++ - + -Add9->Con12 - - +Add9->Con_2 + + +1 Add9->Add8 - - + + +2 Return10 - -Return + +Return Return10->Start1 - - + + +0 Return10->Add9 - - + + +1 diff --git a/chapter02/src/main/java/com/seaofnodes/simple/GraphVisualizer.java b/chapter02/src/main/java/com/seaofnodes/simple/GraphVisualizer.java index 1af19d80..df128674 100644 --- a/chapter02/src/main/java/com/seaofnodes/simple/GraphVisualizer.java +++ b/chapter02/src/main/java/com/seaofnodes/simple/GraphVisualizer.java @@ -1,9 +1,6 @@ package com.seaofnodes.simple; -import com.seaofnodes.simple.node.ConstantNode; -import com.seaofnodes.simple.node.Control; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.node.StartNode; +import com.seaofnodes.simple.node.*; import java.util.*; @@ -21,7 +18,10 @@ public class GraphVisualizer { Collection all = findAll(parser); StringBuilder sb = new StringBuilder(); sb.append("digraph chapter02 {\n"); - + sb.append("/*\n"); + sb.append(parser.src()); + sb.append("\n*/\n"); + // 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 @@ -29,12 +29,19 @@ public class GraphVisualizer { // still making the subgraphs DOT gets confused. sb.append("\trankdir=BT;\n"); // Force Nodes before Scopes + // Preserve node input order + sb.append("\tordering=\"in\";\n"); + + // Merge multiple edges hitting the same node. Makes common shared + // nodes much prettier to look at. + sb.append("\tconcentrate=\"true\";\n"); + // Just the Nodes first, in a cluster no edges nodes(sb, all); // Walk the Node edges nodeEdges(sb, all); - + sb.append("}\n"); return sb.toString(); } @@ -43,13 +50,14 @@ public class GraphVisualizer { private void nodes(StringBuilder sb, Collection all) { // Just the Nodes first, in a cluster no edges sb.append("\tsubgraph cluster_Nodes {\n"); // Magic "cluster_" in the subgraph name - for (Node n : all) { + for( Node n : all ) { sb.append("\t\t").append(n.uniqueName()).append(" [ "); + String lab = n.glabel(); // control nodes have box shape // other nodes are ellipses, i.e. default shape - if (n instanceof Control) + if( n.isCFG() ) sb.append("shape=box style=filled fillcolor=yellow "); - sb.append("label=\"").append(n.label()).append("\" "); + sb.append("label=\"").append(lab).append("\" "); sb.append("];\n"); } sb.append("\t}\n"); // End Node cluster @@ -58,25 +66,32 @@ public class GraphVisualizer { // Walk the node edges private void nodeEdges(StringBuilder sb, Collection all) { - for( Node n : all ) - for( Node out : n._inputs ) - if( out != null ) { - sb.append('\t').append(n.uniqueName()).append(" -> ").append(out.uniqueName()); - // Control edges are colored red - if( n instanceof ConstantNode && out instanceof StartNode ) - sb.append(" [style=dotted]"); - else if( n instanceof Control && out instanceof Control ) - sb.append(" [color=red]"); - sb.append(";\n"); + // All them edge labels + sb.append("\tedge [ fontname=Helvetica, fontsize=8 ];\n"); + for( Node n : all ) { + // In this chapter we do display the Constant->Start edge; + int i=0; + for( Node def : n._inputs ) { + if( def != null ) { + // Most edges land here use->def + sb.append('\t').append(n.uniqueName()).append(" -> "); + sb.append(def.uniqueName()); + // Number edges, so we can see how they track + sb.append("[taillabel=").append(i); + if( n instanceof ConstantNode && def instanceof StartNode ) + sb.append(" style=dotted"); + // control edges are colored red + else if( def.isCFG() ) + sb.append(" color=red"); + sb.append("];\n"); + } + i++; + } } } - + /** - * Walks the whole graph, starting from Start. - * Since Start is the input to all constants - we look at the outputs for - * Start, but for then subsequently we look at the inputs of each node. - * During graph construction not all nodes are reachable this way, so we - * also scan the symbol tables. + * Finds all nodes in the graph. */ private Collection findAll(Parser parser) { final StartNode start = Parser.START; @@ -91,9 +106,12 @@ public class GraphVisualizer { */ private void walk(HashMap all, Node n) { if (all.get(n._nid) != null) return; // Been there, done that - all.put(n._nid, n); - for (Node c : n._inputs) - if (c != null) - walk(all, c); + all.put(n._nid, n); + for (Node c : n._inputs) + if (c != null) + walk(all, c); + for (Node c : n._outputs) + if (c != null) + walk(all, c); } } diff --git a/chapter02/src/main/java/com/seaofnodes/simple/Parser.java b/chapter02/src/main/java/com/seaofnodes/simple/Parser.java index 3ca13c3f..5422ea66 100644 --- a/chapter02/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter02/src/main/java/com/seaofnodes/simple/Parser.java @@ -21,29 +21,34 @@ public class Parser { */ public static StartNode START; + // The Lexer. Thin wrapper over a byte[] buffer with a cursor. private final Lexer _lexer; - public Parser( String source ) { + + public Parser(String source) { _lexer = new Lexer(source); Node.reset(); START = new StartNode(); } + String src() { return new String( _lexer._input ); } + public ReturnNode parse() { require("return"); - return parseReturn(); + return (ReturnNode) parseReturn(); } /** - * Parses returnStatement; "return" already parsed + * Parses a return statement; "return" already parsed. * *

      *     'return' expr ;
      * 
+ * @return an expression {@link Node}, never {@code null} */ - private ReturnNode parseReturn() { - var expr = require(parseExpression(),";"); - return new ReturnNode(START, expr); + private Node parseReturn() { + var expr = require(parseExpression(), ";"); + return new ReturnNode(START, expr).peephole(); } /** @@ -52,6 +57,7 @@ public class Parser { *
      *     expr : additiveExpr
      * 
+ * @return an expression {@link Node}, never {@code null} */ private Node parseExpression() { return parseAddition(); } @@ -61,11 +67,12 @@ public class Parser { *
      *     additiveExpr : multiplicativeExpr (('+' | '-') multiplicativeExpr)*
      * 
+ * @return an add expression {@link Node}, never {@code null} */ private Node parseAddition() { var lhs = parseMultiplication(); - if( match("+") ) return new AddNode(lhs, parseAddition()).peephole(); - if( match("-") ) return new SubNode(lhs, parseAddition()).peephole(); + if (match("+")) return new AddNode(lhs, parseAddition()).peephole(); + if (match("-")) return new SubNode(lhs, parseAddition()).peephole(); return lhs; } @@ -75,11 +82,12 @@ public class Parser { *
      *     multiplicativeExpr : unaryExpr (('*' | '/') unaryExpr)*
      * 
+ * @return a multiply expression {@link Node}, never {@code null} */ private Node parseMultiplication() { var lhs = parseUnary(); - if( match("*") ) return new MulNode(lhs, parseMultiplication()).peephole(); - if( match("/") ) return new DivNode(lhs, parseMultiplication()).peephole(); + if (match("*")) return new MulNode(lhs, parseMultiplication()).peephole(); + if (match("/")) return new DivNode(lhs, parseMultiplication()).peephole(); return lhs; } @@ -89,9 +97,10 @@ public class Parser { *
      *     unaryExpr : ('-') unaryExpr | primaryExpr
      * 
+ * @return a unary expression {@link Node}, never {@code null} */ private Node parseUnary() { - if( match("-") ) return new MinusNode(parseUnary()).peephole(); + if (match("-")) return new MinusNode(parseUnary()).peephole(); return parsePrimary(); } @@ -99,12 +108,13 @@ public class Parser { * Parse a primary expression: * *
-     *     primaryExpr : integerLiteral
+     *     primaryExpr : integerLiteral | Identifier | '(' expression ')'
      * 
+ * @return a primary {@link Node}, never {@code null} */ private Node parsePrimary() { if( _lexer.isNumber() ) return parseIntegerLiteral(); - if( match("(") ) return require(parseExpression(),")"); + if( match("(") ) return require(parseExpression(), ")"); throw errorSyntax("integer literal"); } @@ -116,7 +126,7 @@ public class Parser { * */ private ConstantNode parseIntegerLiteral() { - return (ConstantNode)new ConstantNode(_lexer.parseNumber()).peephole(); + return (ConstantNode) new ConstantNode(_lexer.parseNumber()).peephole(); } ////////////////////////////////// @@ -126,7 +136,7 @@ public class Parser { private boolean match(String syntax) { return _lexer.match(syntax); } // Require an exact match - private void require(String syntax) { require(null,syntax); } + private void require(String syntax) { require(null, syntax); } private Node require(Node n, String syntax) { if (match(syntax)) return n; throw errorSyntax(syntax); @@ -135,7 +145,7 @@ public class Parser { RuntimeException errorSyntax(String syntax) { return error("Syntax error, expected " + syntax + ": " + _lexer.getAnyNextToken()); } - + static RuntimeException error(String errorMessage) { return new RuntimeException(errorMessage); } @@ -167,9 +177,9 @@ public class Parser { // Very handy in the debugger, shows the unparsed program @Override public String toString() { - return new String(_input,_position,_input.length-_position); + return new String(_input, _position, _input.length - _position); } - + // True if at EOF private boolean isEOF() { return _position >= _input.length; @@ -203,12 +213,12 @@ public class Parser { // Return true, if we find "syntax" after skipping white space; also // then advance the cursor past syntax. // Return false otherwise, and do not advance the cursor. - boolean match( String syntax ) { + boolean match(String syntax) { skipWhiteSpace(); int len = syntax.length(); - if( _position + len > _input.length ) return false; - for( int i = 0; i < len; i++ ) - if( (char)_input[_position + i] != syntax.charAt(i) ) + if (_position + len > _input.length) return false; + for (int i = 0; i < len; i++) + if ((char) _input[_position + i] != syntax.charAt(i)) return false; _position += len; return true; @@ -216,14 +226,14 @@ public class Parser { // Used for errors String getAnyNextToken() { - if( isEOF() ) return ""; - if( isIdStart(peek()) ) return parseId(); - if( isPunctuation(peek()) ) return parsePunctuation(); + if (isEOF()) return ""; + if (isIdStart(peek())) return parseId(); + if (isPunctuation(peek())) return parsePunctuation(); return String.valueOf(peek()); } - boolean isNumber() { return isNumber(peek()); } - boolean isNumber(char ch) { return Character.isDigit(ch); } + boolean isNumber() {return isNumber(peek());} + boolean isNumber(char ch) {return Character.isDigit(ch);} private Type parseNumber() { int start = _position; @@ -231,7 +241,7 @@ public class Parser { String snum = new String(_input, start, --_position - start); if (snum.length() > 1 && snum.charAt(0) == '0') throw error("Syntax error: integer values cannot start with '0'"); - return new TypeInteger(Long.parseLong(snum)); + return TypeInteger.constant(Long.parseLong(snum)); } // First letter of an identifier @@ -257,8 +267,7 @@ public class Parser { private String parsePunctuation() { int start = _position; - if (isPunctuation(nextChar())) ; - return new String(_input, start, --_position - start); + return new String(_input, start, 1); } } diff --git a/chapter02/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter02/src/main/java/com/seaofnodes/simple/node/AddNode.java index 015779dc..6784ad60 100644 --- a/chapter02/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/chapter02/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -3,17 +3,16 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.type.*; public class AddNode extends Node { - public AddNode(Node lhs, Node rhs) { - super(null, lhs, rhs); - } + public AddNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - @Override - public String label() { return "Add"; } + @Override public String label() { return "Add"; } + + @Override public String glabel() { return "+"; } @Override - StringBuilder _print(StringBuilder sb) { - in(1)._print(sb.append("(")); - in(2)._print(sb.append("+")); + StringBuilder _print1(StringBuilder sb) { + in(1)._print0(sb.append("(")); + in(2)._print0(sb.append("+")); return sb.append(")"); } @@ -21,8 +20,9 @@ public class AddNode extends Node { @Override public Type compute() { if( in(1)._type instanceof TypeInteger i0 && - in(2)._type instanceof TypeInteger i1 ) - return new TypeInteger(i0._con + i1._con); + in(2)._type instanceof TypeInteger i1 ) { + return TypeInteger.constant(i0.value()+i1.value()); + } return TypeBot.BOTTOM; } diff --git a/chapter02/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter02/src/main/java/com/seaofnodes/simple/node/ConstantNode.java index 99ccf4b5..ec85bf1f 100644 --- a/chapter02/src/main/java/com/seaofnodes/simple/node/ConstantNode.java +++ b/chapter02/src/main/java/com/seaofnodes/simple/node/ConstantNode.java @@ -16,22 +16,25 @@ import com.seaofnodes.simple.type.Type; * The Constant's value is the value stored in it. */ public class ConstantNode extends Node { - + Type _con; public ConstantNode( Type type ) { super(Parser.START); - _type = type; + _con = type; } @Override - public String label() { return "Con" + _type; } + public String label() { return "#"+_con; } + + @Override + public String uniqueName() { return "Con_" + _nid; } @Override - StringBuilder _print(StringBuilder sb) { - return _type._print(sb); + StringBuilder _print1(StringBuilder sb) { + return _con._print(sb); } @Override - public Type compute() { return _type; } + public Type compute() { return _con; } @Override public Node idealize() { return null; } diff --git a/chapter02/src/main/java/com/seaofnodes/simple/node/Control.java b/chapter02/src/main/java/com/seaofnodes/simple/node/Control.java deleted file mode 100644 index 48cf2b7a..00000000 --- a/chapter02/src/main/java/com/seaofnodes/simple/node/Control.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.seaofnodes.simple.node; - - -/** - * Control interface is a marker interface to be implemented - * by Control nodes. - */ -public interface Control { -} diff --git a/chapter02/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter02/src/main/java/com/seaofnodes/simple/node/DivNode.java index 0056c42d..ca7df79c 100644 --- a/chapter02/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/chapter02/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -5,17 +5,16 @@ import com.seaofnodes.simple.type.TypeBot; import com.seaofnodes.simple.type.TypeInteger; public class DivNode extends Node { - public DivNode(Node lhs, Node rhs) { - super(null, lhs, rhs); - } + public DivNode(Node lhs, Node rhs) { super(null, lhs, rhs); } + + @Override public String label() { return "Div"; } + + @Override public String glabel() { return "//"; } @Override - public String label() { return "Div"; } - - @Override - StringBuilder _print(StringBuilder sb) { - in(1)._print(sb.append("(")); - in(2)._print(sb.append("/")); + StringBuilder _print1(StringBuilder sb) { + in(1)._print0(sb.append("(")); + in(2)._print0(sb.append("/")); return sb.append(")"); } @@ -23,11 +22,9 @@ public class DivNode extends Node { public Type compute() { if (in(1)._type instanceof TypeInteger i0 && in(2)._type instanceof TypeInteger i1) { - if (i0.isConstant() && i1.isConstant()) - return i1._con == 0 - ? TypeInteger.ZERO - : new TypeInteger(i0._con/i1._con); - return new TypeInteger(i0._con / i1._con); + return i1.value() == 0 + ? TypeInteger.ZERO + : TypeInteger.constant(i0.value()/i1.value()); } return TypeBot.BOTTOM; } diff --git a/chapter02/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter02/src/main/java/com/seaofnodes/simple/node/MinusNode.java index 7fab83b0..834f4c11 100644 --- a/chapter02/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter02/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -5,23 +5,22 @@ import com.seaofnodes.simple.type.TypeBot; import com.seaofnodes.simple.type.TypeInteger; public class MinusNode extends Node { - public MinusNode(Node in) { - super(null, in); - } + public MinusNode(Node in) { super(null, in); } - @Override - public String label() { return "Minus"; } + @Override public String label() { return "Minus"; } + + @Override public String glabel() { return "-"; } @Override - StringBuilder _print(StringBuilder sb) { - in(1)._print(sb.append("(-")); + StringBuilder _print1(StringBuilder sb) { + in(1)._print0(sb.append("(-")); return sb.append(")"); } @Override public Type compute() { if (in(1)._type instanceof TypeInteger i0) - return new TypeInteger(-i0._con); + return TypeInteger.constant(-i0.value()); return TypeBot.BOTTOM; } diff --git a/chapter02/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter02/src/main/java/com/seaofnodes/simple/node/MulNode.java index e1a26858..0fa54966 100644 --- a/chapter02/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/chapter02/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -5,25 +5,25 @@ import com.seaofnodes.simple.type.TypeBot; import com.seaofnodes.simple.type.TypeInteger; public class MulNode extends Node { - public MulNode(Node lhs, Node rhs) { - super(null, lhs, rhs); - } - - @Override - public String label() { return "Mul"; } + public MulNode(Node lhs, Node rhs) { super(null, lhs, rhs); } + + @Override public String label() { return "Mul"; } + @Override public String glabel() { return "*"; } + @Override - StringBuilder _print(StringBuilder sb) { - in(1)._print(sb.append("(")); - in(2)._print(sb.append("*")); + StringBuilder _print1(StringBuilder sb) { + in(1)._print0(sb.append("(")); + in(2)._print0(sb.append("*")); return sb.append(")"); } - + @Override public Type compute() { if (in(1)._type instanceof TypeInteger i0 && - in(2)._type instanceof TypeInteger i1) - return new TypeInteger(i0._con * i1._con); + in(2)._type instanceof TypeInteger i1) { + return TypeInteger.constant(i0.value()*i1.value()); + } return TypeBot.BOTTOM; } diff --git a/chapter02/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter02/src/main/java/com/seaofnodes/simple/node/Node.java index 7c39083e..dbef984e 100644 --- a/chapter02/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter02/src/main/java/com/seaofnodes/simple/node/Node.java @@ -2,8 +2,7 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.type.Type; -import java.util.ArrayList; -import java.util.Collections; +import java.util.*; /** * All Nodes in the Sea of Nodes IR inherit from the Node class. @@ -24,8 +23,7 @@ public abstract class Node { *

* Generally fixed length, ordered, nulls allowed, no unused trailing space. * Ordering is required because e.g. "a/b" is different from "b/a". - * The first input (offset 0) is often a Control node. - * @see Control + * The first input (offset 0) is often a {@link #isCFG} node. */ public final ArrayList _inputs; @@ -40,6 +38,13 @@ public abstract class Node { */ public final ArrayList _outputs; + + /** + * Current computed type for this Node. This value changes as the graph + * changes and more knowledge is gained about the program. + */ + public Type _type; + /** * A private Global Static mutable counter, for unique node id generation. * To make the compiler multi-threaded, this field will have to move into a TLS. @@ -47,7 +52,7 @@ public abstract class Node { * */ private static int UNIQUE_ID = 1; - protected Node(Node ...inputs) { + protected Node(Node... inputs) { _nid = UNIQUE_ID++; // allocate unique dense ID _inputs = new ArrayList<>(); Collections.addAll(_inputs,inputs); @@ -55,26 +60,39 @@ public abstract class Node { for( Node n : _inputs ) if( n != null ) n._outputs.add( this ); - // Do an initial type computation - _type = compute(); } + // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ" public abstract String label(); + // Unique label for graph visualization, e.g. "Add12" or "Region30" or "EQ99" public String uniqueName() { return label() + _nid; } + // Graphical label, e.g. "+" or "Region" or "==" + public String glabel() { return label(); } + @Override public final String toString() { // TODO: This needs a lot of work - return uniqueName(); + return print(); } // This is a *deep* print. This version will fail on cycles, which we will - // correct later when we can parse programs with loops. + // correct later when we can parse programs with loops. We print with a + // tik-tok style; the common _print0 calls the per-Node _print1, which + // calls back to _print0; public final String print() { - return _print(new StringBuilder()).toString(); + return _print0(new StringBuilder()).toString(); + } + // This is the common print: check for DEAD and print "DEAD" else call the + // per-Node print1. + final StringBuilder _print0(StringBuilder sb) { + return isDead() + ? sb.append(uniqueName()).append(":DEAD") + : _print1(sb); } - abstract StringBuilder _print(StringBuilder sb); + // Every Node implements this. + abstract StringBuilder _print1(StringBuilder sb); /** @@ -86,25 +104,21 @@ public abstract class Node { public int nIns() { return _inputs.size(); } - /** - * Gets the ith output node - * @param i Offset of the output node - * @return Output node (not null) - */ - public Node out(int i) { return _outputs.get(i); } - public int nOuts() { return _outputs.size(); } + public boolean isUnused() { return nOuts() == 0; } + + public boolean isCFG() { return false; } + /** * We allow disabling peephole opt so that we can observe the * full graph, vs the optimized graph. */ public static boolean _disablePeephole = false; - /** * Try to peephole at this node and return a better replacement Node if - * possible. We check and replace: + * possible. We compute a {@link Type} and then check and replace: *

*/ public final Node peephole( ) { + // Compute initial or improved Type + Type type = _type = compute(); + if (_disablePeephole) return this; // Peephole optimizations turned off // Replace constant computations from non-constants with a constant node - Type type = compute(); if (!(this instanceof ConstantNode) && type.isConstant()) { kill(); // Kill `this` because replacing with a Constant - return new ConstantNode(type); + return new ConstantNode(type).peephole(); } // Future chapter: Global Value Numbering goes here - // Ask each node for a replacement + // Ask each node for a better replacement Node n = idealize(); if( n != null ) return n; return this; // No progress } - /** - * Kill a Node with no uses, by setting all of its defs - * to null. This may recursively kill more Nodes and is basically dead - * code elimination. This function is co-recursive with {@link #set_def}. - */ - void kill( ) { - assert nOuts()==0; // Has no uses, so it is dead - for( int i=0; idef into a Node. Keeps the edges correct, by removing * the corresponding use->def edge. This may make the original @@ -151,10 +156,11 @@ public abstract class Node { * * @param idx which def to set * @param new_def the new definition + * @return this for flow coding */ - void set_def(int idx, Node new_def ) { + Node set_def(int idx, Node new_def ) { Node old_def = in(idx); - if( old_def == new_def ) return; // No change + if( old_def == new_def ) return this; // No change // If new def is not null, add the corresponding def->use edge // This needs to happen before removing the old node's def->use edge as // the new_def might get killed if the old node kills it recursively. @@ -174,15 +180,26 @@ public abstract class Node { } // Set the new_def over the old (killed) edge _inputs.set(idx,new_def); + // Return self for easy flow-coding + return this; } - - /** - * Current computed type for this Node. This value changes as the graph - * changes and more knowledge is gained about the program. + * Kill a Node with no uses, by setting all of its defs + * to null. This may recursively kill more Nodes and is basically dead + * code elimination. This function is co-recursive with {@link #set_def}. */ - public Type _type; - + public void kill( ) { + assert isUnused(); // Has no uses, so it is dead + for( int i=0; iMonotonic @@ -208,9 +225,4 @@ public abstract class Node { * Node unique id generator, and is done as part of making a new Parser. */ public static void reset() { UNIQUE_ID = 1; } - - /* - * hashCode and equals implementation to be added in later chapter. - */ - } diff --git a/chapter02/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter02/src/main/java/com/seaofnodes/simple/node/ReturnNode.java index c188ca79..18f5eed9 100644 --- a/chapter02/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ b/chapter02/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -1,6 +1,6 @@ package com.seaofnodes.simple.node; -import com.seaofnodes.simple.type.TypeBot; +import com.seaofnodes.simple.type.*; /** * The Return node has two inputs. The first input is a control node and the @@ -11,7 +11,7 @@ import com.seaofnodes.simple.type.TypeBot; *

* The Return's output is the value from the data node. */ -public class ReturnNode extends Node implements Control { +public class ReturnNode extends Node { public ReturnNode(Node ctrl, Node data) { super(ctrl, data); @@ -24,10 +24,11 @@ public class ReturnNode extends Node implements Control { public String label() { return "Return"; } @Override - StringBuilder _print(StringBuilder sb) { - return expr()._print(sb.append("return ")).append(";"); + StringBuilder _print1(StringBuilder sb) { + return expr()._print0(sb.append("return ")).append(";"); } + @Override public boolean isCFG() { return true; } @Override public TypeBot compute() { diff --git a/chapter02/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter02/src/main/java/com/seaofnodes/simple/node/StartNode.java index 8d8031bb..08b8333a 100644 --- a/chapter02/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ b/chapter02/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -3,11 +3,13 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.type.TypeBot; /** - * The Start node represents the start of the function. For now, we do not have any inputs to Start because our function does not - * yet accept parameters. When we add parameters the value of Start will be a tuple, and will require Projections to extract the values. - * We discuss this in detail in Chapter 9: Functions and Calls. + * The Start node represents the start of the function. For now, we do not + * have any inputs to Start because our function does not yet accept + * parameters. When we add parameters the value of Start will be a tuple, and + * will require Projections to extract the values. We discuss this in detail + * in Chapter 9: Functions and Calls. */ -public class StartNode extends Node implements Control { +public class StartNode extends Node { public StartNode(/*arguments go here*/) { super(); @@ -17,10 +19,12 @@ public class StartNode extends Node implements Control { public String label() { return "Start"; } @Override - StringBuilder _print(StringBuilder sb) { + StringBuilder _print1(StringBuilder sb) { return sb.append(label()); } - + + @Override public boolean isCFG() { return true; } + @Override public TypeBot compute() { return TypeBot.BOTTOM; diff --git a/chapter02/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter02/src/main/java/com/seaofnodes/simple/node/SubNode.java index 2d7db8d3..71d70fa6 100644 --- a/chapter02/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/chapter02/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -5,25 +5,25 @@ import com.seaofnodes.simple.type.TypeBot; import com.seaofnodes.simple.type.TypeInteger; public class SubNode extends Node { - public SubNode(Node lhs, Node rhs) { - super(null, lhs, rhs); - } + public SubNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - @Override - public String label() { return "Sub"; } + @Override public String label() { return "Sub"; } + + @Override public String glabel() { return "-"; } @Override - StringBuilder _print(StringBuilder sb) { - in(1)._print(sb.append("(")); - in(2)._print(sb.append("-")); + StringBuilder _print1(StringBuilder sb) { + in(1)._print0(sb.append("(")); + in(2)._print0(sb.append("-")); return sb.append(")"); } @Override public Type compute() { if (in(1)._type instanceof TypeInteger i0 && - in(2)._type instanceof TypeInteger i1) - return new TypeInteger(i0._con - i1._con); + in(2)._type instanceof TypeInteger i1) { + return TypeInteger.constant(i0.value()-i1.value()); + } return TypeBot.BOTTOM; } diff --git a/chapter02/src/main/java/com/seaofnodes/simple/type/TypeBot.java b/chapter02/src/main/java/com/seaofnodes/simple/type/TypeBot.java index 5284884a..bc191241 100644 --- a/chapter02/src/main/java/com/seaofnodes/simple/type/TypeBot.java +++ b/chapter02/src/main/java/com/seaofnodes/simple/type/TypeBot.java @@ -8,7 +8,7 @@ public class TypeBot extends Type { @Override public String toString() { return "Bottom"; } - - @Override + + @Override public StringBuilder _print(StringBuilder sb) { return sb.append(toString()); } } diff --git a/chapter02/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/chapter02/src/main/java/com/seaofnodes/simple/type/TypeInteger.java index 06cac15e..ff6e865b 100644 --- a/chapter02/src/main/java/com/seaofnodes/simple/type/TypeInteger.java +++ b/chapter02/src/main/java/com/seaofnodes/simple/type/TypeInteger.java @@ -14,6 +14,8 @@ public class TypeInteger extends Type { public TypeInteger(long con) { _con = con; } + public static TypeInteger constant(long con) { return new TypeInteger(con); } + @Override public String toString() { return _print(new StringBuilder()).toString(); @@ -25,7 +27,7 @@ public class TypeInteger extends Type { @Override public boolean isConstant() { return true; } - public long getConstant() { return _con; } + public long value() { return _con; } @Override public boolean equals( Object o ) { diff --git a/chapter02/src/test/java/com/seaofnodes/simple/Chapter02Test.java b/chapter02/src/test/java/com/seaofnodes/simple/Chapter02Test.java index 83cd7a0f..971b60ba 100644 --- a/chapter02/src/test/java/com/seaofnodes/simple/Chapter02Test.java +++ b/chapter02/src/test/java/com/seaofnodes/simple/Chapter02Test.java @@ -1,16 +1,12 @@ package com.seaofnodes.simple; -import com.seaofnodes.simple.node.ConstantNode; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.node.ReturnNode; -import com.seaofnodes.simple.node.StartNode; +import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; public class Chapter02Test { @@ -76,15 +72,13 @@ public class Chapter02Test { public void testSimpleProgram() { Parser parser = new Parser("return 1;"); ReturnNode ret = parser.parse(); - GraphVisualizer gv = new GraphVisualizer(); - System.out.println(gv.generateDotOutput(parser)); StartNode start = Parser.START; assertEquals(start, ret.ctrl()); Node expr = ret.expr(); if( expr instanceof ConstantNode con ) { assertEquals(start,con.in(0)); - assertEquals(new TypeInteger(1), con._type); + assertEquals(TypeInteger.constant(1), con._type); } else { fail(); } @@ -97,21 +91,27 @@ public class Chapter02Test { StartNode start = Parser.START; for( Node use : start._outputs ) if( use instanceof ConstantNode con ) - assertEquals(new TypeInteger(0),con._type); + assertEquals(TypeInteger.constant(0),con._type); } @Test public void testBad1() { - exceptionRule.expect(RuntimeException.class); - exceptionRule.expectMessage("Syntax error, expected return: ret"); - new Parser("ret").parse(); + try { + new Parser("ret").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Syntax error, expected return: ret",e.getMessage()); + } } @Test public void testBad2() { - exceptionRule.expect(RuntimeException.class); - exceptionRule.expectMessage("Syntax error: integer values cannot start with '0'"); - new Parser("return 0123;").parse(); + try { + new Parser("return 0123;").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Syntax error: integer values cannot start with '0'",e.getMessage()); + } } @Test @@ -122,9 +122,12 @@ public class Chapter02Test { @Test public void testBad4() { - exceptionRule.expect(RuntimeException.class); - exceptionRule.expectMessage("Syntax error, expected ;:"); - new Parser("return 100").parse(); + try { + new Parser("return 100").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Syntax error, expected ;: ",e.getMessage()); + } } @Test diff --git a/chapter03/docs/03-graph1.gv b/chapter03/docs/03-graph1.gv index 5983f622..2bdca351 100644 --- a/chapter03/docs/03-graph1.gv +++ b/chapter03/docs/03-graph1.gv @@ -1,33 +1,39 @@ digraph chapter03 { +/* +int a=1; int b=2; int c=0; { int b=3; c=a+b; #showGraph; } return c; #showGraph; +*/ rankdir=BT; + ordering="in"; + concentrate="true"; subgraph cluster_Nodes { - Start1 [ shape=box style=filled fillcolor=yellow label="Start" ]; - Con_2 [ label="#1" ]; - Con_3 [ label="#2" ]; - Con_5 [ label="#3" ]; - Add6 [ label="Add" ]; + Start2 [ shape=box style=filled fillcolor=yellow label="Start" ]; + Con_3 [ label="#1" ]; + Con_4 [ label="#2" ]; + Con_6 [ label="#3" ]; + Add7 [ label="+" ]; } node [shape=plaintext]; - subgraph cluster_0 { - scope0 [label=< + subgraph cluster_Scope1_0 { + Scope1_0 [label=< - +
0abc
0abc
>]; - subgraph cluster_1 { - scope1 [label=< + subgraph cluster_Scope1_1 { + Scope1_1 [label=< - +
1b
1b
>]; } } - Con_2 -> Start1 [style=dotted]; - Con_3 -> Start1 [style=dotted]; - Con_5 -> Start1 [style=dotted]; - Add6 -> Con_2; - Add6 -> Con_5; + edge [ fontname=Helvetica, fontsize=8 ]; + Con_3 -> Start2[taillabel=0 style=dotted]; + Con_4 -> Start2[taillabel=0 style=dotted]; + Con_6 -> Start2[taillabel=0 style=dotted]; + Add7 -> Con_3[taillabel=1]; + Add7 -> Con_6[taillabel=2]; edge [style=dashed color=cornflowerblue]; - scope0:scope0_a -> Con_2; - scope0:scope0_b -> Con_3; - scope0:scope0_c -> Add6; - scope1:scope1_b -> Con_5; + Scope1_0:"Scope1_0_a" -> Con_3; + Scope1_0:"Scope1_0_b" -> Con_4; + Scope1_0:"Scope1_0_c" -> Add7; + Scope1_1:"Scope1_1_b" -> Con_6; } diff --git a/chapter03/docs/03-graph1.svg b/chapter03/docs/03-graph1.svg index 364cd42c..ab6f9600 100644 --- a/chapter03/docs/03-graph1.svg +++ b/chapter03/docs/03-graph1.svg @@ -14,77 +14,82 @@ -cluster_0 +cluster_Scope1_0 -cluster_1 +cluster_Scope1_1 - + -Start1 +Start2 Start - + -Con_2 +Con_3 #1 - + -Con_2->Start1 +Con_3->Start2 +0 - + -Con_3 +Con_4 #2 - + -Con_3->Start1 +Con_4->Start2 +0 - + -Con_5 +Con_6 #3 - + -Con_5->Start1 +Con_6->Start2 +0 - + -Add6 - -Add +Add7 + ++ - + -Add6->Con_2 - - +Add7->Con_3 + + +1 - + -Add6->Con_5 - - +Add7->Con_6 + + +2 - + -scope0 - +Scope1_0 + 0 @@ -94,36 +99,36 @@ c - + -scope0:scope0_a->Con_2 +Scope1_0:Scope1_0_a->Con_3 - + -scope0:scope0_b->Con_3 +Scope1_0:Scope1_0_b->Con_4 - + -scope0:scope0_c->Add6 - - +Scope1_0:Scope1_0_c->Add7 + + - + -scope1 - +Scope1_1 + 1 b - + -scope1:scope1_b->Con_5 +Scope1_1:Scope1_1_b->Con_6 diff --git a/chapter03/docs/03-graph2.gv b/chapter03/docs/03-graph2.gv index c6c55028..f570b13e 100644 --- a/chapter03/docs/03-graph2.gv +++ b/chapter03/docs/03-graph2.gv @@ -1,29 +1,35 @@ digraph chapter03 { +/* +int a=1; int b=2; int c=0; { int b=3; c=a+b; #showGraph; } return c; #showGraph; +*/ rankdir=BT; + ordering="in"; + concentrate="true"; subgraph cluster_Nodes { - Start1 [ shape=box style=filled fillcolor=yellow label="Start" ]; - Con_2 [ label="#1" ]; - Con_3 [ label="#2" ]; - Con_5 [ label="#3" ]; - Add6 [ label="Add" ]; - Return7 [ shape=box style=filled fillcolor=yellow label="Return" ]; + Start2 [ shape=box style=filled fillcolor=yellow label="Start" ]; + Con_3 [ label="#1" ]; + Con_4 [ label="#2" ]; + Con_6 [ label="#3" ]; + Add7 [ label="+" ]; + Return8 [ shape=box style=filled fillcolor=yellow label="Return" ]; } node [shape=plaintext]; - subgraph cluster_0 { - scope0 [label=< + subgraph cluster_Scope1_0 { + Scope1_0 [label=< - +
0abc
0abc
>]; } - Con_2 -> Start1 [style=dotted]; - Con_3 -> Start1 [style=dotted]; - Con_5 -> Start1 [style=dotted]; - Add6 -> Con_2; - Add6 -> Con_5; - Return7 -> Start1 [color=red]; - Return7 -> Add6; + edge [ fontname=Helvetica, fontsize=8 ]; + Con_3 -> Start2[taillabel=0 style=dotted]; + Con_4 -> Start2[taillabel=0 style=dotted]; + Con_6 -> Start2[taillabel=0 style=dotted]; + Add7 -> Con_3[taillabel=1]; + Add7 -> Con_6[taillabel=2]; + Return8 -> Start2[taillabel=0 color=red]; + Return8 -> Add7[taillabel=1]; edge [style=dashed color=cornflowerblue]; - scope0:scope0_a -> Con_2; - scope0:scope0_b -> Con_3; - scope0:scope0_c -> Add6; -} + Scope1_0:"Scope1_0_a" -> Con_3; + Scope1_0:"Scope1_0_b" -> Con_4; + Scope1_0:"Scope1_0_c" -> Add7; +} \ No newline at end of file diff --git a/chapter03/docs/03-graph2.svg b/chapter03/docs/03-graph2.svg index 4ab5a8b6..0a539c6a 100644 --- a/chapter03/docs/03-graph2.svg +++ b/chapter03/docs/03-graph2.svg @@ -14,91 +14,98 @@ -cluster_0 +cluster_Scope1_0 - + -Start1 +Start2 Start - + -Con_2 +Con_3 #1 - + -Con_2->Start1 +Con_3->Start2 +0 - + -Con_3 +Con_4 #2 - + -Con_3->Start1 +Con_4->Start2 +0 - + -Con_5 +Con_6 #3 - + -Con_5->Start1 +Con_6->Start2 +0 - + -Add6 - -Add +Add7 + ++ - + -Add6->Con_2 - - +Add7->Con_3 + + +1 - + -Add6->Con_5 - - +Add7->Con_6 + + +2 - + -Return7 - -Return +Return8 + +Return - + -Return7->Start1 - +Return8->Start2 + +0 - + -Return7->Add6 - - +Return8->Add7 + + +1 - + -scope0 - +Scope1_0 + 0 @@ -108,23 +115,23 @@ c - + -scope0:scope0_a->Con_2 +Scope1_0:Scope1_0_a->Con_3 - + -scope0:scope0_b->Con_3 +Scope1_0:Scope1_0_b->Con_4 - + -scope0:scope0_c->Add6 - - +Scope1_0:Scope1_0_c->Add7 + + diff --git a/chapter03/docs/03-graph3.gv b/chapter03/docs/03-graph3.gv index 717024e3..50cb6d27 100644 --- a/chapter03/docs/03-graph3.gv +++ b/chapter03/docs/03-graph3.gv @@ -1,32 +1,37 @@ digraph chapter03 { +/* +int x0=1; int y0=2; int x1=3; int y1=4; return (x0-x1)*(x0-x1) + (y0-y1)*(y0-y1); #showGraph; +*/ rankdir=BT; + ordering="in"; + concentrate="true"; subgraph cluster_Nodes { - Start1 [ shape=box style=filled fillcolor=yellow label="Start" ]; - Con_2 [ label="#1" ]; - Con_3 [ label="#2" ]; - Con_19 [ label="#8" ]; - Con_4 [ label="#3" ]; - Return20 [ shape=box style=filled fillcolor=yellow label="Return" ]; - Con_5 [ label="#4" ]; + Start2 [ shape=box style=filled fillcolor=yellow label="Start" ]; + Con_3 [ label="#1" ]; + Con_4 [ label="#2" ]; + Con_20 [ label="#8" ]; + Con_5 [ label="#3" ]; + Return21 [ shape=box style=filled fillcolor=yellow label="Return" ]; + Con_6 [ label="#4" ]; } node [shape=plaintext]; - subgraph cluster_0 { - scope0 [label=< + subgraph cluster_Scope1_0 { + Scope1_0 [label=< - +
0y0x0y1x1
0y0x0y1x1
>]; } - Con_2 -> Start1 [style=dotted]; - Con_3 -> Start1 [style=dotted]; - Con_19 -> Start1 [style=dotted]; - Con_4 -> Start1 [style=dotted]; - Return20 -> Start1 [color=red]; - Return20 -> Con_19; - Con_5 -> Start1 [style=dotted]; + edge [ fontname=Helvetica, fontsize=8 ]; + Con_3 -> Start2[taillabel=0 style=dotted]; + Con_4 -> Start2[taillabel=0 style=dotted]; + Con_20 -> Start2[taillabel=0 style=dotted]; + Con_5 -> Start2[taillabel=0 style=dotted]; + Return21 -> Start2[taillabel=0 color=red]; + Return21 -> Con_20[taillabel=1]; + Con_6 -> Start2[taillabel=0 style=dotted]; edge [style=dashed color=cornflowerblue]; - scope0:scope0_y0 -> Con_3; - scope0:scope0_x0 -> Con_2; - scope0:scope0_y1 -> Con_5; - scope0:scope0_x1 -> Con_4; + Scope1_0:"Scope1_0_y0" -> Con_4; + Scope1_0:"Scope1_0_x0" -> Con_3; + Scope1_0:"Scope1_0_y1" -> Con_6; + Scope1_0:"Scope1_0_x1" -> Con_5; } - diff --git a/chapter03/docs/03-graph3.svg b/chapter03/docs/03-graph3.svg index 3b720323..623bd902 100644 --- a/chapter03/docs/03-graph3.svg +++ b/chapter03/docs/03-graph3.svg @@ -14,96 +14,103 @@ -cluster_0 +cluster_Scope1_0 - + -Start1 +Start2 Start - + -Con_2 +Con_3 #1 - + -Con_2->Start1 +Con_3->Start2 +0 - + -Con_3 +Con_4 #2 - + -Con_3->Start1 +Con_4->Start2 +0 - + -Con_19 - -#8 +Con_20 + +#8 - + -Con_19->Start1 - - +Con_20->Start2 + + +0 - + -Con_4 +Con_5 #3 - + -Con_4->Start1 +Con_5->Start2 +0 - + -Return20 - -Return +Return21 + +Return - + -Return20->Start1 - - +Return21->Start2 + + +0 - + -Return20->Con_19 - - +Return21->Con_20 + + +1 - + -Con_5 +Con_6 #4 - + -Con_5->Start1 +Con_6->Start2 +0 - + -scope0 +Scope1_0 0 @@ -116,27 +123,27 @@ x1 - + -scope0:scope0_x0->Con_2 +Scope1_0:Scope1_0_x0->Con_3 - + -scope0:scope0_y0->Con_3 +Scope1_0:Scope1_0_y0->Con_4 - + -scope0:scope0_x1->Con_4 +Scope1_0:Scope1_0_x1->Con_5 - + -scope0:scope0_y1->Con_5 +Scope1_0:Scope1_0_y1->Con_6 diff --git a/chapter03/src/main/java/com/seaofnodes/simple/GraphVisualizer.java b/chapter03/src/main/java/com/seaofnodes/simple/GraphVisualizer.java index dc5358f4..a85a670e 100644 --- a/chapter03/src/main/java/com/seaofnodes/simple/GraphVisualizer.java +++ b/chapter03/src/main/java/com/seaofnodes/simple/GraphVisualizer.java @@ -18,6 +18,9 @@ public class GraphVisualizer { Collection all = findAll(parser); StringBuilder sb = new StringBuilder(); sb.append("digraph chapter03 {\n"); + sb.append("/*\n"); + sb.append(parser.src()); + sb.append("\n*/\n"); // 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 @@ -25,6 +28,13 @@ public class GraphVisualizer { // scopes and nodes. If we try to cross subgraph cluster borders while // still making the subgraphs DOT gets confused. sb.append("\trankdir=BT;\n"); // Force Nodes before Scopes + + // Preserve node input order + sb.append("\tordering=\"in\";\n"); + + // Merge multiple edges hitting the same node. Makes common shared + // nodes much prettier to look at. + sb.append("\tconcentrate=\"true\";\n"); // Just the Nodes first, in a cluster no edges nodes(sb, all); @@ -47,12 +57,15 @@ public class GraphVisualizer { // Just the Nodes first, in a cluster no edges sb.append("\tsubgraph cluster_Nodes {\n"); // Magic "cluster_" in the subgraph name for( Node n : all ) { + if( n instanceof ScopeNode ) + continue; // Do not emit, rolled into Scope cluster already sb.append("\t\t").append(n.uniqueName()).append(" [ "); + String lab = n.glabel(); // control nodes have box shape // other nodes are ellipses, i.e. default shape - if( n instanceof Control ) + if( n.isCFG() ) sb.append("shape=box style=filled fillcolor=yellow "); - sb.append("label=\"").append(n.label()).append("\" "); + sb.append("label=\"").append(lab).append("\" "); sb.append("];\n"); } sb.append("\t}\n"); // End Node cluster @@ -62,12 +75,12 @@ public class GraphVisualizer { sb.append("\tnode [shape=plaintext];\n"); int level=0; for( HashMap scope : scopenode._scopes ) { - sb.append("\tsubgraph cluster_").append(level).append(" {\n"); // Magic "cluster_" in the subgraph name - String scopeName = makeScopeName(level); + String scopeName = makeScopeName(scopenode, level); + sb.append("\tsubgraph cluster_").append(scopeName).append(" {\n"); // Magic "cluster_" in the subgraph name sb.append("\t\t").append(scopeName).append(" [label=<\n"); sb.append("\t\t\t\n"); // Add the scope level - sb.append("\t\t\t"); + sb.append("\t\t\t"); for( String name : scope.keySet() ) sb.append(""); sb.append("\n"); @@ -79,23 +92,37 @@ public class GraphVisualizer { // We close them all at once here. sb.append( "\t}\n".repeat( level ) ); // End all Scope clusters } - - private String makeScopeName(int level) { return "scope" + level; } + + private String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; } private String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; } // Walk the node edges private void nodeEdges(StringBuilder sb, Collection all) { - for( Node n : all ) - for( Node out : n._inputs ) - if( out != null ) { - sb.append('\t').append(n.uniqueName()).append(" -> ").append(out.uniqueName()); - // Control edges are colored red - if( n instanceof ConstantNode && out instanceof StartNode ) - sb.append(" [style=dotted]"); - else if( n instanceof Control && out instanceof Control ) - sb.append(" [color=red]"); - sb.append(";\n"); + // All them edge labels + sb.append("\tedge [ fontname=Helvetica, fontsize=8 ];\n"); + for( Node n : all ) { + // In this chapter we do display the Constant->Start edge; + // ScopeNodes are done separately + if( n instanceof ScopeNode ) + continue; + int i=0; + for( Node def : n._inputs ) { + if( def != null ) { + // Most edges land here use->def + sb.append('\t').append(n.uniqueName()).append(" -> "); + sb.append(def.uniqueName()); + // Number edges, so we can see how they track + sb.append("[taillabel=").append(i); + if( n instanceof ConstantNode && def instanceof StartNode ) + sb.append(" style=dotted"); + // control edges are colored red + else if( def.isCFG() ) + sb.append(" color=red"); + sb.append("];\n"); } + i++; + } + } } // Walk the scope edges @@ -103,19 +130,23 @@ public class GraphVisualizer { sb.append("\tedge [style=dashed color=cornflowerblue];\n"); int level=0; for( HashMap scope : scopenode._scopes ) { - String scopeName = makeScopeName(level); - for( String name : scope.keySet() ) - sb.append("\t").append(scopeName).append(":").append(makePortName(scopeName, name)).append(" -> ").append(scopenode.in(scope.get(name)).uniqueName()).append(";\n"); + String scopeName = makeScopeName(scopenode, level); + for( String name : scope.keySet() ) { + Node def = scopenode.in(scope.get(name)); + if( def==null ) continue; + sb.append("\t") + .append(scopeName).append(":") + .append('"').append(makePortName(scopeName, name)).append('"') // wrap port name with quotes because $ctrl is not valid unquoted + .append(" -> "); + sb.append(def.uniqueName()); + sb.append(";\n"); + } level++; } } /** - * Walks the whole graph, starting from Start. - * Since Start is the input to all constants - we look at the outputs for - * Start, but for then subsequently we look at the inputs of each node. - * During graph construction not all nodes are reachable this way, so we - * also scan the symbol tables. + * Finds all nodes in the graph. */ private Collection findAll(Parser parser) { final StartNode start = Parser.START; @@ -133,10 +164,14 @@ public class GraphVisualizer { * Walk a subgraph and populate distinct nodes in the all list. */ private void walk(HashMap all, Node n) { + if(n == null ) return; if (all.get(n._nid) != null) return; // Been there, done that all.put(n._nid, n); for (Node c : n._inputs) if (c != null) walk(all, c); + for (Node c : n._outputs) + if (c != null) + walk(all, c); } } diff --git a/chapter03/src/main/java/com/seaofnodes/simple/Parser.java b/chapter03/src/main/java/com/seaofnodes/simple/Parser.java index 3d06a9c3..c8195882 100644 --- a/chapter03/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter03/src/main/java/com/seaofnodes/simple/Parser.java @@ -23,6 +23,7 @@ public class Parser { */ public static StartNode START; + // The Lexer. Thin wrapper over a byte[] buffer with a cursor. private final Lexer _lexer; /** @@ -31,17 +32,35 @@ public class Parser { */ public ScopeNode _scope; + /** + * List of keywords disallowed as identifiers + */ + private final HashSet KEYWORDS = new HashSet<>(){{ + add("int"); + add("return"); + }}; + + public Parser(String source) { _lexer = new Lexer(source); - _scope = new ScopeNode(); Node.reset(); + _scope = new ScopeNode(); START = new StartNode(); } - public ReturnNode parse() { - return (ReturnNode) parseBlock(false); - } + String src() { return new String( _lexer._input ); } + public ReturnNode parse() { return parse(false); } + public ReturnNode parse(boolean show) { + try { + var ret = (ReturnNode) parseBlock(); + if (!_lexer.isEOF()) throw error("Syntax error, unexpected " + _lexer.getAnyNextToken()); + return ret; + } + finally { + if( show ) showGraph(); + } + } /** * Parses a block @@ -49,23 +68,17 @@ public class Parser { *
      *     '{' statements '}'
      * 
+ * Does not parse the opening or closing '{}' + * @return a {@link Node} or {@code null} */ - private Node parseBlock(boolean requireClosingBracket) { + private Node parseBlock() { // Enter a new scope _scope.push(); Node n = null; - do { - if (match("}")) { - if (requireClosingBracket) break; - else throw error("unexpected '}' token"); - } else if (_lexer.isEOF()) { - if (requireClosingBracket) require("}"); - else break; - } - + while (!peek('}') && !_lexer.isEOF()) { Node n0 = parseStatement(); if (n0 != null) n = n0; // Allow null returns from eg showGraph - } while (true); + }; // Exit scope _scope.pop(); return n; @@ -77,32 +90,34 @@ public class Parser { *
      *     returnStatement | declStatement | blockStatement | expressionStatement
      * 
+ * @return a {@link Node} or {@code null} */ private Node parseStatement() { - if (match("return")) return parseReturn(); - else if (match("int")) return parseDecl(); - else if (match("{")) return parseBlock(true); - else if (match("#showGraph")) return showGraph(); + if (matchx("return") ) return parseReturn(); + else if (matchx("int")) return parseDecl(); + else if (match ("{" )) return require(parseBlock(),"}"); + else if (matchx("#showGraph")) return require(showGraph(),";"); else return parseExpressionStatement(); } /** - * Parses returnStatement; "return" already parsed + * Parses a return statement; "return" already parsed. * *
      *     'return' expr ;
      * 
+ * @return an expression {@link Node}, never {@code null} */ private Node parseReturn() { var expr = require(parseExpression(), ";"); - return new ReturnNode(START, expr); + return new ReturnNode(START, expr).peephole(); } /** * Dumps out the node graph + * @return {@code null} */ private Node showGraph() { - require(";"); System.out.println(new GraphVisualizer().generateDotOutput(this)); return null; } @@ -113,6 +128,7 @@ public class Parser { *
      *     name '=' expression ';'
      * 
+ * @return an expression {@link Node}, never {@code null} */ private Node parseExpressionStatement() { var name = requireId(); @@ -129,6 +145,7 @@ public class Parser { *
      *     'int' name = expression ';'
      * 
+ * @return an expression {@link Node}, never {@code null} */ private Node parseDecl() { // Type is 'int' for now @@ -146,8 +163,9 @@ public class Parser { *
      *     expr : additiveExpr
      * 
+ * @return an expression {@link Node}, never {@code null} */ - private Node parseExpression() {return parseAddition();} + private Node parseExpression() { return parseAddition(); } /** * Parse an additive expression @@ -155,6 +173,7 @@ public class Parser { *
      *     additiveExpr : multiplicativeExpr (('+' | '-') multiplicativeExpr)*
      * 
+ * @return an add expression {@link Node}, never {@code null} */ private Node parseAddition() { var lhs = parseMultiplication(); @@ -169,6 +188,7 @@ public class Parser { *
      *     multiplicativeExpr : unaryExpr (('*' | '/') unaryExpr)*
      * 
+ * @return a multiply expression {@link Node}, never {@code null} */ private Node parseMultiplication() { var lhs = parseUnary(); @@ -183,6 +203,7 @@ public class Parser { *
      *     unaryExpr : ('-') unaryExpr | primaryExpr
      * 
+ * @return a unary expression {@link Node}, never {@code null} */ private Node parseUnary() { if (match("-")) return new MinusNode(parseUnary()).peephole(); @@ -195,15 +216,16 @@ public class Parser { *
      *     primaryExpr : integerLiteral | Identifier | '(' expression ')'
      * 
+ * @return a primary {@link Node}, never {@code null} */ private Node parsePrimary() { - if (_lexer.isNumber()) return parseIntegerLiteral(); - if (match("(")) return require(parseExpression(), ")"); - String name = requireId(); - Node id = _scope.lookup(name); - if( id==null ) - throw error("Undefined name '" + name + "'"); - return id; + if( _lexer.isNumber() ) return parseIntegerLiteral(); + if( match("(") ) return require(parseExpression(), ")"); + String name = _lexer.matchId(); + if( name == null) throw errorSyntax("an identifier or expression"); + Node n = _scope.lookup(name); + if( n!=null ) return n; + throw error("Undefined name '" + name + "'"); } /** @@ -221,17 +243,21 @@ public class Parser { // Utilities for lexical analysis // Return true and skip if "syntax" is next in the stream. - private boolean match(String syntax) {return _lexer.match(syntax);} + private boolean match (String syntax) { return _lexer.match (syntax); } + // Match must be "exact", not be followed by more id letters + private boolean matchx(String syntax) { return _lexer.matchx(syntax); } + // Return true and do NOT skip if 'ch' is next + private boolean peek(char ch) { return _lexer.peek(ch); } // Require and return an identifier private String requireId() { String id = _lexer.matchId(); - if (id != null) return id; - throw error("identifier"); + if (id != null && !KEYWORDS.contains(id) ) return id; + throw error("Expected an identifier, found '"+id+"'"); } // Require an exact match - private void require(String syntax) {require(null, syntax);} + private void require(String syntax) { require(null, syntax); } private Node require(Node n, String syntax) { if (match(syntax)) return n; throw errorSyntax(syntax); @@ -313,11 +339,24 @@ public class Parser { int len = syntax.length(); if (_position + len > _input.length) return false; for (int i = 0; i < len; i++) - if ((char) _input[_position + i] != syntax.charAt(i)) return false; + if ((char) _input[_position + i] != syntax.charAt(i)) + return false; _position += len; return true; } + boolean matchx(String syntax) { + if( !match(syntax) ) return false; + if( !isIdLetter(peek()) ) return true; + _position -= syntax.length(); + return false; + } + + private boolean peek(char ch) { + skipWhiteSpace(); + return peek()==ch; + } + // Return an identifier or null String matchId() { skipWhiteSpace(); @@ -341,7 +380,7 @@ public class Parser { String snum = new String(_input, start, --_position - start); if (snum.length() > 1 && snum.charAt(0) == '0') throw error("Syntax error: integer values cannot start with '0'"); - return new TypeInteger(Long.parseLong(snum)); + return TypeInteger.constant(Long.parseLong(snum)); } // First letter of an identifier @@ -367,8 +406,7 @@ public class Parser { private String parsePunctuation() { int start = _position; - if (isPunctuation(nextChar())) ; - return new String(_input, start, --_position - start); + return new String(_input, start, 1); } } diff --git a/chapter03/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter03/src/main/java/com/seaofnodes/simple/node/AddNode.java index 015779dc..6784ad60 100644 --- a/chapter03/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/chapter03/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -3,17 +3,16 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.type.*; public class AddNode extends Node { - public AddNode(Node lhs, Node rhs) { - super(null, lhs, rhs); - } + public AddNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - @Override - public String label() { return "Add"; } + @Override public String label() { return "Add"; } + + @Override public String glabel() { return "+"; } @Override - StringBuilder _print(StringBuilder sb) { - in(1)._print(sb.append("(")); - in(2)._print(sb.append("+")); + StringBuilder _print1(StringBuilder sb) { + in(1)._print0(sb.append("(")); + in(2)._print0(sb.append("+")); return sb.append(")"); } @@ -21,8 +20,9 @@ public class AddNode extends Node { @Override public Type compute() { if( in(1)._type instanceof TypeInteger i0 && - in(2)._type instanceof TypeInteger i1 ) - return new TypeInteger(i0._con + i1._con); + in(2)._type instanceof TypeInteger i1 ) { + return TypeInteger.constant(i0.value()+i1.value()); + } return TypeBot.BOTTOM; } diff --git a/chapter03/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter03/src/main/java/com/seaofnodes/simple/node/ConstantNode.java index 798d40d5..ec85bf1f 100644 --- a/chapter03/src/main/java/com/seaofnodes/simple/node/ConstantNode.java +++ b/chapter03/src/main/java/com/seaofnodes/simple/node/ConstantNode.java @@ -16,25 +16,25 @@ import com.seaofnodes.simple.type.Type; * The Constant's value is the value stored in it. */ public class ConstantNode extends Node { - + Type _con; public ConstantNode( Type type ) { super(Parser.START); - _type = type; + _con = type; } @Override - public String label() { return "#"+_type; } + public String label() { return "#"+_con; } @Override public String uniqueName() { return "Con_" + _nid; } @Override - StringBuilder _print(StringBuilder sb) { - return _type._print(sb); + StringBuilder _print1(StringBuilder sb) { + return _con._print(sb); } @Override - public Type compute() { return _type; } + public Type compute() { return _con; } @Override public Node idealize() { return null; } diff --git a/chapter03/src/main/java/com/seaofnodes/simple/node/Control.java b/chapter03/src/main/java/com/seaofnodes/simple/node/Control.java deleted file mode 100644 index 48cf2b7a..00000000 --- a/chapter03/src/main/java/com/seaofnodes/simple/node/Control.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.seaofnodes.simple.node; - - -/** - * Control interface is a marker interface to be implemented - * by Control nodes. - */ -public interface Control { -} diff --git a/chapter03/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter03/src/main/java/com/seaofnodes/simple/node/DivNode.java index 0056c42d..ca7df79c 100644 --- a/chapter03/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/chapter03/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -5,17 +5,16 @@ import com.seaofnodes.simple.type.TypeBot; import com.seaofnodes.simple.type.TypeInteger; public class DivNode extends Node { - public DivNode(Node lhs, Node rhs) { - super(null, lhs, rhs); - } + public DivNode(Node lhs, Node rhs) { super(null, lhs, rhs); } + + @Override public String label() { return "Div"; } + + @Override public String glabel() { return "//"; } @Override - public String label() { return "Div"; } - - @Override - StringBuilder _print(StringBuilder sb) { - in(1)._print(sb.append("(")); - in(2)._print(sb.append("/")); + StringBuilder _print1(StringBuilder sb) { + in(1)._print0(sb.append("(")); + in(2)._print0(sb.append("/")); return sb.append(")"); } @@ -23,11 +22,9 @@ public class DivNode extends Node { public Type compute() { if (in(1)._type instanceof TypeInteger i0 && in(2)._type instanceof TypeInteger i1) { - if (i0.isConstant() && i1.isConstant()) - return i1._con == 0 - ? TypeInteger.ZERO - : new TypeInteger(i0._con/i1._con); - return new TypeInteger(i0._con / i1._con); + return i1.value() == 0 + ? TypeInteger.ZERO + : TypeInteger.constant(i0.value()/i1.value()); } return TypeBot.BOTTOM; } diff --git a/chapter03/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter03/src/main/java/com/seaofnodes/simple/node/MinusNode.java index 7fab83b0..834f4c11 100644 --- a/chapter03/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter03/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -5,23 +5,22 @@ import com.seaofnodes.simple.type.TypeBot; import com.seaofnodes.simple.type.TypeInteger; public class MinusNode extends Node { - public MinusNode(Node in) { - super(null, in); - } + public MinusNode(Node in) { super(null, in); } - @Override - public String label() { return "Minus"; } + @Override public String label() { return "Minus"; } + + @Override public String glabel() { return "-"; } @Override - StringBuilder _print(StringBuilder sb) { - in(1)._print(sb.append("(-")); + StringBuilder _print1(StringBuilder sb) { + in(1)._print0(sb.append("(-")); return sb.append(")"); } @Override public Type compute() { if (in(1)._type instanceof TypeInteger i0) - return new TypeInteger(-i0._con); + return TypeInteger.constant(-i0.value()); return TypeBot.BOTTOM; } diff --git a/chapter03/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter03/src/main/java/com/seaofnodes/simple/node/MulNode.java index e1a26858..0fa54966 100644 --- a/chapter03/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/chapter03/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -5,25 +5,25 @@ import com.seaofnodes.simple.type.TypeBot; import com.seaofnodes.simple.type.TypeInteger; public class MulNode extends Node { - public MulNode(Node lhs, Node rhs) { - super(null, lhs, rhs); - } - - @Override - public String label() { return "Mul"; } + public MulNode(Node lhs, Node rhs) { super(null, lhs, rhs); } + + @Override public String label() { return "Mul"; } + @Override public String glabel() { return "*"; } + @Override - StringBuilder _print(StringBuilder sb) { - in(1)._print(sb.append("(")); - in(2)._print(sb.append("*")); + StringBuilder _print1(StringBuilder sb) { + in(1)._print0(sb.append("(")); + in(2)._print0(sb.append("*")); return sb.append(")"); } - + @Override public Type compute() { if (in(1)._type instanceof TypeInteger i0 && - in(2)._type instanceof TypeInteger i1) - return new TypeInteger(i0._con * i1._con); + in(2)._type instanceof TypeInteger i1) { + return TypeInteger.constant(i0.value()*i1.value()); + } return TypeBot.BOTTOM; } diff --git a/chapter03/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter03/src/main/java/com/seaofnodes/simple/node/Node.java index 9c71c424..a778687d 100644 --- a/chapter03/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter03/src/main/java/com/seaofnodes/simple/node/Node.java @@ -2,8 +2,7 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.type.Type; -import java.util.ArrayList; -import java.util.Collections; +import java.util.*; /** * All Nodes in the Sea of Nodes IR inherit from the Node class. @@ -24,8 +23,7 @@ public abstract class Node { *

* Generally fixed length, ordered, nulls allowed, no unused trailing space. * Ordering is required because e.g. "a/b" is different from "b/a". - * The first input (offset 0) is often a Control node. - * @see Control + * The first input (offset 0) is often a {@link #isCFG} node. */ public final ArrayList _inputs; @@ -40,6 +38,13 @@ public abstract class Node { */ public final ArrayList _outputs; + + /** + * Current computed type for this Node. This value changes as the graph + * changes and more knowledge is gained about the program. + */ + public Type _type; + /** * A private Global Static mutable counter, for unique node id generation. * To make the compiler multi-threaded, this field will have to move into a TLS. @@ -47,34 +52,47 @@ public abstract class Node { * */ private static int UNIQUE_ID = 1; - protected Node(Node ...inputs) { + protected Node(Node... inputs) { _nid = UNIQUE_ID++; // allocate unique dense ID _inputs = new ArrayList<>(); Collections.addAll(_inputs,inputs); _outputs = new ArrayList<>(); for( Node n : _inputs ) if( n != null ) - n._outputs.add( this ); - // Do an initial type computation - _type = compute(); + n.addUse( this ); } + // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ" public abstract String label(); + // Unique label for graph visualization, e.g. "Add12" or "Region30" or "EQ99" public String uniqueName() { return label() + _nid; } + // Graphical label, e.g. "+" or "Region" or "==" + public String glabel() { return label(); } + @Override public final String toString() { // TODO: This needs a lot of work - return uniqueName(); + return print(); } // This is a *deep* print. This version will fail on cycles, which we will - // correct later when we can parse programs with loops. + // correct later when we can parse programs with loops. We print with a + // tik-tok style; the common _print0 calls the per-Node _print1, which + // calls back to _print0; public final String print() { - return _print(new StringBuilder()).toString(); + return _print0(new StringBuilder()).toString(); + } + // This is the common print: check for DEAD and print "DEAD" else call the + // per-Node print1. + final StringBuilder _print0(StringBuilder sb) { + return isDead() + ? sb.append(uniqueName()).append(":DEAD") + : _print1(sb); } - abstract StringBuilder _print(StringBuilder sb); + // Every Node implements this. + abstract StringBuilder _print1(StringBuilder sb); /** @@ -86,25 +104,21 @@ public abstract class Node { public int nIns() { return _inputs.size(); } - /** - * Gets the ith output node - * @param i Offset of the output node - * @return Output node (not null) - */ - public Node out(int i) { return _outputs.get(i); } - public int nOuts() { return _outputs.size(); } + public boolean isUnused() { return nOuts() == 0; } + + public boolean isCFG() { return false; } + /** * We allow disabling peephole opt so that we can observe the * full graph, vs the optimized graph. */ public static boolean _disablePeephole = false; - /** * Try to peephole at this node and return a better replacement Node if - * possible. We check and replace: + * possible. We compute a {@link Type} and then check and replace: *

    *
  • if the Type {@link Type#isConstant}, we replace with a {@link ConstantNode}
  • *
  • in a future chapter we will look for a @@ -114,36 +128,27 @@ public abstract class Node { *
*/ public final Node peephole( ) { + // Compute initial or improved Type + Type type = _type = compute(); + if (_disablePeephole) return this; // Peephole optimizations turned off // Replace constant computations from non-constants with a constant node - Type type = compute(); if (!(this instanceof ConstantNode) && type.isConstant()) { kill(); // Kill `this` because replacing with a Constant - return new ConstantNode(type); + return new ConstantNode(type).peephole(); } // Future chapter: Global Value Numbering goes here - // Ask each node for a replacement + // Ask each node for a better replacement Node n = idealize(); if( n != null ) return n; return this; // No progress } - /** - * Kill a Node with no uses, by setting all of its defs - * to null. This may recursively kill more Nodes and is basically dead - * code elimination. This function is co-recursive with {@link #set_def}. - */ - public void kill( ) { - assert nOuts()==0; // Has no uses, so it is dead - for( int i=0; idef into a Node. Keeps the edges correct, by removing * the corresponding use->def edge. This may make the original @@ -151,6 +156,7 @@ public abstract class Node { * * @param idx which def to set * @param new_def the new definition + * @return this for flow coding */ Node set_def(int idx, Node new_def ) { Node old_def = in(idx); @@ -159,10 +165,10 @@ public abstract class Node { // This needs to happen before removing the old node's def->use edge as // the new_def might get killed if the old node kills it recursively. if( new_def != null ) - new_def._outputs.add(this); + new_def.addUse(this); if( old_def != null && // If the old def exists, remove a def->use edge old_def.delUse(this) ) // If we removed the last use, the old def is now dead - old_def.kill(); // Kill old def + old_def.kill(); // Kill old def // Set the new_def over the old (killed) edge _inputs.set(idx,new_def); // Return self for easy flow-coding @@ -181,11 +187,10 @@ public abstract class Node { _inputs.add(new_def); // If new def is not null, add the corresponding def->use edge if( new_def != null ) - new_def._outputs.add(this); + new_def.addUse(this); return new_def; } - // Breaks the edge invariants, used temporarily private void addUse(Node n) { _outputs.add(n); } @@ -194,10 +199,10 @@ public abstract class Node { // Error is 'use' does not exist; ok for 'use' to be null. private boolean delUse( Node use ) { ArrayList outs = _outputs; - int lidx = outs.size()-1; // Last index + int lidx = outs.size()-1; // Last index // This 1-line hack compresses an element out of an ArrayList // without having to copy the contents. The last element is - // stuffed over the deleted element, and then the size is reduced. + // stuffed over the deleted element, and then the size is reduced. outs.set(outs.indexOf(use),outs.get(lidx)); outs.remove(lidx); // Reduce ArrayList size without copying anything return lidx==0; @@ -209,17 +214,26 @@ public abstract class Node { for( int i=0; iuses, by setting all of its defs + * to null. This may recursively kill more Nodes and is basically dead + * code elimination. This function is co-recursive with {@link #pop_n}. */ - public Type _type; - + public void kill( ) { + assert isUnused(); // Has no uses, so it is dead + pop_n(nIns()); // Set all inputs to null, recursively killing unused Nodes + _type=null; // Flag as dead + assert isDead(); // Really dead now + } + + // Mostly used for asserts and printing. + boolean isDead() { return isUnused() && nIns()==0 && _type==null; } + /** * This function needs to be * Monotonic @@ -245,9 +259,4 @@ public abstract class Node { * Node unique id generator, and is done as part of making a new Parser. */ public static void reset() { UNIQUE_ID = 1; } - - /* - * hashCode and equals implementation to be added in later chapter. - */ - } diff --git a/chapter03/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter03/src/main/java/com/seaofnodes/simple/node/ReturnNode.java index c188ca79..18f5eed9 100644 --- a/chapter03/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ b/chapter03/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -1,6 +1,6 @@ package com.seaofnodes.simple.node; -import com.seaofnodes.simple.type.TypeBot; +import com.seaofnodes.simple.type.*; /** * The Return node has two inputs. The first input is a control node and the @@ -11,7 +11,7 @@ import com.seaofnodes.simple.type.TypeBot; *

* The Return's output is the value from the data node. */ -public class ReturnNode extends Node implements Control { +public class ReturnNode extends Node { public ReturnNode(Node ctrl, Node data) { super(ctrl, data); @@ -24,10 +24,11 @@ public class ReturnNode extends Node implements Control { public String label() { return "Return"; } @Override - StringBuilder _print(StringBuilder sb) { - return expr()._print(sb.append("return ")).append(";"); + StringBuilder _print1(StringBuilder sb) { + return expr()._print0(sb.append("return ")).append(";"); } + @Override public boolean isCFG() { return true; } @Override public TypeBot compute() { diff --git a/chapter03/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter03/src/main/java/com/seaofnodes/simple/node/ScopeNode.java index c7d6edb9..5f5d66a0 100644 --- a/chapter03/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ b/chapter03/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -1,9 +1,9 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.type.TypeBot; -import java.util.HashMap; -import java.util.Stack; +import java.util.*; /** * The Scope node is a purely parser helper - it tracks names to nodes with a @@ -17,12 +17,15 @@ public class ScopeNode extends Node { * The top of this stack represents current scope. */ public final Stack> _scopes = new Stack<>(); - - @Override - public String label() { return "Scope"; } - + + // Reverse lookup for nicer graph printing + private final ArrayList _rlabels = new ArrayList<>(); + + @Override public String label() { return "Scope"; } + @Override - StringBuilder _print(StringBuilder sb) { + StringBuilder _print1(StringBuilder sb) { + sb.append(label()); for( HashMap scope : _scopes ) { sb.append("["); boolean first=true; @@ -32,22 +35,21 @@ public class ScopeNode extends Node { sb.append(name).append(":"); Node n = in(scope.get(name)); if( n==null ) sb.append("null"); - else n._print(sb); + else n._print0(sb); } sb.append("]"); } return sb; } - + @Override public TypeBot compute() { return TypeBot.BOTTOM; } - @Override - public Node idealize() { return null; } + @Override public Node idealize() { return null; } // Add an empty lexical scope - public void push() { - _scopes.push(new HashMap<>()); + public HashMap push() { + return _scopes.push(new HashMap<>()); } // Remove the current lexical scope, killing all unused nodes. @@ -61,6 +63,7 @@ public class ScopeNode extends Node { HashMap scope = _scopes.lastElement(); if( scope.put(name,nIns()) != null ) return null; // Double define + _rlabels.add(name); return add_def(n); } @@ -73,7 +76,7 @@ public class ScopeNode extends Node { return null; } - + // If the name is present in any scope, then redefine public Node update(String name, Node n) { for (int i = _scopes.size() - 1; i >= 0; i--) { @@ -85,5 +88,4 @@ public class ScopeNode extends Node { return null; } - } diff --git a/chapter03/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter03/src/main/java/com/seaofnodes/simple/node/StartNode.java index 8d8031bb..08b8333a 100644 --- a/chapter03/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ b/chapter03/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -3,11 +3,13 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.type.TypeBot; /** - * The Start node represents the start of the function. For now, we do not have any inputs to Start because our function does not - * yet accept parameters. When we add parameters the value of Start will be a tuple, and will require Projections to extract the values. - * We discuss this in detail in Chapter 9: Functions and Calls. + * The Start node represents the start of the function. For now, we do not + * have any inputs to Start because our function does not yet accept + * parameters. When we add parameters the value of Start will be a tuple, and + * will require Projections to extract the values. We discuss this in detail + * in Chapter 9: Functions and Calls. */ -public class StartNode extends Node implements Control { +public class StartNode extends Node { public StartNode(/*arguments go here*/) { super(); @@ -17,10 +19,12 @@ public class StartNode extends Node implements Control { public String label() { return "Start"; } @Override - StringBuilder _print(StringBuilder sb) { + StringBuilder _print1(StringBuilder sb) { return sb.append(label()); } - + + @Override public boolean isCFG() { return true; } + @Override public TypeBot compute() { return TypeBot.BOTTOM; diff --git a/chapter03/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter03/src/main/java/com/seaofnodes/simple/node/SubNode.java index 2d7db8d3..71d70fa6 100644 --- a/chapter03/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/chapter03/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -5,25 +5,25 @@ import com.seaofnodes.simple.type.TypeBot; import com.seaofnodes.simple.type.TypeInteger; public class SubNode extends Node { - public SubNode(Node lhs, Node rhs) { - super(null, lhs, rhs); - } + public SubNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - @Override - public String label() { return "Sub"; } + @Override public String label() { return "Sub"; } + + @Override public String glabel() { return "-"; } @Override - StringBuilder _print(StringBuilder sb) { - in(1)._print(sb.append("(")); - in(2)._print(sb.append("-")); + StringBuilder _print1(StringBuilder sb) { + in(1)._print0(sb.append("(")); + in(2)._print0(sb.append("-")); return sb.append(")"); } @Override public Type compute() { if (in(1)._type instanceof TypeInteger i0 && - in(2)._type instanceof TypeInteger i1) - return new TypeInteger(i0._con - i1._con); + in(2)._type instanceof TypeInteger i1) { + return TypeInteger.constant(i0.value()-i1.value()); + } return TypeBot.BOTTOM; } diff --git a/chapter03/src/main/java/com/seaofnodes/simple/type/TypeBot.java b/chapter03/src/main/java/com/seaofnodes/simple/type/TypeBot.java index 5284884a..bc191241 100644 --- a/chapter03/src/main/java/com/seaofnodes/simple/type/TypeBot.java +++ b/chapter03/src/main/java/com/seaofnodes/simple/type/TypeBot.java @@ -8,7 +8,7 @@ public class TypeBot extends Type { @Override public String toString() { return "Bottom"; } - - @Override + + @Override public StringBuilder _print(StringBuilder sb) { return sb.append(toString()); } } diff --git a/chapter03/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/chapter03/src/main/java/com/seaofnodes/simple/type/TypeInteger.java index 06cac15e..ff6e865b 100644 --- a/chapter03/src/main/java/com/seaofnodes/simple/type/TypeInteger.java +++ b/chapter03/src/main/java/com/seaofnodes/simple/type/TypeInteger.java @@ -14,6 +14,8 @@ public class TypeInteger extends Type { public TypeInteger(long con) { _con = con; } + public static TypeInteger constant(long con) { return new TypeInteger(con); } + @Override public String toString() { return _print(new StringBuilder()).toString(); @@ -25,7 +27,7 @@ public class TypeInteger extends Type { @Override public boolean isConstant() { return true; } - public long getConstant() { return _con; } + public long value() { return _con; } @Override public boolean equals( Object o ) { diff --git a/chapter03/src/test/java/com/seaofnodes/simple/Chapter03Test.java b/chapter03/src/test/java/com/seaofnodes/simple/Chapter03Test.java index 21faacac..d86b978f 100644 --- a/chapter03/src/test/java/com/seaofnodes/simple/Chapter03Test.java +++ b/chapter03/src/test/java/com/seaofnodes/simple/Chapter03Test.java @@ -1,16 +1,12 @@ package com.seaofnodes.simple; -import com.seaofnodes.simple.node.ConstantNode; -import com.seaofnodes.simple.node.Node; -import com.seaofnodes.simple.node.ReturnNode; -import com.seaofnodes.simple.node.StartNode; +import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.type.TypeInteger; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; public class Chapter03Test { @@ -54,6 +50,15 @@ public class Chapter03Test { assertEquals("return 8;", ret.print()); } + @Test + public void testSelfAssign() { + try { + new Parser("int a=a; return a;").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Undefined name 'a'",e.getMessage()); + } + } @Test public void testChapter2ParseGrammar() { @@ -120,7 +125,7 @@ public class Chapter03Test { Node expr = ret.expr(); if( expr instanceof ConstantNode con ) { assertEquals(start,con.in(0)); - assertEquals(new TypeInteger(1), con._type); + assertEquals(TypeInteger.constant(1), con._type); } else { fail(); } @@ -133,21 +138,27 @@ public class Chapter03Test { StartNode start = Parser.START; for( Node use : start._outputs ) if( use instanceof ConstantNode con ) - assertEquals(new TypeInteger(0),con._type); + assertEquals(TypeInteger.constant(0),con._type); } @Test public void testBad1() { - exceptionRule.expect(RuntimeException.class); - exceptionRule.expectMessage("Syntax error, expected =: "); - new Parser("ret").parse(); + try { + new Parser("ret").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Syntax error, expected =: ",e.getMessage()); + } } @Test public void testBad2() { - exceptionRule.expect(RuntimeException.class); - exceptionRule.expectMessage("Syntax error: integer values cannot start with '0'"); - new Parser("return 0123;").parse(); + try { + new Parser("return 0123;").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Syntax error: integer values cannot start with '0'",e.getMessage()); + } } @Test @@ -158,9 +169,12 @@ public class Chapter03Test { @Test public void testBad4() { - exceptionRule.expect(RuntimeException.class); - exceptionRule.expectMessage("Syntax error, expected ;:"); - new Parser("return 100").parse(); + try { + new Parser("return 100").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Syntax error, expected ;: ",e.getMessage()); + } } @Test @@ -171,8 +185,22 @@ public class Chapter03Test { @Test public void testBad6() { - exceptionRule.expect(RuntimeException.class); - exceptionRule.expectMessage("Syntax error, expected }:"); - new Parser("int a=1; int b=2; int c=0; { int b=3; c=a+b;").parse(); + try { + new Parser("int a=1; int b=2; int c=0; { int b=3; c=a+b;").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Syntax error, expected }: ",e.getMessage()); + } } + + @Test + public void testBad7() { + try { + new Parser("return 1;}").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Syntax error, unexpected }",e.getMessage()); + } + } + } diff --git a/chapter04/README.md b/chapter04/README.md index 2700a6a9..350b08b5 100644 --- a/chapter04/README.md +++ b/chapter04/README.md @@ -15,12 +15,12 @@ Here is the [complete language grammar](docs/04-grammar.md) for this chapter. In this chapter we add some new nodes and revise some existing nodes. Following are revised or new nodes -| Node Name | Type | Description | Inputs | Value | -|-----------|------------------|------------------------------------------------|--------------------------------|-------------------------------------------------------------------------| -| MultiNode | Abstract class | A node that has a tuple result | | A tuple | -| Start | Control | Start of function, now a MultiNode | An input argument named `arg`. | A tuple with a ctrl token and an `arg` data node | -| Proj | ? | Projection nodes extract values from MultiNode | A MultiNode and index | Result is the extracted value from the input MultiNode at offset index | -| Bool | Data | Represents results of a comparison operator | Two data nodes | Result a comparison, represented as integer value where 1=true, 0=false | +| Node Name | Type | Description | Inputs | Value | +|-----------|----------------|------------------------------------------------|-----------------------|----------------------------------------------------------------------------| +| MultiNode | Abstract class | A node that has a tuple result | | A tuple | +| Start | Control | Start of function, now a MultiNode | | A tuple with a ctrl token and an `arg` data node | +| Proj | ? | Projection nodes extract values from MultiNode | A MultiNode and index | Result is the extracted value from the input MultiNode at offset index | +| Bool | Data | Represents results of a comparison operator | Two data nodes | Result is a comparison, represented as integer value where 1=true, 0=false | Below is our list of Nodes from [Chapter 3](../chapter03/README.md): @@ -116,13 +116,18 @@ In previous chapters, we had a hard coded control input edge from Start to Return. In this chapter we no longer have such a hard-wired edge. Instead, we track the current in-scope control node via the name `$ctrl`. This means that when we need to create an edge to the predecessor control node, we simply -lookup this name in the current scope. +look up this name in the current scope. -This introduces the idea that the control flow subgraph is a Petri net model [[1]](#1). +This introduces the idea that the control flow subgraph is a Petri net model.[^1] The control token moves virtually from node to node as execution proceeds. The initial control token is in Start, it then moves via the Proj node to Return. In later chapters we will see how the token moves across branches. +## Note Regarding Visualizations + +From this chapter onwards we omit the edges from Constants to Start node, mainly to reduce +clutter and help draw reader's attention to the more important aspects of the graph. + ## More Peephole Optimizations Now that we have non-constant integer values, we do additional optimizations, rearranging algebraic @@ -246,7 +251,5 @@ The reason this is done is that we know `m` is the new replacement and is alive, but since it is not yet part of the graph up the tree, it has no users yet. By adding a bogus user we prevent it being mistaken for dead and being killed. -## References -[1] -Click, C. (1995). -Combining Analyses, Combining Optimizations, 131. +[^1]: Click, C. (1995). + Combining Analyses, Combining Optimizations, 131. diff --git a/chapter04/docs/04-post-peephole.gv b/chapter04/docs/04-post-peephole.gv index 22f55468..51e176bc 100644 --- a/chapter04/docs/04-post-peephole.gv +++ b/chapter04/docs/04-post-peephole.gv @@ -3,8 +3,10 @@ digraph chapter04 { return 1+arg+2; #showGraph; */ rankdir=BT; + ordering="in"; + concentrate="true"; subgraph cluster_Nodes { - Start1 [ shape=plaintext label=< + Start2 [ shape=plaintext label=<

").append(level).append("
").append(level).append("").append(name).append("
Start
@@ -14,28 +16,28 @@ return 1+arg+2; #showGraph;
> ]; - Con_9 [ label="#3" ]; - Add10 [ label="Add" ]; - Return11 [ shape=box style=filled fillcolor=yellow label="Return" ]; + Con_10 [ label="#3" ]; + Add11 [ label="+" ]; + Return12 [ shape=box style=filled fillcolor=yellow label="Return" ]; } node [shape=plaintext]; - subgraph cluster_0 { - scope0 [label=< + subgraph cluster_Scope1_0 { + Scope1_0 [label=< - +
0$ctrlarg
0$ctrlarg
>]; - subgraph cluster_1 { - scope1 [label=< + subgraph cluster_Scope1_1 { + Scope1_1 [label=<
1
>]; } } - Add10 -> Start1:p1; - Add10 -> Con_9; - Return11 -> Start1:p0 [color=red]; - Return11 -> Add10; + edge [ fontname=Helvetica, fontsize=8 ]; + Add11 -> Start2:p1[taillabel=1]; + Add11 -> Con_10[taillabel=2]; + Return12 -> Start2:p0[taillabel=0; color=red]; + Return12 -> Add11[taillabel=1]; edge [style=dashed color=cornflowerblue]; - scope0:"scope0_$ctrl" -> Start1:p0; - scope0:"scope0_arg" -> Start1:p1; + Scope1_0:"Scope1_0_arg" -> Start2:p1; } \ No newline at end of file diff --git a/chapter04/docs/04-post-peephole.svg b/chapter04/docs/04-post-peephole.svg index 5a45722c..550c16ef 100644 --- a/chapter04/docs/04-post-peephole.svg +++ b/chapter04/docs/04-post-peephole.svg @@ -14,16 +14,16 @@ -cluster_0 +cluster_Scope1_0 -cluster_1 +cluster_Scope1_1 - + -Start1 +Start2 Start @@ -34,51 +34,55 @@ arg - + -Con_9 +Con_10 #3 - + -Add10 - -Add +Add11 + ++ - + -Add10->Start1:p1 - - +Add11->Start2:p1 + + +1 - + -Add10->Con_9 - - +Add11->Con_10 + + +2 - + -Return11 - -Return +Return12 + +Return - + -Return11->Start1:p0 - - +Return12->Start2:p0 + + +0 - + -Return11->Add10 - - +Return12->Add11 + + +1 - + -scope0 +Scope1_0 0 @@ -87,21 +91,15 @@ arg - + -scope0:scope0_$ctrl->Start1:p0 - - - - - -scope0:scope0_arg->Start1:p1 +Scope1_0:Scope1_0_arg->Start2:p1 - + -scope1 +Scope1_1 1 diff --git a/chapter04/docs/04-pre-peephole.gv b/chapter04/docs/04-pre-peephole.gv index e8016c87..b6fefc2d 100644 --- a/chapter04/docs/04-pre-peephole.gv +++ b/chapter04/docs/04-pre-peephole.gv @@ -3,8 +3,10 @@ digraph chapter04 { return 1+arg+2; #showGraph; */ rankdir=BT; + ordering="in"; + concentrate="true"; subgraph cluster_Nodes { - Start1 [ shape=plaintext label=< + Start2 [ shape=plaintext label=<
Start
@@ -14,32 +16,32 @@ return 1+arg+2; #showGraph;
> ]; - Con_4 [ label="#1" ]; - Con_5 [ label="#2" ]; - Add6 [ label="Add" ]; - Add7 [ label="Add" ]; - Return8 [ shape=box style=filled fillcolor=yellow label="Return" ]; + Con_5 [ label="#1" ]; + Con_6 [ label="#2" ]; + Add7 [ label="+" ]; + Add8 [ label="+" ]; + Return9 [ shape=box style=filled fillcolor=yellow label="Return" ]; } node [shape=plaintext]; - subgraph cluster_0 { - scope0 [label=< + subgraph cluster_Scope1_0 { + Scope1_0 [label=< - +
0$ctrlarg
0$ctrlarg
>]; - subgraph cluster_1 { - scope1 [label=< + subgraph cluster_Scope1_1 { + Scope1_1 [label=<
1
>]; } } - Add6 -> Start1:p1; - Add6 -> Con_5; - Add7 -> Con_4; - Add7 -> Add6; - Return8 -> Start1:p0 [color=red]; - Return8 -> Add7; + edge [ fontname=Helvetica, fontsize=8 ]; + Add7 -> Start2:p1[taillabel=1]; + Add7 -> Con_6[taillabel=2]; + Add8 -> Con_5[taillabel=1]; + Add8 -> Add7[taillabel=2]; + Return9 -> Start2:p0[taillabel=0; color=red]; + Return9 -> Add8[taillabel=1]; edge [style=dashed color=cornflowerblue]; - scope0:"scope0_$ctrl" -> Start1:p0; - scope0:"scope0_arg" -> Start1:p1; -} + Scope1_0:"Scope1_0_arg" -> Start2:p1; +} \ No newline at end of file diff --git a/chapter04/docs/04-pre-peephole.svg b/chapter04/docs/04-pre-peephole.svg index b1ab4eee..9ec38d27 100644 --- a/chapter04/docs/04-pre-peephole.svg +++ b/chapter04/docs/04-pre-peephole.svg @@ -14,16 +14,16 @@
-cluster_0 +cluster_Scope1_0 -cluster_1 +cluster_Scope1_1 - + -Start1 +Start2 Start @@ -34,75 +34,81 @@ arg - + -Con_4 - -#1 +Con_5 + +#1 - + -Con_5 +Con_6 #2 - + -Add6 - -Add +Add7 + ++ - + -Add6->Start1:p1 - - +Add7->Start2:p1 + + +1 - + -Add6->Con_5 - - +Add7->Con_6 + + +2 - + -Add7 - -Add +Add8 + ++ - + -Add7->Con_4 - - +Add8->Con_5 + + +1 - + -Add7->Add6 - - +Add8->Add7 + + +2 - + -Return8 - -Return +Return9 + +Return - + -Return8->Start1:p0 - - +Return9->Start2:p0 + + +0 - + -Return8->Add7 - - +Return9->Add8 + + +1 - + -scope0 +Scope1_0 0 @@ -111,21 +117,15 @@ arg - + -scope0:scope0_$ctrl->Start1:p0 - - - - - -scope0:scope0_arg->Start1:p1 +Scope1_0:Scope1_0_arg->Start2:p1 - + -scope1 +Scope1_1 1 diff --git a/chapter04/src/main/java/com/seaofnodes/simple/GraphVisualizer.java b/chapter04/src/main/java/com/seaofnodes/simple/GraphVisualizer.java index f73bfd3c..f624cbde 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/GraphVisualizer.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/GraphVisualizer.java @@ -1,7 +1,6 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.node.*; -import com.seaofnodes.simple.type.TypeControl; import java.util.*; @@ -29,6 +28,13 @@ public class GraphVisualizer { // scopes and nodes. If we try to cross subgraph cluster borders while // still making the subgraphs DOT gets confused. sb.append("\trankdir=BT;\n"); // Force Nodes before Scopes + + // Preserve node input order + sb.append("\tordering=\"in\";\n"); + + // Merge multiple edges hitting the same node. Makes common shared + // nodes much prettier to look at. + sb.append("\tconcentrate=\"true\";\n"); // Just the Nodes first, in a cluster no edges nodes(sb, all); @@ -51,10 +57,10 @@ public class GraphVisualizer { // Just the Nodes first, in a cluster no edges sb.append("\tsubgraph cluster_Nodes {\n"); // Magic "cluster_" in the subgraph name for( Node n : all ) { - if( n instanceof ProjNode ) - continue; // Do not emit, rolled into MultiNode already + if( n instanceof ProjNode || n instanceof ScopeNode ) + continue; // Do not emit, rolled into MultiNode or Scope cluster already sb.append("\t\t").append(n.uniqueName()).append(" [ "); - String lab = n.label(); + String lab = n.glabel(); if( n instanceof MultiNode ) { // Make a box with the MultiNode on top, and all the projections on the bottom sb.append("shape=plaintext label=<\n"); @@ -71,8 +77,8 @@ public class GraphVisualizer { sb.append("\t\t\t\t"); } sb.append("").append(proj.label()).append(""); + if( proj.isCFG() ) sb.append(" BGCOLOR=\"yellow\""); + sb.append(">").append(proj.glabel()).append(""); } } if (doProjTable) { @@ -86,7 +92,7 @@ public class GraphVisualizer { } else { // control nodes have box shape // other nodes are ellipses, i.e. default shape - if( n instanceof Control ) + if( n.isCFG() ) sb.append("shape=box style=filled fillcolor=yellow "); sb.append("label=\"").append(lab).append("\" "); } @@ -99,8 +105,8 @@ public class GraphVisualizer { sb.append("\tnode [shape=plaintext];\n"); int level=0; for( HashMap scope : scopenode._scopes ) { - sb.append("\tsubgraph cluster_").append(level).append(" {\n"); // Magic "cluster_" in the subgraph name - String scopeName = makeScopeName(level); + String scopeName = makeScopeName(scopenode, level); + sb.append("\tsubgraph cluster_").append(scopeName).append(" {\n"); // Magic "cluster_" in the subgraph name sb.append("\t\t").append(scopeName).append(" [label=<\n"); sb.append("\t\t\t\n"); // Add the scope level @@ -116,31 +122,38 @@ public class GraphVisualizer { // We close them all at once here. sb.append( "\t}\n".repeat( level ) ); // End all Scope clusters } - - private String makeScopeName(int level) { return "scope" + level; } - private String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; } - private boolean isControlEdge(Node def) { - return def._type instanceof TypeControl; - } + private String makeScopeName(ScopeNode sn, int level) { return sn.uniqueName() + "_" + level; } + private String makePortName(String scopeName, String varName) { return scopeName + "_" + varName; } // Walk the node edges private void nodeEdges(StringBuilder sb, Collection all) { + // All them edge labels + sb.append("\tedge [ fontname=Helvetica, fontsize=8 ];\n"); for( Node n : all ) { - if( n instanceof ConstantNode || n instanceof ProjNode ) - continue; // Do not display the Constant->Start edge; ProjNodes handled by Multi - for( Node def : n._inputs ) + // Do not display the Constant->Start edge; + // ProjNodes handled by Multi; + // ScopeNodes are done separately + if( n instanceof ConstantNode || n instanceof ProjNode || n instanceof ScopeNode ) + continue; + int i=0; + for( Node def : n._inputs ) { if( def != null ) { + // Most edges land here use->def sb.append('\t').append(n.uniqueName()).append(" -> "); if( def instanceof ProjNode proj ) { String mname = proj.ctrl().uniqueName(); sb.append(mname).append(":p").append(proj._idx); } else sb.append(def.uniqueName()); - // Control edges are colored red - if( isControlEdge(def) ) - sb.append(" [color=red]"); - sb.append(";\n"); + // Number edges, so we can see how they track + sb.append("[taillabel=").append(i); + // control edges are colored red + if( def.isCFG() ) + sb.append(" color=red"); + sb.append("];\n"); } + i++; + } } } @@ -149,7 +162,7 @@ public class GraphVisualizer { sb.append("\tedge [style=dashed color=cornflowerblue];\n"); int level=0; for( HashMap scope : scopenode._scopes ) { - String scopeName = makeScopeName(level); + String scopeName = makeScopeName(scopenode, level); for( String name : scope.keySet() ) { Node def = scopenode.in(scope.get(name)); if( def==null ) continue; diff --git a/chapter04/src/main/java/com/seaofnodes/simple/Parser.java b/chapter04/src/main/java/com/seaofnodes/simple/Parser.java index 275a4ebd..b505c2c5 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/Parser.java @@ -14,6 +14,12 @@ import java.util.*; */ public class Parser { + /** + * The control is a name that binds to the currently active control + * node in the graph + */ + public static final String CTRL = "$ctrl"; + /** * A Global Static, unique to each compilation. This is a public, so we * can make constants everywhere without having to thread the StartNode @@ -23,6 +29,7 @@ public class Parser { */ public static StartNode START; + // The Lexer. Thin wrapper over a byte[] buffer with a cursor. private final Lexer _lexer; /** @@ -31,10 +38,19 @@ public class Parser { */ public ScopeNode _scope; + /** + * List of keywords disallowed as identifiers + */ + private final HashSet KEYWORDS = new HashSet<>(){{ + add("int"); + add("return"); + }}; + + public Parser(String source, TypeInteger arg) { _lexer = new Lexer(source); - _scope = new ScopeNode(); Node.reset(); + _scope = new ScopeNode(); START = new StartNode(new Type[]{ Type.CONTROL, arg }); START.peephole(); } @@ -45,17 +61,23 @@ public class Parser { String src() { return new String( _lexer._input ); } + private Node ctrl() { return _scope.ctrl(); } + private Node ctrl(Node n) { return _scope.ctrl(n); } - public ReturnNode parse() { + public ReturnNode parse() { return parse(false); } + public ReturnNode parse(boolean show) { _scope.push(); try { - _scope.define("$ctrl", new ProjNode(START, 0, "$ctrl").peephole()); - _scope.define("arg" , new ProjNode(START, 1, "arg" ).peephole()); - return (ReturnNode) parseBlock(false); + _scope.define(CTRL , new ProjNode(START, 0, CTRL ).peephole()); + _scope.define("arg", new ProjNode(START, 1, "arg").peephole()); + var ret = (ReturnNode) parseBlock(); + if (!_lexer.isEOF()) throw error("Syntax error, unexpected " + _lexer.getAnyNextToken()); + return ret; } finally { _scope.pop(); + if( show ) showGraph(); } } @@ -65,23 +87,17 @@ public class Parser { *
      *     '{' statements '}'
      * 
+ * Does not parse the opening or closing '{}' + * @return a {@link Node} or {@code null} */ - private Node parseBlock(boolean requireClosingBracket) { + private Node parseBlock() { // Enter a new scope _scope.push(); Node n = null; - do { - if (match("}")) { - if (requireClosingBracket) break; - else throw error("unexpected '}' token"); - } else if (_lexer.isEOF()) { - if (requireClosingBracket) require("}"); - else break; - } - + while (!peek('}') && !_lexer.isEOF()) { Node n0 = parseStatement(); if (n0 != null) n = n0; // Allow null returns from eg showGraph - } while (true); + }; // Exit scope _scope.pop(); return n; @@ -93,12 +109,13 @@ public class Parser { *
      *     returnStatement | declStatement | blockStatement | expressionStatement
      * 
+ * @return a {@link Node} or {@code null} */ private Node parseStatement() { - if (match("return")) return parseReturn(); - else if (match("int")) return parseDecl(); - else if (match("{")) return parseBlock(true); - else if (match("#showGraph")) return showGraph(); + if (matchx("return") ) return parseReturn(); + else if (matchx("int")) return parseDecl(); + else if (match ("{" )) return require(parseBlock(),"}"); + else if (matchx("#showGraph")) return require(showGraph(),";"); else return parseExpressionStatement(); } @@ -109,19 +126,20 @@ public class Parser { *
      *     'return' expr ;
      * 
+ * @return an expression {@link Node}, never {@code null} */ private Node parseReturn() { var expr = require(parseExpression(), ";"); - Node ret = new ReturnNode(_scope.lookup("$ctrl"), expr); - _scope.update("$ctrl",null); + Node ret = new ReturnNode(ctrl(), expr).peephole(); + ctrl(null); // Kill control return ret; } /** * Dumps out the node graph + * @return {@code null} */ private Node showGraph() { - require(";"); System.out.println(new GraphVisualizer().generateDotOutput(this)); return null; } @@ -132,6 +150,7 @@ public class Parser { *
      *     name '=' expression ';'
      * 
+ * @return an expression {@link Node}, never {@code null} */ private Node parseExpressionStatement() { var name = requireId(); @@ -148,6 +167,7 @@ public class Parser { *
      *     'int' name = expression ';'
      * 
+ * @return an expression {@link Node}, never {@code null} */ private Node parseDecl() { // Type is 'int' for now @@ -163,11 +183,20 @@ public class Parser { * Parse an expression of the form: * *
-     *     expr : additiveExpr
+     *     expr : compareExpr
      * 
+ * @return an expression {@link Node}, never {@code null} */ private Node parseExpression() { return parseComparison(); } + /** + * Parse an expression of the form: + * + *
+     *     expr : additiveExpr op additiveExpr
+     * 
+ * @return an comparator expression {@link Node}, never {@code null} + */ private Node parseComparison() { var lhs = parseAddition(); if (match("==")) return new BoolNode.EQNode(lhs, parseComparison()).peephole(); @@ -185,6 +214,7 @@ public class Parser { *
      *     additiveExpr : multiplicativeExpr (('+' | '-') multiplicativeExpr)*
      * 
+ * @return an add expression {@link Node}, never {@code null} */ private Node parseAddition() { var lhs = parseMultiplication(); @@ -199,6 +229,7 @@ public class Parser { *
      *     multiplicativeExpr : unaryExpr (('*' | '/') unaryExpr)*
      * 
+ * @return a multiply expression {@link Node}, never {@code null} */ private Node parseMultiplication() { var lhs = parseUnary(); @@ -213,6 +244,7 @@ public class Parser { *
      *     unaryExpr : ('-') unaryExpr | primaryExpr
      * 
+ * @return a unary expression {@link Node}, never {@code null} */ private Node parseUnary() { if (match("-")) return new MinusNode(parseUnary()).peephole(); @@ -225,15 +257,16 @@ public class Parser { *
      *     primaryExpr : integerLiteral | Identifier | '(' expression ')'
      * 
+ * @return a primary {@link Node}, never {@code null} */ private Node parsePrimary() { - if (_lexer.isNumber()) return parseIntegerLiteral(); - if (match("(")) return require(parseExpression(), ")"); - String name = requireId(); - Node id = _scope.lookup(name); - if( id==null ) - throw error("Undefined name '" + name + "'"); - return id.peephole(); + if( _lexer.isNumber() ) return parseIntegerLiteral(); + if( match("(") ) return require(parseExpression(), ")"); + String name = _lexer.matchId(); + if( name == null) throw errorSyntax("an identifier or expression"); + Node n = _scope.lookup(name); + if( n!=null ) return n; + throw error("Undefined name '" + name + "'"); } /** @@ -251,13 +284,17 @@ public class Parser { // Utilities for lexical analysis // Return true and skip if "syntax" is next in the stream. - private boolean match(String syntax) { return _lexer.match(syntax); } + private boolean match (String syntax) { return _lexer.match (syntax); } + // Match must be "exact", not be followed by more id letters + private boolean matchx(String syntax) { return _lexer.matchx(syntax); } + // Return true and do NOT skip if 'ch' is next + private boolean peek(char ch) { return _lexer.peek(ch); } // Require and return an identifier private String requireId() { String id = _lexer.matchId(); - if (id != null) return id; - throw error("identifier"); + if (id != null && !KEYWORDS.contains(id) ) return id; + throw error("Expected an identifier, found '"+id+"'"); } // Require an exact match @@ -343,11 +380,24 @@ public class Parser { int len = syntax.length(); if (_position + len > _input.length) return false; for (int i = 0; i < len; i++) - if ((char) _input[_position + i] != syntax.charAt(i)) return false; + if ((char) _input[_position + i] != syntax.charAt(i)) + return false; _position += len; return true; } + boolean matchx(String syntax) { + if( !match(syntax) ) return false; + if( !isIdLetter(peek()) ) return true; + _position -= syntax.length(); + return false; + } + + private boolean peek(char ch) { + skipWhiteSpace(); + return peek()==ch; + } + // Return an identifier or null String matchId() { skipWhiteSpace(); @@ -397,8 +447,7 @@ public class Parser { private String parsePunctuation() { int start = _position; - if (isPunctuation(nextChar())) ; - return new String(_input, start, --_position - start); + return new String(_input, start, 1); } } diff --git a/chapter04/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter04/src/main/java/com/seaofnodes/simple/node/AddNode.java index 8b157a9c..d4d15bef 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -3,12 +3,11 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.type.*; public class AddNode extends Node { - public AddNode(Node lhs, Node rhs) { - super(null, lhs, rhs); - } + public AddNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - @Override - public String label() { return "Add"; } + @Override public String label() { return "Add"; } + + @Override public String glabel() { return "+"; } @Override StringBuilder _print1(StringBuilder sb) { @@ -36,37 +35,64 @@ public class AddNode extends Node { Type t1 = lhs._type; Type t2 = rhs._type; + // Already handled by peephole constant folding + assert !(t1.isConstant() && t2.isConstant()); + // Add of 0. We do not check for (0+x) because this will already // canonicalize to (x+0) - if ( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 ) + if( t2 instanceof TypeInteger i && i.value()==0 ) return lhs; - - // Move constants to RHS: con+arg becomes arg+con - if ( t1.isConstant() && !t2.isConstant() ) { - Node tmp = in(1); // Swap inputs without letting either input go dead during the swap - _inputs.set(1,in(2)); - _inputs.set(2,tmp); - return this; - } - + + // Add of same to a multiply by 2 + if( lhs==rhs ) + return new MulNode(lhs,new ConstantNode(TypeInteger.constant(2)).peephole()); + // Goal: a left-spine set of adds, with constants on the rhs (which then fold). - // Do we have (x + con1) + con2 ? - // Rotate to x +(con1 + con2) - // The constants will fold on the next peephole. - if (t2.isConstant() && lhs instanceof AddNode && lhs.in(2)._type.isConstant() ) - return new AddNode(lhs.in(1),new AddNode(lhs.in(2),rhs).peephole()); + // Move non-adds to RHS + if( !(lhs instanceof AddNode) && rhs instanceof AddNode ) + return swap12(); - // Do we have x + (y + con) ? - // Swap to (x + y) + con - if (rhs instanceof AddNode add && add.in(2)._type.isConstant() ) - return new AddNode(new AddNode(lhs,add.in(1)).peephole(), add.in(2)); + // Now we might see (add add non) or (add non non) or (add add add) but never (add non add) - // Add of same to a multiply by 2 - if( lhs==rhs ) - return new MulNode(lhs,new ConstantNode(TypeInteger.constant(2)).peephole()); + // Do we have x + (y + z) ? + // Swap to (x + y) + z + // Rotate (add add add) to remove the add on RHS + if( rhs instanceof AddNode add ) + return new AddNode(new AddNode(lhs,add.in(1)).peephole(), add.in(2)); + + // Now we might see (add add non) or (add non non) but never (add non add) nor (add add add) + if( !(lhs instanceof AddNode) ) + return spline_cmp(lhs,rhs) ? swap12() : null; + + // Now we only see (add add non) + + // Do we have (x + con1) + con2? + // Replace with (x + (con1+con2) which then fold the constants + if( lhs.in(2)._type.isConstant() && t2.isConstant() ) + return new AddNode(lhs.in(1),new AddNode(lhs.in(2),rhs).peephole()); + + // Now we sort along the spline via rotates, to gather similar things together. + + // Do we rotate (x + y) + z + // into (x + z) + y ? + if( spline_cmp(lhs.in(2),rhs) ) + return new AddNode(new AddNode(lhs.in(1),rhs).peephole(),lhs.in(2)); return null; } + + // Compare two off-spline nodes and decide what order they should be in. + // Do we rotate ((x + hi) + lo) into ((x + lo) + hi) ? + // Generally constants always go right, then others. + // Ties with in a category sort by node ID. + // TRUE if swapping hi and lo. + static boolean spline_cmp( Node hi, Node lo ) { + if( lo._type.isConstant() ) return false; + if( hi._type.isConstant() ) return true ; + + // Same category of "others" + return lo._nid > hi._nid; + } } diff --git a/chapter04/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter04/src/main/java/com/seaofnodes/simple/node/BoolNode.java index b0e0bf42..02eb7ce9 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -13,7 +13,10 @@ abstract public class BoolNode extends Node { abstract String op(); // String opcode name @Override - public String label() { return op(); } + public String label() { return getClass().getSimpleName(); } + + @Override + public String glabel() { return op(); } @Override StringBuilder _print1(StringBuilder sb) { diff --git a/chapter04/src/main/java/com/seaofnodes/simple/node/Control.java b/chapter04/src/main/java/com/seaofnodes/simple/node/Control.java deleted file mode 100644 index 48cf2b7a..00000000 --- a/chapter04/src/main/java/com/seaofnodes/simple/node/Control.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.seaofnodes.simple.node; - - -/** - * Control interface is a marker interface to be implemented - * by Control nodes. - */ -public interface Control { -} diff --git a/chapter04/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter04/src/main/java/com/seaofnodes/simple/node/DivNode.java index 1f562378..ae87deac 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -5,13 +5,12 @@ import com.seaofnodes.simple.type.TypeBot; import com.seaofnodes.simple.type.TypeInteger; public class DivNode extends Node { - public DivNode(Node lhs, Node rhs) { - super(null, lhs, rhs); - } + public DivNode(Node lhs, Node rhs) { super(null, lhs, rhs); } + + @Override public String label() { return "Div"; } + + @Override public String glabel() { return "//"; } - @Override - public String label() { return "Div"; } - @Override StringBuilder _print1(StringBuilder sb) { in(1)._print0(sb.append("(")); diff --git a/chapter04/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter04/src/main/java/com/seaofnodes/simple/node/MinusNode.java index 04084d0f..533155d9 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -5,12 +5,11 @@ import com.seaofnodes.simple.type.TypeBot; import com.seaofnodes.simple.type.TypeInteger; public class MinusNode extends Node { - public MinusNode(Node in) { - super(null, in); - } + public MinusNode(Node in) { super(null, in); } - @Override - public String label() { return "Minus"; } + @Override public String label() { return "Minus"; } + + @Override public String glabel() { return "-"; } @Override StringBuilder _print1(StringBuilder sb) { @@ -21,7 +20,7 @@ public class MinusNode extends Node { @Override public Type compute() { if (in(1)._type instanceof TypeInteger i0) - return TypeInteger.constant(-i0.value()); + return i0.isConstant() ? TypeInteger.constant(-i0.value()) : i0; return TypeBot.BOTTOM; } diff --git a/chapter04/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter04/src/main/java/com/seaofnodes/simple/node/MulNode.java index e3e83b38..5f6c1ad8 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -5,12 +5,11 @@ import com.seaofnodes.simple.type.TypeBot; import com.seaofnodes.simple.type.TypeInteger; public class MulNode extends Node { - public MulNode(Node lhs, Node rhs) { - super(null, lhs, rhs); - } + public MulNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - @Override - public String label() { return "Mul"; } + @Override public String label() { return "Mul"; } + + @Override public String glabel() { return "*"; } @Override StringBuilder _print1(StringBuilder sb) { @@ -43,12 +42,8 @@ public class MulNode extends Node { return lhs; // Move constants to RHS: con*arg becomes arg*con - if ( t1.isConstant() && !t2.isConstant() ) { - Node tmp = in(1); // Swap inputs without letting either input go dead during the swap - _inputs.set(1,in(2)); - _inputs.set(2,tmp); - return this; - } + if ( t1.isConstant() && !t2.isConstant() ) + return swap12(); return null; } diff --git a/chapter04/src/main/java/com/seaofnodes/simple/node/MultiNode.java b/chapter04/src/main/java/com/seaofnodes/simple/node/MultiNode.java index e517c3d5..fe3fddf8 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/node/MultiNode.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/node/MultiNode.java @@ -4,10 +4,6 @@ import com.seaofnodes.simple.type.Type; public abstract class MultiNode extends Node { - public MultiNode(Node... inputs) { - super(inputs); - } - - // Return the ProjNode with the idx - abstract ProjNode proj(int idx); + public MultiNode(Node... inputs) { super(inputs); } + } diff --git a/chapter04/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter04/src/main/java/com/seaofnodes/simple/node/Node.java index 7cc9291c..804d30e0 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/node/Node.java @@ -2,8 +2,7 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.type.Type; -import java.util.ArrayList; -import java.util.Collections; +import java.util.*; /** * All Nodes in the Sea of Nodes IR inherit from the Node class. @@ -24,8 +23,7 @@ public abstract class Node { *

* Generally fixed length, ordered, nulls allowed, no unused trailing space. * Ordering is required because e.g. "a/b" is different from "b/a". - * The first input (offset 0) is often a Control node. - * @see Control + * The first input (offset 0) is often a {@link #isCFG} node. */ public final ArrayList _inputs; @@ -61,13 +59,18 @@ public abstract class Node { _outputs = new ArrayList<>(); for( Node n : _inputs ) if( n != null ) - n._outputs.add( this ); + n.addUse( this ); } + // Easy reading label for debugger, e.g. "Add" or "Region" or "EQ" public abstract String label(); + // Unique label for graph visualization, e.g. "Add12" or "Region30" or "EQ99" public String uniqueName() { return label() + _nid; } + // Graphical label, e.g. "+" or "Region" or "==" + public String glabel() { return label(); } + @Override public final String toString() { // TODO: This needs a lot of work @@ -101,24 +104,18 @@ public abstract class Node { public int nIns() { return _inputs.size(); } - /** - * Gets the ith output node - * @param i Offset of the output node - * @return Output node (not null) - */ - public Node out(int i) { return _outputs.get(i); } - public int nOuts() { return _outputs.size(); } public boolean isUnused() { return nOuts() == 0; } + public boolean isCFG() { return false; } + /** * We allow disabling peephole opt so that we can observe the * full graph, vs the optimized graph. */ public static boolean _disablePeephole = false; - /** * Try to peephole at this node and return a better replacement Node if * possible. We compute a {@link Type} and then check and replace: @@ -197,7 +194,7 @@ public abstract class Node { // This needs to happen before removing the old node's def->use edge as // the new_def might get killed if the old node kills it recursively. if( new_def != null ) - new_def._outputs.add(this); + new_def.addUse(this); if( old_def != null && // If the old def exists, remove a def->use edge old_def.delUse(this) ) // If we removed the last use, the old def is now dead old_def.kill(); // Kill old def @@ -219,11 +216,10 @@ public abstract class Node { _inputs.add(new_def); // If new def is not null, add the corresponding def->use edge if( new_def != null ) - new_def._outputs.add(this); + new_def.addUse(this); return new_def; } - // Breaks the edge invariants, used temporarily private void addUse(Node n) { _outputs.add(n); } @@ -266,7 +262,7 @@ public abstract class Node { // Mostly used for asserts and printing. boolean isDead() { return isUnused() && nIns()==0 && _type==null; } - + /** * This function needs to be * Monotonic @@ -332,14 +328,21 @@ public abstract class Node { */ public abstract Node idealize(); + + // ----------------------- + // Peephole utilities + + // Swap inputs without letting either input go dead during the swap. + Node swap12() { + Node tmp = in(1); + _inputs.set(1,in(2)); + _inputs.set(2,tmp); + return this; + } + /** * Used to allow repeating tests in the same JVM. This just resets the * Node unique id generator, and is done as part of making a new Parser. */ public static void reset() { UNIQUE_ID = 1; } - - /* - * hashCode and equals implementation to be added in later chapter. - */ - } diff --git a/chapter04/src/main/java/com/seaofnodes/simple/node/ProjNode.java b/chapter04/src/main/java/com/seaofnodes/simple/node/ProjNode.java index be37c514..be0556ed 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/node/ProjNode.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/node/ProjNode.java @@ -22,7 +22,9 @@ public class ProjNode extends Node { public String label() { return _label; } @Override - StringBuilder _print1(StringBuilder sb) { return sb.append("Proj_" +_nid); } + StringBuilder _print1(StringBuilder sb) { return sb.append(_label); } + + @Override public boolean isCFG() { return _idx==0; } public MultiNode ctrl() { return (MultiNode)in(0); } diff --git a/chapter04/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter04/src/main/java/com/seaofnodes/simple/node/ReturnNode.java index c94115bb..e20d01e5 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -1,7 +1,6 @@ package com.seaofnodes.simple.node; -import com.seaofnodes.simple.type.Type; -import com.seaofnodes.simple.type.TypeBot; +import com.seaofnodes.simple.type.*; /** * The Return node has two inputs. The first input is a control node and the @@ -12,7 +11,7 @@ import com.seaofnodes.simple.type.TypeBot; *

* The Return's output is the value from the data node. */ -public class ReturnNode extends Node implements Control { +public class ReturnNode extends Node { public ReturnNode(Node ctrl, Node data) { super(ctrl, data); @@ -29,10 +28,11 @@ public class ReturnNode extends Node implements Control { return expr()._print0(sb.append("return ")).append(";"); } + @Override public boolean isCFG() { return true; } @Override public Type compute() { - return expr().compute(); + return new TypeTuple(TypeControl.CONTROL,expr()._type); } @Override diff --git a/chapter04/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter04/src/main/java/com/seaofnodes/simple/node/ScopeNode.java index 8bd81e82..45966791 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.type.TypeBot; import java.util.*; @@ -16,12 +17,15 @@ public class ScopeNode extends Node { * The top of this stack represents current scope. */ public final Stack> _scopes = new Stack<>(); - - @Override - public String label() { return "Scope"; } + + // Reverse lookup for nicer graph printing + private final ArrayList _rlabels = new ArrayList<>(); + + @Override public String label() { return "Scope"; } @Override StringBuilder _print1(StringBuilder sb) { + sb.append(label()); for( HashMap scope : _scopes ) { sb.append("["); boolean first=true; @@ -41,12 +45,11 @@ public class ScopeNode extends Node { @Override public TypeBot compute() { return TypeBot.BOTTOM; } - @Override - public Node idealize() { return null; } + @Override public Node idealize() { return null; } // Add an empty lexical scope - public void push() { - _scopes.push(new HashMap<>()); + public HashMap push() { + return _scopes.push(new HashMap<>()); } // Remove the current lexical scope, killing all unused nodes. @@ -60,6 +63,7 @@ public class ScopeNode extends Node { HashMap scope = _scopes.lastElement(); if( scope.put(name,nIns()) != null ) return null; // Double define + _rlabels.add(name); return add_def(n); } @@ -72,7 +76,15 @@ public class ScopeNode extends Node { return null; } + public Node ctrl() { assert lookup(Parser.CTRL)==in(0); return in(0); } + + public Node ctrl(Node n) { + assert lookup(Parser.CTRL)==in(0); + set_def(0,n); + return n; + } + // If the name is present in any scope, then redefine public Node update(String name, Node n) { for (int i = _scopes.size() - 1; i >= 0; i--) { @@ -84,5 +96,4 @@ public class ScopeNode extends Node { return null; } - } diff --git a/chapter04/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter04/src/main/java/com/seaofnodes/simple/node/StartNode.java index b59e3375..91573de8 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -11,7 +11,7 @@ import com.seaofnodes.simple.type.TypeTuple; * will require Projections to extract the values. We discuss this in detail * in Chapter 9: Functions and Calls. */ -public class StartNode extends MultiNode implements Control { +public class StartNode extends MultiNode { final TypeTuple _args; @@ -27,20 +27,12 @@ public class StartNode extends MultiNode implements Control { StringBuilder _print1(StringBuilder sb) { return sb.append(label()); } - + + @Override public boolean isCFG() { return true; } + @Override public TypeTuple compute() { return _args; } @Override public Node idealize() { return null; } - - - @Override - public ProjNode proj( int idx ) { - for( Node proj : _outputs ) - if( ((ProjNode)proj)._idx==idx ) - return (ProjNode)proj; - return null; - } - } diff --git a/chapter04/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter04/src/main/java/com/seaofnodes/simple/node/SubNode.java index e5939e1b..42ddc1c1 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -5,12 +5,11 @@ import com.seaofnodes.simple.type.TypeBot; import com.seaofnodes.simple.type.TypeInteger; public class SubNode extends Node { - public SubNode(Node lhs, Node rhs) { - super(null, lhs, rhs); - } + public SubNode(Node lhs, Node rhs) { super(null, lhs, rhs); } - @Override - public String label() { return "Sub"; } + @Override public String label() { return "Sub"; } + + @Override public String glabel() { return "-"; } @Override StringBuilder _print1(StringBuilder sb) { diff --git a/chapter04/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/chapter04/src/main/java/com/seaofnodes/simple/type/TypeInteger.java index b53b1b29..63c46035 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/type/TypeInteger.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/type/TypeInteger.java @@ -26,7 +26,7 @@ public class TypeInteger extends Type { @Override public String toString() { - return _print(new StringBuilder()).toString(); + return _print(new StringBuilder()).toString(); } @Override diff --git a/chapter04/src/main/java/com/seaofnodes/simple/type/TypeTuple.java b/chapter04/src/main/java/com/seaofnodes/simple/type/TypeTuple.java index ef87a415..036147a2 100644 --- a/chapter04/src/main/java/com/seaofnodes/simple/type/TypeTuple.java +++ b/chapter04/src/main/java/com/seaofnodes/simple/type/TypeTuple.java @@ -1,10 +1,10 @@ package com.seaofnodes.simple.type; public class TypeTuple extends Type { - + public final Type[] _types; - public TypeTuple(Type[] _types) { + public TypeTuple(Type... _types) { this._types = _types; } diff --git a/chapter04/src/test/java/com/seaofnodes/simple/Chapter04Test.java b/chapter04/src/test/java/com/seaofnodes/simple/Chapter04Test.java index ed999039..ebfe1399 100644 --- a/chapter04/src/test/java/com/seaofnodes/simple/Chapter04Test.java +++ b/chapter04/src/test/java/com/seaofnodes/simple/Chapter04Test.java @@ -15,44 +15,44 @@ public class Chapter04Test { @Test public void testChapter4Peephole() { - Parser parser = new Parser("return 1+arg+2; #showGraph;", TypeInteger.BOT); + Parser parser = new Parser("return 1+arg+2; #showGraph;"); ReturnNode ret = parser.parse(); - assertEquals("return (Proj_3+3);", ret.print()); + assertEquals("return (arg+3);", ret.print()); } @Test public void testChapter4Peephole2() { - Parser parser = new Parser("return (1+arg)+2;", TypeInteger.BOT); + Parser parser = new Parser("return (1+arg)+2;"); ReturnNode ret = parser.parse(); - assertEquals("return (Proj_3+3);", ret.print()); + assertEquals("return (arg+3);", ret.print()); } @Test public void testChapter4Add0() { - Parser parser = new Parser("return 0+arg;", TypeInteger.BOT); + Parser parser = new Parser("return 0+arg;"); ReturnNode ret = parser.parse(); - assertEquals("return Proj_3;", ret.print()); + assertEquals("return arg;", ret.print()); } @Test public void testChapter4AddAddMul() { - Parser parser = new Parser("return arg+0+arg;", TypeInteger.BOT); + Parser parser = new Parser("return arg+0+arg;"); ReturnNode ret = parser.parse(); - assertEquals("return (Proj_3*2);", ret.print()); + assertEquals("return (arg*2);", ret.print()); } @Test public void testChapter4Peephole3() { - Parser parser = new Parser("return 1+arg+2+arg+3; #showGraph;", TypeInteger.BOT); + Parser parser = new Parser("return 1+arg+2+arg+3; #showGraph;"); ReturnNode ret = parser.parse(); - assertEquals("return ((Proj_3*2)+6);", ret.print()); + assertEquals("return ((arg*2)+6);", ret.print()); } @Test public void testChapter4Mul1() { - Parser parser = new Parser("return 1*arg;", TypeInteger.BOT); + Parser parser = new Parser("return 1*arg;"); ReturnNode ret = parser.parse(); - assertEquals("return Proj_3;", ret.print()); + assertEquals("return arg;", ret.print()); } @Test @@ -72,53 +72,63 @@ public class Chapter04Test { @Test public void testChapter4CompEq() { - Parser parser = new Parser("return 3==3; #showGraph;", TypeInteger.BOT); + Parser parser = new Parser("return 3==3; #showGraph;"); ReturnNode ret = parser.parse(); assertEquals("return 1;", ret.print()); } @Test public void testChapter4CompEq2() { - Parser parser = new Parser("return 3==4; #showGraph;", TypeInteger.BOT); + Parser parser = new Parser("return 3==4; #showGraph;"); ReturnNode ret = parser.parse(); assertEquals("return 0;", ret.print()); } @Test public void testChapter4CompNEq() { - Parser parser = new Parser("return 3!=3; #showGraph;", TypeInteger.BOT); + Parser parser = new Parser("return 3!=3; #showGraph;"); ReturnNode ret = parser.parse(); assertEquals("return 0;", ret.print()); } @Test public void testChapter4CompNEq2() { - Parser parser = new Parser("return 3!=4; #showGraph;", TypeInteger.BOT); + Parser parser = new Parser("return 3!=4; #showGraph;"); ReturnNode ret = parser.parse(); assertEquals("return 1;", ret.print()); } @Test public void testChapter4Bug1() { - Parser parser = new Parser("int a=arg+1; int b=a; b=1; return a+2; #showGraph;", TypeInteger.BOT); + Parser parser = new Parser("int a=arg+1; int b=a; b=1; return a+2; #showGraph;"); ReturnNode ret = parser.parse(); - assertEquals("return (Proj_3+3);", ret.print()); + assertEquals("return (arg+3);", ret.print()); } @Test public void testChapter4Bug2() { - Parser parser = new Parser("int a=arg+1; a=a; return a; #showGraph;", TypeInteger.BOT); + Parser parser = new Parser("int a=arg+1; a=a; return a; #showGraph;"); ReturnNode ret = parser.parse(); - assertEquals("return (Proj_3+1);", ret.print()); + assertEquals("return (arg+1);", ret.print()); } @Test public void testChapter4Bug3() { - Parser parser = new Parser("int a=arg*2+1; a=a+-1; return a; #showGraph;", TypeInteger.BOT); - ReturnNode ret = parser.parse(); - assertEquals("return (Proj_3*2);", ret.print()); + try { + new Parser("inta=1; return a;").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Undefined name 'inta'",e.getMessage()); + } } + @Test + public void testChapter4Bug4() { + Parser parser = new Parser("return -arg;"); + ReturnNode ret = parser.parse(); + assertEquals("return (-arg);", ret.print()); + } + @Test public void testVarDecl() { Parser parser = new Parser("int a=1; return a;"); @@ -158,12 +168,14 @@ public class Chapter04Test { @Test public void testSelfAssign() { - exceptionRule.expect(RuntimeException.class); - exceptionRule.expectMessage("Undefined name 'a'"); - new Parser("int a=a; return a;").parse(); + try { + new Parser("int a=a; return a;").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Undefined name 'a'",e.getMessage()); + } } - @Test public void testChapter2ParseGrammar() { Node._disablePeephole = true; // disable peephole so we can observe full graph @@ -247,16 +259,22 @@ public class Chapter04Test { @Test public void testBad1() { - exceptionRule.expect(RuntimeException.class); - exceptionRule.expectMessage("Syntax error, expected =: "); - new Parser("ret").parse(); + try { + new Parser("ret").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Syntax error, expected =: ",e.getMessage()); + } } @Test public void testBad2() { - exceptionRule.expect(RuntimeException.class); - exceptionRule.expectMessage("Syntax error: integer values cannot start with '0'"); - new Parser("return 0123;").parse(); + try { + new Parser("return 0123;").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Syntax error: integer values cannot start with '0'",e.getMessage()); + } } @Test @@ -267,9 +285,12 @@ public class Chapter04Test { @Test public void testBad4() { - exceptionRule.expect(RuntimeException.class); - exceptionRule.expectMessage("Syntax error, expected ;:"); - new Parser("return 100").parse(); + try { + new Parser("return 100").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Syntax error, expected ;: ",e.getMessage()); + } } @Test @@ -280,8 +301,22 @@ public class Chapter04Test { @Test public void testBad6() { - exceptionRule.expect(RuntimeException.class); - exceptionRule.expectMessage("Syntax error, expected }:"); - new Parser("int a=1; int b=2; int c=0; { int b=3; c=a+b;").parse(); + try { + new Parser("int a=1; int b=2; int c=0; { int b=3; c=a+b;").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Syntax error, expected }: ",e.getMessage()); + } } + + @Test + public void testBad7() { + try { + new Parser("return 1;}").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Syntax error, unexpected }",e.getMessage()); + } + } + } diff --git a/chapter05/README.md b/chapter05/README.md index 23d936c5..78a8b1fe 100644 --- a/chapter05/README.md +++ b/chapter05/README.md @@ -15,7 +15,7 @@ Here is a recap of the nodes introduced in previous chapters: | Node Name | Type | Chapter | Description | Inputs | Value | |-----------|----------------|---------|------------------------------------------------|-------------------------------------------------------------------------------|----------------------------------------------------------------------------| | Multi | Abstract class | 4 | A node that has a tuple result | | A tuple | -| Start | Control | 1 | Start of function, now a MultiNode | A type for the input argument named `arg`. | A tuple with a ctrl token and an `arg` data node | +| Start | Control | 1 | Start of function, now a MultiNode | | A tuple with a ctrl token and an `arg` data node | | Proj | ? | 4 | Projection nodes extract values from MultiNode | A MultiNode and index | Result is the extracted value from the input MultiNode at offset index | | Bool | Data | 4 | Represents results of a comparison operator | Two data nodes | Result is a comparison, represented as integer value where 1=true, 0=false | | Return | Control | 1 | End of function | Predecessor control node and a data node for the return value of the function | Return value of the function | @@ -112,6 +112,115 @@ This involves following: updated to point to the `Phi` node. 10. Finally, we set the `Region` node as the control token, and discard the duplicated `ScopeNode`. +Here is the implementing code. + +```java +/** + * Parses a statement + * + *

+ *     if ( expression ) statement [else statement]
+ * 
+ * @return a {@link Node}, never {@code null} + */ +private Node parseIf() { + require("("); + // Parse predicate + var pred = require(parseExpression(), ")"); + // IfNode takes current control and predicate + IfNode ifNode = (IfNode)new IfNode(ctrl(), pred).peephole(); + // Setup projection nodes + Node ifT = new ProjNode(ifNode, 0, "True" ).peephole(); + Node ifF = new ProjNode(ifNode, 1, "False").peephole(); + // In if true branch, the ifT proj node becomes the ctrl + // But first clone the scope and set it as current + int ndefs = _scope.nIns(); + ScopeNode fScope = _scope.dup(); // Duplicate current scope + _allScopes.push(fScope); // For graph visualization we need all scopes + + // Parse the true side + ctrl(ifT); // set ctrl token to ifTrue projection + parseStatement(); // Parse true-side + ScopeNode tScope = _scope; + + // Parse the false side + _scope = fScope; // Restore scope, then parse else block if any + ctrl(ifF); // Ctrl token is now set to ifFalse projection + if (matchx("else")) parseStatement(); + + if( tScope.nIns() != ndefs || fScope.nIns() != ndefs ) + throw error("Cannot define a new name on one arm of an if"); + + // Merge results + _scope = tScope; + _allScopes.pop(); // Discard pushed from graph display + return ctrl(tScope.mergeScopes(fScope)); +} +``` + +## Operations on ScopeNodes + +As explained above, we duplicate ScopeNodes and merge them at a later point. There are some +subtleties in how this is implemented that is worth going over. + +### Duplicating a ScopeNode + +Below is the code for duplicating a ScopeNode. + +Our goals are: +1) Duplicate the name bindings across all stack levels +2) Make the new ScopeNode a user of all the bound nodes +3) Ensure that the order of defs in the duplicate is the same to allow easy merging + +```java +/** + * Duplicate a ScopeNode; including all levels, up to Nodes. So this is + * neither shallow (would dup the Scope but not the internal HashMap + * tables), nor deep (would dup the Scope, the HashMap tables, but then + * also the program Nodes). + *

+ * The new Scope is a full-fledged Node with proper use<->def edges. + */ +public ScopeNode dup() { + ScopeNode dup = new ScopeNode(); + for( HashMap tab : _scopes ) + dup._scopes.push(new HashMap<>(tab)); + for( int i=0; i Start2:p1[taillabel=1]; EQNode8 -> Con_7[taillabel=2]; - If9 -> Start2:p0[taillabel=0; color=red]; + If9 -> Start2:p0[taillabel=0 color=red]; If9 -> EQNode8[taillabel=1]; Add14 -> Start2:p1[taillabel=1]; Add14 -> Con_13[taillabel=2]; Sub16 -> Start2:p1[taillabel=1]; Sub16 -> Con_15[taillabel=2]; - Region17 -> If9:p0[taillabel=1; color=red]; - Region17 -> If9:p1[taillabel=2; color=red]; - Region17 -> Phi_a18 [style=invis] + Region17 -> If9:p0[taillabel=1 color=red]; + Region17 -> If9:p1[taillabel=2 color=red]; + Phi_a18 -> Region17 [style=dotted taillabel=0]; Phi_a18 -> Add14[taillabel=1]; Phi_a18 -> Sub16[taillabel=2]; edge [style=dashed color=cornflowerblue]; diff --git a/chapter05/docs/05-graph2.svg b/chapter05/docs/05-graph2.svg index ebc4ffac..d5e48a9e 100644 --- a/chapter05/docs/05-graph2.svg +++ b/chapter05/docs/05-graph2.svg @@ -4,220 +4,226 @@ - + chapter05 - + cluster_Nodes cluster_Scope1_0 - + cluster_Scope1_1 - + Start2 - - -Start - - - -$ctrl - -arg + + +Start + + + +$ctrl + +arg Con_7 - -#1 + +#1 EQNode8 - -== + +== EQNode8->Start2:p1 - - -1 + + +1 EQNode8->Con_7 - - -2 + + +2 If9 - - -If - - - -True - - -False + + +If + + + +True + + +False If9->Start2:p0 - - -0 + + +0 If9->EQNode8 - - -1 + + +1 Con_13 - -#2 + +#2 Add14 - -+ + ++ Add14->Start2:p1 - - - -1 + + +1 Add14->Con_13 - - -2 + + +2 Con_15 - -#3 + +#3 Sub16 - -- + +- Sub16->Start2:p1 - - -1 + + +1 Sub16->Con_15 - - -2 + + +2 Region17 - -Region + +Region Region17->If9:p0 - - -1 + + +1 Region17->If9:p1 - - -2 + + +2 Phi_a18 - -φ_a + +φ_a - Phi_a18->Add14 - - -1 + + +1 Phi_a18->Sub16 - - -2 + + +2 + + + +Phi_a18->Region17 + + +0 Scope1_0 - - -0 - -$ctrl - -arg + + +0 + +$ctrl + +arg Scope1_0:Scope1_0_arg->Start2:p1 - + + Scope1_0:Scope1_0_$ctrl->Region17 - - + + Scope1_1 - - -1 - -a + + +1 + +a Scope1_1:Scope1_1_a->Phi_a18 - - + + diff --git a/chapter05/docs/05-graph3.gv b/chapter05/docs/05-graph3.gv index 59a8a3e2..7e049c9c 100644 --- a/chapter05/docs/05-graph3.gv +++ b/chapter05/docs/05-graph3.gv @@ -9,7 +9,6 @@ else { } #showGraph; return a; - */ rankdir=BT; ordering="in"; @@ -48,20 +47,20 @@ return a; { rank=same; Region17;Phi_a18;} } edge [ fontname=Helvetica, fontsize=8 ]; - Stop3 -> Return19[taillabel=0; color=red]; + Stop3 -> Return19[taillabel=0 color=red]; EQNode8 -> Start2:p1[taillabel=1]; EQNode8 -> Con_7[taillabel=2]; - If9 -> Start2:p0[taillabel=0; color=red]; + If9 -> Start2:p0[taillabel=0 color=red]; If9 -> EQNode8[taillabel=1]; Add14 -> Start2:p1[taillabel=1]; Add14 -> Con_13[taillabel=2]; Sub16 -> Start2:p1[taillabel=1]; Sub16 -> Con_15[taillabel=2]; - Region17 -> If9:p0[taillabel=1; color=red]; - Region17 -> If9:p1[taillabel=2; color=red]; - Region17 -> Phi_a18 [style=invis] + Region17 -> If9:p0[taillabel=1 color=red]; + Region17 -> If9:p1[taillabel=2 color=red]; + Phi_a18 -> Region17 [style=dotted taillabel=0]; Phi_a18 -> Add14[taillabel=1]; Phi_a18 -> Sub16[taillabel=2]; - Return19 -> Region17[taillabel=0; color=red]; + Return19 -> Region17[taillabel=0 color=red]; Return19 -> Phi_a18[taillabel=1]; -} +} \ No newline at end of file diff --git a/chapter05/docs/05-graph3.svg b/chapter05/docs/05-graph3.svg index 5b6f0c86..855fdc2c 100644 --- a/chapter05/docs/05-graph3.svg +++ b/chapter05/docs/05-graph3.svg @@ -16,195 +16,201 @@ Start2 - - -Start - - - -$ctrl - -arg + + +Start + + + +$ctrl + +arg Stop3 - -Stop + +Stop Return19 - -Return + +Return Stop3->Return19 - - -0 + + +0 Con_7 - -#1 + +#1 EQNode8 - -== + +== EQNode8->Start2:p1 - - -1 + + +1 EQNode8->Con_7 - - -2 + + +2 If9 - - -If - - - -True - - -False + + +If + + + +True + + +False If9->Start2:p0 - - -0 + + +0 If9->EQNode8 - - -1 + + +1 Con_13 - -#2 + +#2 Add14 - -+ + ++ Add14->Start2:p1 - - -1 + + +1 Add14->Con_13 - - -2 + + +2 Con_15 - -#3 + +#3 Sub16 - -- + +- Sub16->Start2:p1 - - -1 + + +1 Sub16->Con_15 - - -2 + + +2 Region17 - -Region + +Region Region17->If9:p0 - - -1 + + +1 Region17->If9:p1 - - -2 + + +2 Phi_a18 - -φ_a + +φ_a - Phi_a18->Add14 - - -1 + + +1 Phi_a18->Sub16 - - -2 + + +2 + + + +Phi_a18->Region17 + + +0 Return19->Region17 - - -0 + + +0 Return19->Phi_a18 - - -1 + + +1 diff --git a/chapter05/docs/05-graph4.gv b/chapter05/docs/05-graph4.gv new file mode 100644 index 00000000..ac3dcdd7 --- /dev/null +++ b/chapter05/docs/05-graph4.gv @@ -0,0 +1,63 @@ +digraph chapter05 { +/* +int a=arg==2; +if( arg==1 ) +{ + a=arg==3; +} +return a; +*/ + rankdir=BT; + ordering="in"; + concentrate="true"; + subgraph cluster_Nodes { + Start2 [ shape=plaintext label=< +

+ + +
Start
+ + +
$ctrlarg
+
> + ]; + Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; + Con_6 [ label="#2" ]; + EQNode7 [ label="==" ]; + Con_8 [ label="#1" ]; + EQNode9 [ label="==" ]; + If10 [ shape=plaintext label=< + + + +
If
+ + +
TrueFalse
+
> + ]; + Con_14 [ label="#3" ]; + EQNode15 [ label="==" ]; + Region16 [ shape=box style=filled fillcolor=yellow label="Region" ]; + Phi_a17 [ style=filled fillcolor=lightyellow label="φ_a" ]; + Return18 [ shape=box style=filled fillcolor=yellow label="Return" ]; + { rank=same; Region16;Phi_a17;} + } + edge [ fontname=Helvetica, fontsize=8 ]; + Stop3 -> Return18[taillabel=0 color=red]; + EQNode7 -> Start2:p1[taillabel=1]; + EQNode7 -> Con_6[taillabel=2]; + EQNode9 -> Start2:p1[taillabel=1]; + EQNode9 -> Con_8[taillabel=2]; + If10 -> Start2:p0[taillabel=0 color=red]; + If10 -> EQNode9[taillabel=1]; + EQNode15 -> Start2:p1[taillabel=1]; + EQNode15 -> Con_14[taillabel=2]; + Region16 -> If10:p0[taillabel=1 color=red]; + Region16 -> If10:p1[taillabel=2 color=red]; + Phi_a17 -> Region16 [style=dotted taillabel=0]; + Phi_a17 -> EQNode15[taillabel=1]; + Phi_a17 -> EQNode7[taillabel=2]; + Return18 -> Region16[taillabel=0 color=red]; + Return18 -> Phi_a17[taillabel=1]; +} \ No newline at end of file diff --git a/chapter05/docs/05-graph4.svg b/chapter05/docs/05-graph4.svg new file mode 100644 index 00000000..3fcee500 --- /dev/null +++ b/chapter05/docs/05-graph4.svg @@ -0,0 +1,216 @@ + + + + + + +chapter05 + + +cluster_Nodes + + + + +Start2 + + +Start + + + +$ctrl + +arg + + + +Stop3 + +Stop + + + +Return18 + +Return + + + +Stop3->Return18 + + +0 + + + +Con_6 + +#2 + + + +EQNode7 + +== + + + +EQNode7->Start2:p1 + + +1 + + + +EQNode7->Con_6 + + +2 + + + +Con_8 + +#1 + + + +EQNode9 + +== + + + +EQNode9->Start2:p1 + + +1 + + + +EQNode9->Con_8 + + +2 + + + +If10 + + +If + + + +True + + +False + + + +If10->Start2:p0 + + +0 + + + +If10->EQNode9 + + +1 + + + +Con_14 + +#3 + + + +EQNode15 + +== + + + +EQNode15->Start2:p1 + + +1 + + + +EQNode15->Con_14 + + +2 + + + +Region16 + +Region + + + +Region16->If10:p0 + + +1 + + + +Region16->If10:p1 + + +2 + + + +Phi_a17 + +φ_a + + + +Phi_a17->EQNode7 + + +2 + + + +Phi_a17->EQNode15 + + +1 + + + +Phi_a17->Region16 + + +0 + + + +Return18->Region16 + + +0 + + + +Return18->Phi_a17 + + +1 + + + diff --git a/chapter05/docs/05-graph5.gv b/chapter05/docs/05-graph5.gv new file mode 100644 index 00000000..b6bfd5f2 --- /dev/null +++ b/chapter05/docs/05-graph5.gv @@ -0,0 +1,60 @@ +digraph chapter05 { +/* +int a=arg==2; +if( arg==1 ) +{ + a=arg==3; +} +return a; +*/ + rankdir=BT; + ordering="in"; + concentrate="true"; + subgraph cluster_Nodes { + Start2 [ shape=plaintext label=< + + + +
Start
+ + +
$ctrlarg
+
> + ]; + Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; + Con_6 [ label="#2" ]; + Con_8 [ label="#1" ]; + EQNode9 [ label="==" ]; + If10 [ shape=plaintext label=< + + + +
If
+ + +
TrueFalse
+
> + ]; + Con_14 [ label="#3" ]; + Region16 [ shape=box style=filled fillcolor=yellow label="Region" ]; + Phi_a19 [ style=filled fillcolor=lightyellow label="φ_a" ]; + EQNode20 [ label="==" ]; + Return21 [ shape=box style=filled fillcolor=yellow label="Return" ]; + { rank=same; Region16;Phi_a19;} + } + edge [ fontname=Helvetica, fontsize=8 ]; + Stop3 -> Return21[taillabel=0 color=red]; + EQNode9 -> Start2:p1[taillabel=1]; + EQNode9 -> Con_8[taillabel=2]; + If10 -> Start2:p0[taillabel=0 color=red]; + If10 -> EQNode9[taillabel=1]; + Region16 -> If10:p0[taillabel=1 color=red]; + Region16 -> If10:p1[taillabel=2 color=red]; + Phi_a19 -> Region16 [style=dotted taillabel=0]; + Phi_a19 -> Con_14[taillabel=1]; + Phi_a19 -> Con_6[taillabel=2]; + EQNode20 -> Start2:p1[taillabel=1]; + EQNode20 -> Phi_a19[taillabel=2]; + Return21 -> Region16[taillabel=0 color=red]; + Return21 -> EQNode20[taillabel=1]; +} \ No newline at end of file diff --git a/chapter05/docs/05-graph5.svg b/chapter05/docs/05-graph5.svg new file mode 100644 index 00000000..4f6fa4bd --- /dev/null +++ b/chapter05/docs/05-graph5.svg @@ -0,0 +1,196 @@ + + + + + + +chapter05 + + +cluster_Nodes + + + + +Start2 + + +Start + + + +$ctrl + +arg + + + +Stop3 + +Stop + + + +Return21 + +Return + + + +Stop3->Return21 + + +0 + + + +Con_6 + +#2 + + + +Con_8 + +#1 + + + +EQNode9 + +== + + + +EQNode9->Start2:p1 + + +1 + + + +EQNode9->Con_8 + + +2 + + + +If10 + + +If + + + +True + + +False + + + +If10->Start2:p0 + + +0 + + + +If10->EQNode9 + + +1 + + + +Con_14 + +#3 + + + +Region16 + +Region + + + +Region16->If10:p0 + + +1 + + + +Region16->If10:p1 + + +2 + + + +Phi_a19 + +φ_a + + + +Phi_a19->Con_6 + + +2 + + + +Phi_a19->Con_14 + + +1 + + + +Phi_a19->Region16 + + +0 + + + +EQNode20 + +== + + + +EQNode20->Start2:p1 + + +1 + + + +EQNode20->Phi_a19 + + +2 + + + +Return21->Region16 + + +0 + + + +Return21->EQNode20 + + +1 + + + diff --git a/chapter05/docs/05-graph6.gv b/chapter05/docs/05-graph6.gv new file mode 100644 index 00000000..f888e8e9 --- /dev/null +++ b/chapter05/docs/05-graph6.gv @@ -0,0 +1,58 @@ +digraph chapter05 { +/* +int c = 3; +int b = 2; +if (arg == 1) { + b = 3; + c = 4; +} +return c; +*/ + rankdir=BT; + ordering="in"; + concentrate="true"; + subgraph cluster_Nodes { + Start2 [ shape=plaintext label=< + + + +
Start
+ + +
$ctrlarg
+
> + ]; + Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; + Con_6 [ label="#3" ]; + Con_8 [ label="#1" ]; + EQNode9 [ label="==" ]; + If10 [ shape=plaintext label=< + + + +
If
+ + +
TrueFalse
+
> + ]; + Con_15 [ label="#4" ]; + Region16 [ shape=box style=filled fillcolor=yellow label="Region" ]; + Phi_c17 [ style=filled fillcolor=lightyellow label="φ_c" ]; + Return19 [ shape=box style=filled fillcolor=yellow label="Return" ]; + { rank=same; Region16;Phi_c17;} + } + edge [ fontname=Helvetica, fontsize=8 ]; + Stop3 -> Return19[taillabel=0 color=red]; + EQNode9 -> Start2:p1[taillabel=1]; + EQNode9 -> Con_8[taillabel=2]; + If10 -> Start2:p0[taillabel=0 color=red]; + If10 -> EQNode9[taillabel=1]; + Region16 -> If10:p0[taillabel=1 color=red]; + Region16 -> If10:p1[taillabel=2 color=red]; + Phi_c17 -> Region16 [style=dotted taillabel=0]; + Phi_c17 -> Con_15[taillabel=1]; + Phi_c17 -> Con_6[taillabel=2]; + Return19 -> Region16[taillabel=0 color=red]; + Return19 -> Phi_c17[taillabel=1]; +} \ No newline at end of file diff --git a/chapter05/docs/05-graph6.svg b/chapter05/docs/05-graph6.svg new file mode 100644 index 00000000..71527d7f --- /dev/null +++ b/chapter05/docs/05-graph6.svg @@ -0,0 +1,176 @@ + + + + + + +chapter05 + + +cluster_Nodes + + + + +Start2 + + +Start + + + +$ctrl + +arg + + + +Stop3 + +Stop + + + +Return19 + +Return + + + +Stop3->Return19 + + +0 + + + +Con_6 + +#3 + + + +Con_8 + +#1 + + + +EQNode9 + +== + + + +EQNode9->Start2:p1 + + +1 + + + +EQNode9->Con_8 + + +2 + + + +If10 + + +If + + + +True + + +False + + + +If10->Start2:p0 + + +0 + + + +If10->EQNode9 + + +1 + + + +Con_15 + +#4 + + + +Region16 + +Region + + + +Region16->If10:p0 + + +1 + + + +Region16->If10:p1 + + +2 + + + +Phi_c17 + +φ_c + + + +Phi_c17->Con_6 + + +2 + + + +Phi_c17->Con_15 + + +1 + + + +Phi_c17->Region16 + + +0 + + + +Return19->Region16 + + +0 + + + +Return19->Phi_c17 + + +1 + + + diff --git a/chapter05/docs/05-graph7.gv b/chapter05/docs/05-graph7.gv new file mode 100644 index 00000000..3abd6455 --- /dev/null +++ b/chapter05/docs/05-graph7.gv @@ -0,0 +1,72 @@ +digraph chapter05 { +/* +int a=arg+1; +int b=arg+2; +if( arg==1 ) + b=b+a; +else + a=b+1; +return a+b; +*/ + rankdir=BT; + ordering="in"; + concentrate="true"; + subgraph cluster_Nodes { + Start2 [ shape=plaintext label=< + + + +
Start
+ + +
$ctrlarg
+
> + ]; + Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; + Phi_b37 [ style=filled fillcolor=lightyellow label="φ_b" ]; + Con_10 [ label="#1" ]; + Add42 [ label="+" ]; + EQNode11 [ label="==" ]; + If12 [ shape=plaintext label=< + + + +
If
+ + +
TrueFalse
+
> + ]; + Con_46 [ label="#4" ]; + Con_48 [ label="#5" ]; + Phi_ab49 [ style=filled fillcolor=lightyellow label="φ_ab" ]; + Add50 [ label="+" ]; + Con_19 [ label="#2" ]; + Return51 [ shape=box style=filled fillcolor=yellow label="Return" ]; + Mul20 [ label="*" ]; + Region31 [ shape=box style=filled fillcolor=yellow label="Region" ]; + { rank=same; Region31;Phi_b37;Phi_ab49;} + } + edge [ fontname=Helvetica, fontsize=8 ]; + Stop3 -> Return51[taillabel=0 color=red]; + Phi_b37 -> Region31 [style=dotted taillabel=0]; + Phi_b37 -> Mul20[taillabel=1]; + Phi_b37 -> Start2:p1[taillabel=2]; + Add42 -> Phi_b37[taillabel=1]; + Add42 -> Start2:p1[taillabel=2]; + EQNode11 -> Start2:p1[taillabel=1]; + EQNode11 -> Con_10[taillabel=2]; + If12 -> Start2:p0[taillabel=0 color=red]; + If12 -> EQNode11[taillabel=1]; + Phi_ab49 -> Region31 [style=dotted taillabel=0]; + Phi_ab49 -> Con_46[taillabel=1]; + Phi_ab49 -> Con_48[taillabel=2]; + Add50 -> Add42[taillabel=1]; + Add50 -> Phi_ab49[taillabel=2]; + Return51 -> Region31[taillabel=0 color=red]; + Return51 -> Add50[taillabel=1]; + Mul20 -> Start2:p1[taillabel=1]; + Mul20 -> Con_19[taillabel=2]; + Region31 -> If12:p0[taillabel=1 color=red]; + Region31 -> If12:p1[taillabel=2 color=red]; +} diff --git a/chapter05/docs/05-graph7.svg b/chapter05/docs/05-graph7.svg new file mode 100644 index 00000000..8ba5ebd6 --- /dev/null +++ b/chapter05/docs/05-graph7.svg @@ -0,0 +1,269 @@ + + + + + + +chapter05 + + +cluster_Nodes + + + + +Start2 + + +Start + + + +$ctrl + +arg + + + +Stop3 + +Stop + + + +Return51 + +Return + + + +Stop3->Return51 + + +0 + + + +Phi_b37 + +φ_b + + + +Phi_b37->Start2:p1 + +2 + + + +Mul20 + +* + + + +Phi_b37->Mul20 + + +1 + + + +Region31 + +Region + + + +Phi_b37->Region31 + + +0 + + + +Con_10 + +#1 + + + +Add42 + ++ + + + +Add42->Start2:p1 + + + + +2 + + + +Add42->Phi_b37 + + +1 + + + +EQNode11 + +== + + + +EQNode11->Start2:p1 + + +1 + + + +EQNode11->Con_10 + + +2 + + + +If12 + + +If + + + +True + + +False + + + +If12->Start2:p0 + + +0 + + + +If12->EQNode11 + + +1 + + + +Con_46 + +#4 + + + +Con_48 + +#5 + + + +Phi_ab49 + +φ_ab + + + +Phi_ab49->Con_46 + + +1 + + + +Phi_ab49->Con_48 + + +2 + + + +Phi_ab49->Region31 + + +0 + + + +Add50 + ++ + + + +Add50->Add42 + + +1 + + + +Add50->Phi_ab49 + + +2 + + + +Con_19 + +#2 + + + +Return51->Add50 + + +1 + + + +Return51->Region31 + + +0 + + + +Mul20->Start2:p1 + +1 + + + +Mul20->Con_19 + + +2 + + + +Region31->If12:p0 + + +1 + + + +Region31->If12:p1 + + +2 + + + diff --git a/chapter05/docs/05-graph8.gv b/chapter05/docs/05-graph8.gv new file mode 100644 index 00000000..bef29b93 --- /dev/null +++ b/chapter05/docs/05-graph8.gv @@ -0,0 +1,129 @@ +digraph chapter05 { +/* +int a=1; +if( arg==1 ) + if( arg==2 ) + a=2; + else + a=3; +else if( arg==3 ) + a=4; +else + a=5; +return a; +#showGraph; +*/ + rankdir=BT; + ordering="in"; + concentrate="true"; + subgraph cluster_Nodes { + Start2 [ shape=plaintext label=< + + + +
Start
+ + +
$ctrlarg
+
> + ]; + Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; + Con_7 [ label="#1" ]; + EQNode8 [ label="==" ]; + If9 [ shape=plaintext label=< + + + +
If
+ + +
TrueFalse
+
> + ]; + Con_13 [ label="#2" ]; + EQNode14 [ label="==" ]; + If15 [ shape=plaintext label=< + + + +
If
+ + +
TrueFalse
+
> + ]; + Con_19 [ label="#2" ]; + Con_20 [ label="#3" ]; + Region21 [ shape=box style=filled fillcolor=yellow label="Region" ]; + Phi_a22 [ style=filled fillcolor=lightyellow label="φ_a" ]; + Con_23 [ label="#3" ]; + EQNode24 [ label="==" ]; + If25 [ shape=plaintext label=< + + + +
If
+ + +
TrueFalse
+
> + ]; + Con_29 [ label="#4" ]; + Con_30 [ label="#5" ]; + Region31 [ shape=box style=filled fillcolor=yellow label="Region" ]; + Phi_a32 [ style=filled fillcolor=lightyellow label="φ_a" ]; + Region33 [ shape=box style=filled fillcolor=yellow label="Region" ]; + Phi_a34 [ style=filled fillcolor=lightyellow label="φ_a" ]; + Return35 [ shape=box style=filled fillcolor=yellow label="Return" ]; + { rank=same; Region21;Phi_a22;} + { rank=same; Region31;Phi_a32;} + { rank=same; Region33;Phi_a34;} + } + node [shape=plaintext]; + subgraph cluster_Scope1_0 { + Scope1_0 [label=< + + +
0$ctrlarg
>]; + subgraph cluster_Scope1_1 { + Scope1_1 [label=< + + +
1a
>]; + } + } + edge [ fontname=Helvetica, fontsize=8 ]; + Stop3 -> Return35[taillabel=0 color=red]; + EQNode8 -> Start2:p1[taillabel=1]; + EQNode8 -> Con_7[taillabel=2]; + If9 -> Start2:p0[taillabel=0 color=red]; + If9 -> EQNode8[taillabel=1]; + EQNode14 -> Start2:p1[taillabel=1]; + EQNode14 -> Con_13[taillabel=2]; + If15 -> If9:p0[taillabel=0 color=red]; + If15 -> EQNode14[taillabel=1]; + Region21 -> If15:p0[taillabel=1 color=red]; + Region21 -> If15:p1[taillabel=2 color=red]; + Phi_a22 -> Region21 [style=dotted taillabel=0]; + Phi_a22 -> Con_19[taillabel=1]; + Phi_a22 -> Con_20[taillabel=2]; + EQNode24 -> Start2:p1[taillabel=1]; + EQNode24 -> Con_23[taillabel=2]; + If25 -> If9:p1[taillabel=0 color=red]; + If25 -> EQNode24[taillabel=1]; + Region31 -> If25:p0[taillabel=1 color=red]; + Region31 -> If25:p1[taillabel=2 color=red]; + Phi_a32 -> Region31 [style=dotted taillabel=0]; + Phi_a32 -> Con_29[taillabel=1]; + Phi_a32 -> Con_30[taillabel=2]; + Region33 -> Region21[taillabel=1 color=red]; + Region33 -> Region31[taillabel=2 color=red]; + Phi_a34 -> Region33 [style=dotted taillabel=0]; + Phi_a34 -> Phi_a22[taillabel=1]; + Phi_a34 -> Phi_a32[taillabel=2]; + Return35 -> Region33[taillabel=0 color=red]; + Return35 -> Phi_a34[taillabel=1]; + edge [style=dashed color=cornflowerblue]; + Scope1_0:"Scope1_0_arg" -> Start2:p1; + Scope1_1:"Scope1_1_a" -> Phi_a34; +} \ No newline at end of file diff --git a/chapter05/docs/05-graph8.svg b/chapter05/docs/05-graph8.svg new file mode 100644 index 00000000..e5310db4 --- /dev/null +++ b/chapter05/docs/05-graph8.svg @@ -0,0 +1,430 @@ + + + + + + +chapter05 + + +cluster_Nodes + + + +cluster_Scope1_0 + + + +cluster_Scope1_1 + + + + +Start2 + + +Start + + + +$ctrl + +arg + + + +Stop3 + +Stop + + + +Return35 + +Return + + + +Stop3->Return35 + + +0 + + + +Con_7 + +#1 + + + +EQNode8 + +== + + + +EQNode8->Start2:p1 + + +1 + + + +EQNode8->Con_7 + + +2 + + + +If9 + + +If + + + +True + + +False + + + +If9->Start2:p0 + + +0 + + + +If9->EQNode8 + + +1 + + + +Con_13 + +#2 + + + +EQNode14 + +== + + + +EQNode14->Start2:p1 + + +1 + + + +EQNode14->Con_13 + + +2 + + + +If15 + + +If + + + +True + + +False + + + +If15->If9:p0 + + +0 + + + +If15->EQNode14 + + +1 + + + +Con_19 + +#2 + + + +Con_20 + +#3 + + + +Region21 + +Region + + + +Region21->If15:p0 + + +1 + + + +Region21->If15:p1 + + +2 + + + +Phi_a22 + +φ_a + + + +Phi_a22->Con_19 + + +1 + + + +Phi_a22->Con_20 + + +2 + + + +Phi_a22->Region21 + + +0 + + + +Con_23 + +#3 + + + +EQNode24 + +== + + + +EQNode24->Start2:p1 + + +1 + + + +EQNode24->Con_23 + + +2 + + + +If25 + + +If + + + +True + + +False + + + +If25->If9:p1 + + +0 + + + +If25->EQNode24 + + +1 + + + +Con_29 + +#4 + + + +Con_30 + +#5 + + + +Region31 + +Region + + + +Region31->If25:p0 + + +1 + + + +Region31->If25:p1 + + +2 + + + +Phi_a32 + +φ_a + + + +Phi_a32->Con_29 + + +1 + + + +Phi_a32->Con_30 + + +2 + + + +Phi_a32->Region31 + + +0 + + + +Region33 + +Region + + + +Region33->Region21 + + +1 + + + +Region33->Region31 + + +2 + + + +Phi_a34 + +φ_a + + + +Phi_a34->Phi_a22 + + +1 + + + +Phi_a34->Phi_a32 + + +2 + + + +Phi_a34->Region33 + + +0 + + + +Return35->Region33 + + +0 + + + +Return35->Phi_a34 + + +1 + + + +Scope1_0 + + +0 + +$ctrl + +arg + + + +Scope1_0:Scope1_0_arg->Start2:p1 + + + + + +Scope1_1 + + +1 + +a + + + +Scope1_1:Scope1_1_a->Phi_a34 + + + + + diff --git a/chapter05/src/main/java/com/seaofnodes/simple/GraphVisualizer.java b/chapter05/src/main/java/com/seaofnodes/simple/GraphVisualizer.java index f3942edb..01b5aeeb 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/GraphVisualizer.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/GraphVisualizer.java @@ -146,7 +146,7 @@ public class GraphVisualizer { // Walk the node edges private void nodeEdges(StringBuilder sb, Collection all) { // All them edge labels - sb.append("\tedge [ fontname=Helvetica, fontsize=8 ];\n"); + sb.append("\tedge [ fontname=Helvetica, fontsize=8 ];\n"); for( Node n : all ) { // Do not display the Constant->Start edge; // ProjNodes handled by Multi; @@ -155,16 +155,12 @@ public class GraphVisualizer { continue; int i=0; for( Node def : n._inputs ) { - // Do not draw the Phi->Region edge (in red); instead Phis are - // on a line with their Region, and we draw an invisible line - // from the Region to the Phi, to force all the Phis to the - // right of the Region. if( n instanceof PhiNode && def instanceof RegionNode ) { - sb.append('\t').append(def.uniqueName()); + // Draw a dotted use->def edge from Phi to Region. + sb.append('\t').append(n.uniqueName()); sb.append(" -> "); - sb.append(n.uniqueName()); - sb.append(" [style=invis]\n"); - + sb.append(def.uniqueName()); + sb.append(" [style=dotted taillabel=").append(i).append("];\n"); } else if( def != null ) { // Most edges land here use->def sb.append('\t').append(n.uniqueName()).append(" -> "); @@ -176,7 +172,7 @@ public class GraphVisualizer { sb.append("[taillabel=").append(i); // control edges are colored red if( def.isCFG() ) - sb.append("; color=red"); + sb.append(" color=red"); sb.append("];\n"); } i++; diff --git a/chapter05/src/main/java/com/seaofnodes/simple/Parser.java b/chapter05/src/main/java/com/seaofnodes/simple/Parser.java index 8bf343b3..a132fce0 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/Parser.java @@ -94,6 +94,7 @@ public class Parser { _scope.define(CTRL , new ProjNode(START, 0, CTRL ).peephole()); _scope.define("arg", new ProjNode(START, 1, "arg").peephole()); parseBlock(); + if (!_lexer.isEOF()) throw error("Syntax error, unexpected " + _lexer.getAnyNextToken()); return STOP; } finally { @@ -129,7 +130,7 @@ public class Parser { * Parses a statement * *

-     *     returnStatement | declStatement | blockStatement | expressionStatement
+     *     returnStatement | declStatement | blockStatement | ifStatement | expressionStatement
      * 
* @return a {@link Node} or {@code null} */ diff --git a/chapter05/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter05/src/main/java/com/seaofnodes/simple/node/MinusNode.java index 834f4c11..533155d9 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -20,7 +20,7 @@ public class MinusNode extends Node { @Override public Type compute() { if (in(1)._type instanceof TypeInteger i0) - return TypeInteger.constant(-i0.value()); + return i0.isConstant() ? TypeInteger.constant(-i0.value()) : i0; return TypeBot.BOTTOM; } diff --git a/chapter05/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter05/src/main/java/com/seaofnodes/simple/node/ScopeNode.java index 64061030..61fac118 100644 --- a/chapter05/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ b/chapter05/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -105,6 +105,10 @@ public class ScopeNode extends Node { * The new Scope is a full-fledged Node with proper use<->def edges. */ public ScopeNode dup() { + // Our goals are: + // 1) duplicate the name bindings of the ScopeNode across all stack levels + // 2) Make the new ScopeNode a user of all the nodes bound + // 3) Ensure that the order of defs is the same to allow easy merging ScopeNode dup = new ScopeNode(); for( HashMap tab : _scopes ) dup._scopes.push(new HashMap<>(tab)); @@ -114,6 +118,14 @@ public class ScopeNode extends Node { return dup; } + /** + * Merges the names whose node bindings differ, by creating Phi node for such names + * The names could occur at all stack levels, but a given name can only differ in the + * innermost stack level where the name is bound. + * + * @param that The ScopeNode to be merged into this + * @return A new Region node representing the merge point + */ public RegionNode mergeScopes(ScopeNode that) { RegionNode r = (RegionNode)ctrl(new RegionNode(null,ctrl(), that.ctrl()).peephole()); for( int i=1; i