summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDibyendu Majumdar <dibyendumajumdar@users.noreply.github.com>2023-12-03 23:23:04 +0000
committerGitHub <noreply@github.com>2023-12-03 15:23:04 -0800
commit6415e3ae9013e3a9ae0437553ea0c3702381469b (patch)
treeaec64a03c86332ce53857d9297b7bbfb7f807acf
parent21a1fc4a4414f8511ae5e94965b4b72fd45751e0 (diff)
downloadSimple-6415e3ae9013e3a9ae0437553ea0c3702381469b.tar.gz
Simple-6415e3ae9013e3a9ae0437553ea0c3702381469b.zip
Chapter 7 (also 8 but not officially) (#48)
* #44 Start on chapter 7 * #44 Temp hack on printing to not break on cycles. Parse while statement. * #44 Remove unnecessary method * #44 Remove unnecessary phi map * #44 Phi should have existing nodes from both scopes as input * #44 We need to only insert phis once for each name that is looked up. To do so, we need to know when a named is looked up first time inside the loop body. The phi must have its second input added later on after the loop is fully parsed. * #44 Add phi also when update() is called. Only add phi if name was known at loop start * #44 Only create phi in loop body if var binding in head != body. During finish() lookup in body directly. * #44 Update the print output to make it similar to that in Xmilia's fork * #44 Disable peephole for phis & region involved in while loop * Fix broken tests Remove "disablePeephole" set in some tests, and remaining set for other tests, so a bulk test-all fails because disablePeephole is true for all. Reset disablePeephole in Node.reset Switch HashSet for BitSet (touches all). * Restructure Node class Group printing, group node & edge manipulations, group optimizations. Also, fix LoopRegion idom, it does not use Region idom but uses the original * #44 Node printing * #44 Node printing * #44 Fix control edges in loop region Change label of loop region Fix the output count in dump * #44 Revise dump output * #44 When a LoopRegionNode is duplicated, ensure the dupe is also a LoopRegionNode * #44 When a LoopScopeNode is merged, we merge the names set too * #44 When a LoopScopeNode is merged, we merge the names set too * #44 Fix print1() on RegionNode to use label() * #44 Fix bug caused by incorrect comparison of nodes between body and head of loop; the check must be performed before any phis are created * Revert "#44 Fix bug caused by incorrect comparison of nodes between body and head of loop; the check must be performed before any phis are created" This reverts commit 5f729709855046c5c10904f08385fd1aec9a298a. * #44 Fix bug caused by incorrect comparison of nodes between body and head of loop; the check must be performed before any phis are created * #44 when we create a phi incrementally for a loop region, all scopes that are cloned from the original must get the same phi update * #44 test case * #44 test case * Squashed commit of the following: commit 15666e287e6bbc39b3fcc49939165a1e908d43b2 Author: Cliff Click <cliffc@acm.org> Date: Mon Nov 27 09:45:40 2023 -0800 lazy/eager phis Eager phis by default, but can flip Parser.LAZY to switch to lazy phis. Several tests golden answers change when flipping this flag due to minor node renumbering. Phi combines same_inputs and singleUniqueInput. Fancier basic-block-like printing. Uses isMultiHead & isMultiTail which show up in many Nodes. Remove blanks in triple-quote strings. commit 6ea70182a92b7db4fe5a1fdc2c4491842ef77ee5 Author: Cliff Click <cliffc@acm.org> Date: Sun Nov 26 11:13:22 2023 -0800 Remove dead scopes Add a debug find commit a52c06a47bf1e56866d8b4d043290838f053ad76 Author: Cliff Click <cliffc@acm.org> Date: Sun Nov 26 10:21:48 2023 -0800 No constraints on backedges nicer loopy graphs commit b5ab550098355f0b66bf24b40adf2e5ba4d86e4e Author: Cliff Click <cliffc@acm.org> Date: Sun Nov 26 10:13:22 2023 -0800 Fix regression commit 30a7f5c734c686c02d3ca479914c9427b65cc469 Author: Cliff Click <cliffc@acm.org> Date: Sat Nov 25 21:51:47 2023 -0800 minor cleanup commit ad742b795ce0c93cfc82b2b11740261cfd923d02 Author: Cliff Click <cliffc@acm.org> Date: Sat Nov 25 21:47:05 2023 -0800 Fixes lazy phi Scope now takes a 'loop' option on dupping; the dup'ed Scope is filled with "Lazy Phi" sentinels - markers to indicate a Phi is need if this variable is ever referenced. If its never referenced, at the loop exit the "Lazy Phi" unwinds back to the original value. Lookup and update calls lazily insert concrete Phis over "Lazy Phis" at first touch. Rename _allScopes to _xScopes its "active" not "all" scopes now. Remove try/finally block at end, because a compiler bug throws, then attempts a GraphViz on a broken graph, then throws again, hiding the original bug. Removed 1 extra layer of Scopes. Scope printing now prints in edge order; added a reversemap for printing. More touches to ascii printing. * Remove flag to parseBlock * Bugfix for lazy on empty loop body Remove extra phi name-matching code * Keep head alive until entire end_loop merging is done * Create chapter 8 as a copy of chapter 7 * #44 Chapter 7 is now eager phis only (#46) * #44 Chapter 7 is now eager phis only * MOVE IR Printing to dedicated class to declutter Node. Remove Ary (#49) * #44 Move IR printing to dedicated class to declutter Node, and remove Ary * #44 refactor Node.delUse() to use common del() * #44 Some cleanups * #44 Some cleanups * #44 Some cleanups * #44 Some cleanups * Revert "#44 Some cleanups" This reverts commit 2702c338c8bdaa5ef2128000e26f1a8496baf34a. * #44 Some cleanups * #44 Some cleanups * #44 Some cleanups * #44 Fix typo * #44 Fix markdown formatting * #44 Remove line numbers from code snippets --------- Co-authored-by: Cliff Click <cliffc@acm.org>
-rw-r--r--.dir-locals.el6
-rw-r--r--README.md23
-rw-r--r--chapter07/Makefile92
-rw-r--r--chapter07/README.md227
-rw-r--r--chapter07/docs/07-grammar.md88
-rw-r--r--chapter07/docs/07-graph1.gv107
-rw-r--r--chapter07/docs/07-graph1.svg268
-rw-r--r--chapter07/docs/07-graph2.gv62
-rw-r--r--chapter07/docs/07-graph2.svg184
-rw-r--r--chapter07/docs/07-graph3.gv108
-rw-r--r--chapter07/docs/07-graph3.svg373
-rw-r--r--chapter07/docs/07-graph4.gv96
-rw-r--r--chapter07/docs/07-graph4.svg329
-rw-r--r--chapter07/docs/build-graphs.sh6
-rw-r--r--chapter07/pom.xml14
-rw-r--r--chapter07/src/main/java/com/seaofnodes/simple/GraphVisualizer.java256
-rw-r--r--chapter07/src/main/java/com/seaofnodes/simple/IRPrinter.java124
-rw-r--r--chapter07/src/main/java/com/seaofnodes/simple/Parser.java572
-rw-r--r--chapter07/src/main/java/com/seaofnodes/simple/Utils.java39
-rw-r--r--chapter07/src/main/java/com/seaofnodes/simple/node/AddNode.java158
-rw-r--r--chapter07/src/main/java/com/seaofnodes/simple/node/BoolNode.java60
-rw-r--r--chapter07/src/main/java/com/seaofnodes/simple/node/ConstantNode.java45
-rw-r--r--chapter07/src/main/java/com/seaofnodes/simple/node/DivNode.java39
-rw-r--r--chapter07/src/main/java/com/seaofnodes/simple/node/IfNode.java58
-rw-r--r--chapter07/src/main/java/com/seaofnodes/simple/node/LoopNode.java35
-rw-r--r--chapter07/src/main/java/com/seaofnodes/simple/node/MinusNode.java30
-rw-r--r--chapter07/src/main/java/com/seaofnodes/simple/node/MulNode.java58
-rw-r--r--chapter07/src/main/java/com/seaofnodes/simple/node/MultiNode.java7
-rw-r--r--chapter07/src/main/java/com/seaofnodes/simple/node/Node.java467
-rw-r--r--chapter07/src/main/java/com/seaofnodes/simple/node/NotNode.java30
-rw-r--r--chapter07/src/main/java/com/seaofnodes/simple/node/PhiNode.java101
-rw-r--r--chapter07/src/main/java/com/seaofnodes/simple/node/ProjNode.java49
-rw-r--r--chapter07/src/main/java/com/seaofnodes/simple/node/RegionNode.java81
-rw-r--r--chapter07/src/main/java/com/seaofnodes/simple/node/ReturnNode.java46
-rw-r--r--chapter07/src/main/java/com/seaofnodes/simple/node/ScopeNode.java169
-rw-r--r--chapter07/src/main/java/com/seaofnodes/simple/node/StartNode.java46
-rw-r--r--chapter07/src/main/java/com/seaofnodes/simple/node/StopNode.java53
-rw-r--r--chapter07/src/main/java/com/seaofnodes/simple/node/SubNode.java43
-rw-r--r--chapter07/src/main/java/com/seaofnodes/simple/type/Type.java77
-rw-r--r--chapter07/src/main/java/com/seaofnodes/simple/type/TypeInteger.java63
-rw-r--r--chapter07/src/main/java/com/seaofnodes/simple/type/TypeTuple.java32
-rw-r--r--chapter07/src/test/java/com/seaofnodes/simple/Chapter07Test.java1026
-rw-r--r--chapter08/Makefile92
-rw-r--r--chapter08/README.md2
-rw-r--r--chapter08/docs/08-grammar.md88
-rw-r--r--chapter08/docs/build-graphs.sh3
-rw-r--r--chapter08/pom.xml14
-rw-r--r--chapter08/src/main/java/com/seaofnodes/simple/GraphVisualizer.java258
-rw-r--r--chapter08/src/main/java/com/seaofnodes/simple/IRPrinter.java124
-rw-r--r--chapter08/src/main/java/com/seaofnodes/simple/Parser.java574
-rw-r--r--chapter08/src/main/java/com/seaofnodes/simple/Utils.java39
-rw-r--r--chapter08/src/main/java/com/seaofnodes/simple/node/AddNode.java158
-rw-r--r--chapter08/src/main/java/com/seaofnodes/simple/node/BoolNode.java60
-rw-r--r--chapter08/src/main/java/com/seaofnodes/simple/node/ConstantNode.java45
-rw-r--r--chapter08/src/main/java/com/seaofnodes/simple/node/DivNode.java39
-rw-r--r--chapter08/src/main/java/com/seaofnodes/simple/node/IfNode.java58
-rw-r--r--chapter08/src/main/java/com/seaofnodes/simple/node/LoopNode.java35
-rw-r--r--chapter08/src/main/java/com/seaofnodes/simple/node/MinusNode.java30
-rw-r--r--chapter08/src/main/java/com/seaofnodes/simple/node/MulNode.java58
-rw-r--r--chapter08/src/main/java/com/seaofnodes/simple/node/MultiNode.java7
-rw-r--r--chapter08/src/main/java/com/seaofnodes/simple/node/Node.java467
-rw-r--r--chapter08/src/main/java/com/seaofnodes/simple/node/NotNode.java30
-rw-r--r--chapter08/src/main/java/com/seaofnodes/simple/node/PhiNode.java101
-rw-r--r--chapter08/src/main/java/com/seaofnodes/simple/node/ProjNode.java49
-rw-r--r--chapter08/src/main/java/com/seaofnodes/simple/node/RegionNode.java81
-rw-r--r--chapter08/src/main/java/com/seaofnodes/simple/node/ReturnNode.java46
-rw-r--r--chapter08/src/main/java/com/seaofnodes/simple/node/ScopeNode.java193
-rw-r--r--chapter08/src/main/java/com/seaofnodes/simple/node/StartNode.java46
-rw-r--r--chapter08/src/main/java/com/seaofnodes/simple/node/StopNode.java53
-rw-r--r--chapter08/src/main/java/com/seaofnodes/simple/node/SubNode.java43
-rw-r--r--chapter08/src/main/java/com/seaofnodes/simple/type/Type.java77
-rw-r--r--chapter08/src/main/java/com/seaofnodes/simple/type/TypeInteger.java63
-rw-r--r--chapter08/src/main/java/com/seaofnodes/simple/type/TypeTuple.java32
-rw-r--r--chapter08/src/test/java/com/seaofnodes/simple/Chapter08Test.java1028
-rw-r--r--pom.xml2
75 files changed, 9729 insertions, 13 deletions
diff --git a/.dir-locals.el b/.dir-locals.el
index c4e1e5c7..74fde018 100644
--- a/.dir-locals.el
+++ b/.dir-locals.el
@@ -1,4 +1,6 @@
;;; Directory Local Variables
;;; For more information see (info "(emacs) Directory Variables")
-
-((dired-mode . ((c-basic-offset . 4))))
+(
+ (dired-mode . ((c-basic-offset . 4)))
+ (java-mode . ((c-basic-offset . 4)))
+)
diff --git a/README.md b/README.md
index 59a342b1..3424c5d9 100644
--- a/README.md
+++ b/README.md
@@ -34,16 +34,17 @@ The simple language will be a small subset of C.
* [Chapter 4](chapter04/README.md): A non-constant external variable input named `arg`. Binary and Comparison operators involving constants and `arg`. Non-zero values will be truthy. Peephole optimizations involving algebraic simplifications.
* [Chapter 5](chapter05/README.md): `if` statement. CFG construction.
* [Chapter 6](chapter06/README.md): Peephole optimization around dead control flow.
-* Chapter 7: `while` statement.
-* Chapter 8: Global Value Numbering.
-* Chapter 9: Functions and calls.
-* Chapter 10: Boolean operators `&&`, `||` and `!` including short circuit.
-* Chapter 11: Memory effects: general memory edges in SSA. Peephole optimization around
+* [Chapter 7](chapter07/README.md): `while` statement. Looping construct - eager phi approach.
+* Chapter 8: Looping construct continued, lazy phi creation, `break` and `continue` statements.
+* Chapter 9: Global Value Numbering.
+* Chapter 10: Functions and calls.
+* Chapter 11: Boolean operators `&&`, `||` and `!` including short circuit.
+* Chapter 12: Memory effects: general memory edges in SSA. Peephole optimization around
load-after-store/store-after-store.
-* Chapter 12: Equivalence class aliasing, fine grained peephole optimizations. Free ptr-to analysis in SoN; but does not
+* Chapter 13: Equivalence class aliasing, fine grained peephole optimizations. Free ptr-to analysis in SoN; but does not
handle aliasing in arrays.
-* Chapter 13: One dimensional static length array type. Array load/store.
-* Chapter 14: Global Code Motion - unwind SoN to CFG. Scheduling.
-* Chapter 15: Instruction selection, BURS.
-* Chapter 16: Backend register allocation.
-* Chapter 17: Garbage Collection. \ No newline at end of file
+* Chapter 14: One dimensional static length array type. Array load/store.
+* Chapter 15: Global Code Motion - unwind SoN to CFG. Scheduling.
+* Chapter 16: Instruction selection, BURS.
+* Chapter 17: Backend register allocation.
+* Chapter 18: Garbage Collection. \ No newline at end of file
diff --git a/chapter07/Makefile b/chapter07/Makefile
new file mode 100644
index 00000000..56f69e12
--- /dev/null
+++ b/chapter07/Makefile
@@ -0,0 +1,92 @@
+SHELL := /bin/bash
+.DELETE_ON_ERROR:
+
+# for printing variable values
+# usage: make print-VARIABLE
+# > VARIABLE = value_of_variable
+print-% : ; @echo $* = $($*)
+
+# literal space
+space := $() $()
+
+# Decide OS-specific questions
+# jar-file seperator
+ifeq ($(OS),Windows_NT)
+ SEP = ;
+else
+ SEP = :
+endif
+# Find a reasonable ctags.
+CTAGS = $(shell which ctags)
+# Hack for MacOS: /usr/bin/ctags is unfriendly, so look for ctags from brew
+ifeq ($(UNAME),Darwin)
+ CTAGS = $(shell brew list ctags 2> /dev/null | grep bin/ctags)
+endif
+
+# Fun Args to javac. Mostly limit to java8 source definitions, and fairly
+# aggressive lint warnings.
+JAVAC_ARGS = -g
+
+# Source code
+SIMPLE := com/seaofnodes/simple
+SRC := src/main/java
+TST := src/test/java
+CLZDIR:= build/classes
+main_javas := $(wildcard $(SRC)/$(SIMPLE)/*java $(SRC)/$(SIMPLE)/*/*java)
+test_javas := $(wildcard $(TST)/$(SIMPLE)/*java $(TST)/$(SIMPLE)/*/*java)
+main_classes := $(patsubst $(SRC)/%java,$(CLZDIR)/main/%class,$(main_javas))
+test_classes := $(patsubst $(TST)/%java,$(CLZDIR)/test/%class,$(test_javas))
+test_cp := $(patsubst $(TST)/$(SIMPLE)/%.java,com.seaofnodes.simple.%,$(test_javas))
+classes = $(main_classes) $(test_classes)
+# All the libraries
+libs = $(wildcard lib/*jar)
+jars = $(subst $(space),$(SEP),$(libs))
+
+
+default_targets := $(main_classes) $(test_classes)
+# Optionally add ctags to the default target if a reasonable one was found.
+ifneq ($(CTAGS),)
+default_targets += tags
+endif
+
+default: $(default_targets)
+
+# Compile just the out-of-date files
+$(main_classes): build/classes/main/%class: $(SRC)/%java
+ @echo "compiling " $@ " because " $?
+ @[ -d $(CLZDIR)/main ] || mkdir -p $(CLZDIR)/main
+ @javac $(JAVAC_ARGS) -cp "$(CLZDIR)/main$(SEP)$(jars)" -sourcepath $(SRC) -d $(CLZDIR)/main $(main_javas)
+
+$(test_classes): $(CLZDIR)/test/%class: $(TST)/%java $(main_classes)
+ @echo "compiling " $@ " because " $?
+ @[ -d $(CLZDIR)/test ] || mkdir -p $(CLZDIR)/test
+ @javac $(JAVAC_ARGS) -cp "$(CLZDIR)/test$(SEP)$(CLZDIR)/main$(SEP)$(jars)" -sourcepath $(TST) -d $(CLZDIR)/test $(test_javas)
+
+# Base launch line for JVM tests
+JVM=nice java -ea -cp "build/classes/main${SEP}${jars}${SEP}$(CLZDIR)/test"
+
+tests: $(default_targets)
+ $(JVM) org.junit.runner.JUnitCore $(test_cp)
+
+.PHONY: clean
+clean:
+ rm -rf build
+ rm -f TAGS
+ (find . -name "*~" -exec rm {} \; 2>/dev/null; exit 0)
+
+# Download libs from maven
+lib: lib/junit-4.12.jar lib/hamcrest-core-1.3.jar
+
+# Jars
+lib/junit-4.12.jar:
+ @[ -d lib ] || mkdir -p lib
+ @(cd lib; wget https://repo1.maven.org/maven2/junit/junit/4.12/junit-4.12.jar)
+
+lib/hamcrest-core-1.3.jar:
+ @[ -d lib ] || mkdir -p lib
+ @(cd lib; wget https://repo1.maven.org/maven2/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar)
+
+# Build emacs tags (part of a tasty emacs ide experience)
+tags: $(main_javas) $(test_javas)
+ @rm -f TAGS
+ @$(CTAGS) -e --recurse=yes --extra=+q --fields=+fksaiS $(SRC) $(TST)
diff --git a/chapter07/README.md b/chapter07/README.md
new file mode 100644
index 00000000..c6bcdee4
--- /dev/null
+++ b/chapter07/README.md
@@ -0,0 +1,227 @@
+# Chapter 7
+
+In this chapter we introduce the `while` statement.
+
+Here is the [complete language grammar](docs/07-grammar.md) for this chapter.
+
+## Dealing With Back Edges
+
+The complication introduced by looping constructs is that variables flow back into the body of the loop on iteration.
+For example:
+
+```java
+while(arg < 10) {
+ arg = arg + 1;
+}
+return arg;
+```
+
+The variable `arg` is assigned a new value inside the body of the loop, and this then flows back into the body of the loop.
+
+In general, we will rewrite the looping construct as follows:
+
+```java
+loop_head:
+if( !(arg < 10) )
+ goto loop_exit;
+arg = arg + 1;
+goto loop_head;
+
+loop_exit:
+```
+
+Above is for illustration only, we do not have labels and `goto` statements in the language.
+
+From an SSA[^1] point of view, since `arg` flows back, it requires a `phi` node at the head. Conceptually we would like the outcome to be:
+
+```java
+// arg1 represents incoming arg
+//
+loop_head:
+arg2 = phi(arg1, arg3);
+if( !(arg2 < 10) )
+ goto loop_exit;
+arg3 = arg2 + 1;
+goto loop_head;
+
+loop_exit:
+```
+
+Notice that the phi for `arg2` refers to `arg3`, which is not known at the time we parse the `while` loop predicate. This is the crux of the problem that we need
+to solve in order to successfully construct the Sea of Nodes graph, which is always in SSA form.
+
+Recall from [Chapter 5](../chapter05/README.md) that when parsing `if` statements, we clone the symbol tables as we go past the `if` predicate.
+Later we merge the two sets of symbol tables at a `Region` - creating phis for all names that encountered a change in definition within the two
+branches of the `if` statement. In the case of the `if` statement, the phis are created at the merge point when we already know the definitions
+that are being merged.
+
+The essential idea for loop constructs is to eagerly create phis for all names in the symbol tables *before* we enter the loop's `if` condition,
+since we do not know which names will be redefined inside the body of the loop. When the loop terminates, we go back and remove unnecessary
+phis. We call this approach the "eager phi" approach.[^2]
+
+In [Chapter 8](../chapter08) we will implement a "lazy phi" approach that creates phis only when we encounter redefinition of a name.
+
+## New Node Types
+
+Our list of nodes remains the same as in [Chapter 5](../chapter05/README.md), however, we create a subtype of `Region` named `Loop` to better
+encapsulate some of the additional logic required. A key aspect of this is to temporarily disable peepholes of the `Region` and any phis
+created until we complete parsing the loop body. This is because our phis are not fully constructed until the loop end.
+
+## Detailed Steps
+
+1. We start by creating a new subclass of `Region`, the `Loop`. The `Loop` gets two control inputs,
+ the first one is the entry point, i.e. the current binding to `$ctrl`, and second one (`null`) is a placeholder for the back edge that is
+ set after loop is parsed. The absence of a back edge is used as an indicator to switch off peepholes of the region and
+ associated phis.
+
+ ```java
+ ctrl(new LoopNode(ctrl(),null).peephole());
+ ```
+
+ The newly created region becomes the current control.
+
+2. We duplicate the current Scope node. This involves duplicating all the symbols at
+ every level with the scope, and creating phis for every symbol except the `$ctrl` binding.
+
+ ```java
+ // Make a new Scope for the body.
+ _scope = _scope.dup(true);
+ ```
+
+ Note that the `dup` call is given an argument `true`. This triggers creating phis. The code
+ that creates the phis in the `dup()` method is shown below.
+
+ ```java
+ // boolean loop=true if this is a loop region
+
+ String[] reverse = reverse_names();
+ dup.add_def(ctrl()); // Control input is just copied
+ for( int i=1; i<nIns(); i++ ) {
+ if ( !loop ) { dup.add_def(in(i)); }
+ else {
+ // Loop region
+ // Create a phi node with second input as null - to be filled in
+ // by endLoop() below
+ dup.add_def(new PhiNode(reverse[i], ctrl(), in(i), null).peephole());
+ // Ensure our node has the same phi in case we created one
+ set_def(i, dup.in(i));
+ }
+ }
+ ```
+
+ Inside the `else` block both the duplicated scope and the original scope get the same phi node.
+
+3. Next we set up the `if` condition, very much like we do with regular ifs.
+
+ ```java
+ // Parse predicate
+ var pred = require(parseExpression(), ")");
+ // IfNode takes current control and predicate
+ IfNode ifNode = (IfNode)new IfNode(ctrl(), pred).<IfNode>keep().peephole();
+ // Setup projection nodes
+ Node ifT = new ProjNode(ifNode, 0, "True" ).peephole();
+ ifNode.unkeep();
+ Node ifF = new ProjNode(ifNode, 1, "False").peephole();
+ ```
+
+4. We set the control token to the `True` projection node `ifT`, but before that we make another clone of
+ the current scope.
+
+ ```java
+ // The exit scope, accounting for any side effects in the predicate
+ var exit = _scope.dup();
+ exit.ctrl(ifF);
+ ```
+
+ The new scope is saved as the exit scope that will live after the loop ends, therefore `$ctrl` in the exit scope is
+ set to the `False` projection. The exit scope captures any side effects of the loop's predicate.
+
+5. We now set the control to the `True` projection and parse the loop body.
+
+ ```java
+ // Parse the true side, which corresponds to loop body
+ ctrl(ifT); // set ctrl token to ifTrue projection
+ parseStatement(); // Parse loop body
+ ```
+
+6. After the loop body is parsed, we go back and process all the phis we created earlier.
+
+ ```java
+ // The true branch loops back, so whatever is current control gets
+ // added to head loop as input
+ head.endLoop(_scope, exit);
+ ```
+
+ The `endLoop` method sets the second control of the loop region to the control from the back edge.
+ It then goes through all the phis and sets the second data input on the phi to the corresponding entry
+ from the loop body; phis that were not used are peepholed and get replaced by the original input.
+
+ ```java
+ Node ctrl = ctrl();
+ ctrl.set_def(2,back.ctrl());
+ for( int i=1; i<nIns(); i++ ) {
+ PhiNode phi = (PhiNode)in(i);
+ assert phi.region()==ctrl && phi.in(2)==null;
+ phi.set_def(2,back.in(i));
+ // Do an eager useless-phi removal
+ Node in = phi.peephole();
+ if( in != phi )
+ phi.subsume(in);
+ }
+ ```
+
+7. Finally, both the original scope (head) we started with, and the duplicate created for the body are killed.
+ At exit the false control is the current control, and exit scope is set as the current scope.
+
+### Visualization
+
+The example quoted above is shown below at an intermediate state:
+
+![Graph1](./docs/07-graph1.svg)
+
+* Three Scopes are shown, reading clockwise, the loop head, exit and the body.
+
+The final graph looks like this:
+
+![Graph2](./docs/07-graph2.svg)
+
+## More Complex Examples
+
+### Nested Loops
+
+```java
+int sum = 0;
+int i = 0;
+while(i < arg) {
+ i = i + 1;
+ int j = 0;
+ while( j < arg ) {
+ sum = sum + j;
+ j = j + 1;
+ }
+}
+return sum;
+```
+
+![Graph2](./docs/07-graph3.svg)
+
+### Loop With Nested If
+
+```java
+int a = 1;
+int b = 2;
+while(a < 10) {
+ if (a == 2) a = 3;
+ else b = 4;
+}
+return b;
+```
+
+![Graph2](./docs/07-graph4.svg)
+
+
+[^1]: Cytron, R. et al (1991).
+ Efficiently computing static single assignment form and the control dependence graph, in ACM Transactions on Programming Languages and Systems, 13(4):451-490, 1991.
+
+[^2]: Click, C. (1995).
+ Combining Analyses, Combining Optimizations, 103. \ No newline at end of file
diff --git a/chapter07/docs/07-grammar.md b/chapter07/docs/07-grammar.md
new file mode 100644
index 00000000..50a2a129
--- /dev/null
+++ b/chapter07/docs/07-grammar.md
@@ -0,0 +1,88 @@
+# Grammar for Chapter 6
+
+```antlrv4
+grammar SimpleLanguage;
+
+program
+ : statement+ EOF
+ ;
+
+statement
+ : returnStatement
+ | declStatement
+ | blockStatment
+ | expressionStatement
+ | ifStatement
+ | whileStatement
+ | metaStatement
+ ;
+
+whileStatement
+ : 'while' '(' expression ')' statement
+ ;
+
+ifStatement
+ : 'if' '(' expression ')' statement ('else' statement)?
+ ;
+
+metaStatement
+ : '#showGraph' ';'
+ ;
+
+expressionStatement
+ : IDENTIFIER '=' expression ';'
+ ;
+
+blockStatement
+ : '{' statement+ '}'
+ ;
+
+declStatement
+ : 'int' IDENTIFIER '=' expression ';'
+ ;
+
+returnStatement
+ : 'return' expression ';'
+ ;
+
+expression
+ : comparisonExpression
+ ;
+
+comparisonExpression
+ : additiveExpression (('==' | '!='| '>'| '<'| '>='| '<=') additiveExpression)*
+ ;
+
+additiveExpression
+ : multiplicativeExpression (('+' | '-') multiplicativeExpression)*
+ ;
+
+multiplicativeExpression
+ : unaryExpression (('*' | '/') unaryExpression)*
+ ;
+
+unaryExpression
+ : ('-') unaryExpression
+ | primaryExpression
+ ;
+
+primaryExpression
+ : IDENTIFIER
+ | INTEGER_LITERAL
+ | 'true'
+ | 'false'
+ | '(' expression ')'
+ ;
+
+INTEGER_LITERAL
+ : [1-9][0-9]*
+ | [0]
+ ;
+
+IDENTIFIER
+ : NON_DIGIT (NON_DIGIT | DIGIT)*
+ ;
+
+NON_DIGIT: [a-zA-Z_];
+DEC_DIGIT: [0-9];
+``` \ No newline at end of file
diff --git a/chapter07/docs/07-graph1.gv b/chapter07/docs/07-graph1.gv
new file mode 100644
index 00000000..ba806a28
--- /dev/null
+++ b/chapter07/docs/07-graph1.gv
@@ -0,0 +1,107 @@
+digraph chapter07 {
+/*
+while(arg < 10) {
+ arg = arg + 1;
+ #showGraph;
+}
+return arg;
+
+*/
+ rankdir=BT;
+ ordering="in";
+ concentrate="true";
+ compound="true";
+ subgraph cluster_Nodes {
+ Start2 [ shape=plaintext label=<
+ <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4">
+ <TR><TD BGCOLOR="yellow">Start</TD></TR>
+ <TR><TD>
+ <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
+ <TR><TD PORT="p0" BGCOLOR="yellow">$ctrl</TD><TD PORT="p1">arg</TD></TR>
+ </TABLE>
+ </TD></TR>
+ </TABLE>>
+ ];
+ Loop6 [ shape=box style=filled fillcolor=yellow label="Loop" ];
+ Phi_arg8 [ style=filled fillcolor=lightyellow label="&phi;_arg" ];
+ Con_9 [ label="10" ];
+ LT10 [ label="<" ];
+ If11 [ shape=plaintext label=<
+ <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4">
+ <TR><TD BGCOLOR="yellow">If</TD></TR>
+ <TR><TD>
+ <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
+ <TR><TD PORT="p0" BGCOLOR="yellow">True</TD><TD PORT="p1" BGCOLOR="yellow">False</TD></TR>
+ </TABLE>
+ </TD></TR>
+ </TABLE>>
+ ];
+ Con_15 [ label="1" ];
+ Add16 [ label="+" ];
+ { rank=same; Loop6;Phi_arg8;}
+ }
+ node [shape=plaintext];
+ subgraph cluster_Scope7_1 {
+ Scope7_1 [label=<
+ <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
+ <TR><TD BGCOLOR="cyan">2</TD></TR>
+ </TABLE>>];
+ subgraph cluster_Scope7_2 {
+ Scope7_2 [label=<
+ <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
+ <TR><TD BGCOLOR="cyan">1</TD></TR>
+ </TABLE>>];
+ subgraph cluster_Scope7_3 {
+ Scope7_3 [label=<
+ <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
+ <TR><TD BGCOLOR="cyan">0</TD><TD PORT="Scope7_3_$ctrl">$ctrl</TD><TD PORT="Scope7_3_arg">arg</TD></TR>
+ </TABLE>>];
+ }
+ }
+ }
+ node [shape=plaintext];
+ subgraph cluster_Scope1_1 {
+ Scope1_1 [label=<
+ <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
+ <TR><TD BGCOLOR="cyan">1</TD></TR>
+ </TABLE>>];
+ subgraph cluster_Scope1_2 {
+ Scope1_2 [label=<
+ <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
+ <TR><TD BGCOLOR="cyan">0</TD><TD PORT="Scope1_2_$ctrl">$ctrl</TD><TD PORT="Scope1_2_arg">arg</TD></TR>
+ </TABLE>>];
+ }
+ }
+ node [shape=plaintext];
+ subgraph cluster_Scope14_1 {
+ Scope14_1 [label=<
+ <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
+ <TR><TD BGCOLOR="cyan">1</TD></TR>
+ </TABLE>>];
+ subgraph cluster_Scope14_2 {
+ Scope14_2 [label=<
+ <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
+ <TR><TD BGCOLOR="cyan">0</TD><TD PORT="Scope14_2_$ctrl">$ctrl</TD><TD PORT="Scope14_2_arg">arg</TD></TR>
+ </TABLE>>];
+ }
+ }
+ edge [ fontname=Helvetica, fontsize=8 ];
+ Loop6 -> Start2:p0[taillabel=1 color=red];
+ Phi_arg8 -> Loop6 [style=dotted taillabel=0];
+ Phi_arg8 -> Start2:p1[taillabel=1];
+ LT10 -> Phi_arg8[taillabel=1];
+ LT10 -> Con_9[taillabel=2];
+ If11 -> Loop6[taillabel=0 color=red];
+ If11 -> LT10[taillabel=1];
+ Add16 -> Phi_arg8[taillabel=1];
+ Add16 -> Con_15[taillabel=2];
+ edge [style=dashed color=cornflowerblue];
+ Scope7_3:"Scope7_3_$ctrl" -> If11:p0;
+ Scope7_3:"Scope7_3_arg" -> Add16;
+ edge [style=dashed color=cornflowerblue];
+ Scope1_2:"Scope1_2_$ctrl" -> Loop6;
+ Scope1_2:"Scope1_2_arg" -> Phi_arg8;
+ edge [style=dashed color=cornflowerblue];
+ Scope14_2:"Scope14_2_$ctrl" -> If11:p1;
+ Scope14_2:"Scope14_2_arg" -> Phi_arg8;
+}
diff --git a/chapter07/docs/07-graph1.svg b/chapter07/docs/07-graph1.svg
new file mode 100644
index 00000000..94248cfb
--- /dev/null
+++ b/chapter07/docs/07-graph1.svg
@@ -0,0 +1,268 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.43.0 (0)
+ -->
+<!-- Title: chapter07 Pages: 1 -->
+<svg width="726pt" height="440pt"
+ viewBox="0.00 0.00 726.00 440.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 436)">
+<title>chapter07</title>
+<polygon fill="white" stroke="transparent" points="-4,4 -4,-436 722,-436 722,4 -4,4"/>
+<g id="clust1" class="cluster">
+<title>cluster_Nodes</title>
+<polygon fill="none" stroke="black" points="196,-100 196,-424 502,-424 502,-100 196,-100"/>
+</g>
+<g id="clust3" class="cluster">
+<title>cluster_Scope7_1</title>
+<polygon fill="none" stroke="black" points="8,-8 8,-92 288,-92 288,-8 8,-8"/>
+</g>
+<g id="clust4" class="cluster">
+<title>cluster_Scope7_2</title>
+<polygon fill="none" stroke="black" points="80,-16 80,-84 280,-84 280,-16 80,-16"/>
+</g>
+<g id="clust5" class="cluster">
+<title>cluster_Scope7_3</title>
+<polygon fill="none" stroke="black" points="152,-24 152,-76 272,-76 272,-24 152,-24"/>
+</g>
+<g id="clust6" class="cluster">
+<title>cluster_Scope1_1</title>
+<polygon fill="none" stroke="black" points="510,-192 510,-260 710,-260 710,-192 510,-192"/>
+</g>
+<g id="clust7" class="cluster">
+<title>cluster_Scope1_2</title>
+<polygon fill="none" stroke="black" points="582,-200 582,-252 702,-252 702,-200 582,-200"/>
+</g>
+<g id="clust8" class="cluster">
+<title>cluster_Scope14_1</title>
+<polygon fill="none" stroke="black" points="296,-16 296,-84 496,-84 496,-16 296,-16"/>
+</g>
+<g id="clust9" class="cluster">
+<title>cluster_Scope14_2</title>
+<polygon fill="none" stroke="black" points="368,-24 368,-76 488,-76 488,-24 368,-24"/>
+</g>
+<!-- Start2 -->
+<g id="node1" class="node">
+<title>Start2</title>
+<polygon fill="yellow" stroke="transparent" points="365,-387 365,-412 446,-412 446,-387 365,-387"/>
+<polygon fill="none" stroke="black" points="365,-387 365,-412 446,-412 446,-387 365,-387"/>
+<text text-anchor="start" x="387" y="-395.8" font-family="Times,serif" font-size="14.00">Start</text>
+<polygon fill="none" stroke="black" points="365,-356 365,-387 446,-387 446,-356 365,-356"/>
+<polygon fill="yellow" stroke="transparent" points="370,-361 370,-382 410,-382 410,-361 370,-361"/>
+<polygon fill="none" stroke="black" points="370,-361 370,-382 410,-382 410,-361 370,-361"/>
+<text text-anchor="start" x="373" y="-367.8" font-family="Times,serif" font-size="14.00">$ctrl</text>
+<polygon fill="none" stroke="black" points="410,-361 410,-382 441,-382 441,-361 410,-361"/>
+<text text-anchor="start" x="413" y="-367.8" font-family="Times,serif" font-size="14.00">arg</text>
+</g>
+<!-- Loop6 -->
+<g id="node2" class="node">
+<title>Loop6</title>
+<polygon fill="yellow" stroke="black" points="494,-316 440,-316 440,-280 494,-280 494,-316"/>
+<text text-anchor="middle" x="467" y="-294.3" font-family="Times,serif" font-size="14.00">Loop</text>
+</g>
+<!-- Loop6&#45;&gt;Start2 -->
+<g id="edge1" class="edge">
+<title>Loop6&#45;&gt;Start2:p0</title>
+<path fill="none" stroke="red" d="M439.64,-314.34C422.52,-324.68 402.19,-338.54 393.85,-350.32"/>
+<polygon fill="red" stroke="red" points="390.45,-349.41 390,-360 396.95,-352 390.45,-349.41"/>
+<text text-anchor="middle" x="436.64" y="-307.94" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- Phi_arg8 -->
+<g id="node3" class="node">
+<title>Phi_arg8</title>
+<ellipse fill="lightyellow" stroke="black" cx="385" cy="-298" rx="37.09" ry="18"/>
+<text text-anchor="middle" x="385" y="-294.3" font-family="Times,serif" font-size="14.00">φ_arg</text>
+</g>
+<!-- Phi_arg8&#45;&gt;Start2 -->
+<g id="edge3" class="edge">
+<title>Phi_arg8&#45;&gt;Start2:p1</title>
+<path fill="none" stroke="black" d="M402.63,-314.22C411.57,-323.35 421.25,-335.86 424.7,-349.71"/>
+<polygon fill="black" stroke="black" points="421.27,-350.52 426,-360 428.22,-349.64 421.27,-350.52"/>
+<text text-anchor="middle" x="399.63" y="-316.82" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- Phi_arg8&#45;&gt;Loop6 -->
+<g id="edge2" class="edge">
+<title>Phi_arg8&#45;&gt;Loop6</title>
+<path fill="none" stroke="black" stroke-dasharray="1,5" d="M422.16,-298C424.62,-298 427.08,-298 429.55,-298"/>
+<polygon fill="black" stroke="black" points="429.67,-301.5 439.67,-298 429.67,-294.5 429.67,-301.5"/>
+<text text-anchor="middle" x="425.16" y="-291.6" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
+</g>
+<!-- Con_9 -->
+<g id="node4" class="node">
+<title>Con_9</title>
+<ellipse fill="none" stroke="black" cx="303" cy="-298" rx="27" ry="18"/>
+<text text-anchor="middle" x="303" y="-294.3" font-family="Times,serif" font-size="14.00">10</text>
+</g>
+<!-- LT10 -->
+<g id="node5" class="node">
+<title>LT10</title>
+<ellipse fill="none" stroke="black" cx="374" cy="-226" rx="27" ry="18"/>
+<text text-anchor="middle" x="374" y="-222.3" font-family="Times,serif" font-size="14.00">&lt;</text>
+</g>
+<!-- LT10&#45;&gt;Phi_arg8 -->
+<g id="edge4" class="edge">
+<title>LT10&#45;&gt;Phi_arg8</title>
+<path fill="none" stroke="black" d="M376.66,-243.95C377.87,-251.65 379.34,-260.97 380.7,-269.64"/>
+<polygon fill="black" stroke="black" points="377.28,-270.39 382.29,-279.72 384.19,-269.3 377.28,-270.39"/>
+<text text-anchor="middle" x="373.66" y="-246.55" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- LT10&#45;&gt;Con_9 -->
+<g id="edge5" class="edge">
+<title>LT10&#45;&gt;Con_9</title>
+<path fill="none" stroke="black" d="M359.29,-241.5C349.39,-251.27 336.16,-264.31 325.01,-275.3"/>
+<polygon fill="black" stroke="black" points="322.36,-273 317.69,-282.51 327.27,-277.99 322.36,-273"/>
+<text text-anchor="middle" x="356.29" y="-244.1" font-family="Helvetica,sans-Serif" font-size="8.00">2</text>
+</g>
+<!-- If11 -->
+<g id="node6" class="node">
+<title>If11</title>
+<polygon fill="yellow" stroke="transparent" points="327,-143 327,-168 421,-168 421,-143 327,-143"/>
+<polygon fill="none" stroke="black" points="327,-143 327,-168 421,-168 421,-143 327,-143"/>
+<text text-anchor="start" x="368.5" y="-151.8" font-family="Times,serif" font-size="14.00">If</text>
+<polygon fill="none" stroke="black" points="327,-112 327,-143 421,-143 421,-112 327,-112"/>
+<polygon fill="yellow" stroke="transparent" points="332,-117 332,-138 372,-138 372,-117 332,-117"/>
+<polygon fill="none" stroke="black" points="332,-117 332,-138 372,-138 372,-117 332,-117"/>
+<text text-anchor="start" x="335" y="-123.8" font-family="Times,serif" font-size="14.00">True</text>
+<polygon fill="yellow" stroke="transparent" points="372,-117 372,-138 416,-138 416,-117 372,-117"/>
+<polygon fill="none" stroke="black" points="372,-117 372,-138 416,-138 416,-117 372,-117"/>
+<text text-anchor="start" x="375" y="-123.8" font-family="Times,serif" font-size="14.00">False</text>
+</g>
+<!-- If11&#45;&gt;Loop6 -->
+<g id="edge6" class="edge">
+<title>If11&#45;&gt;Loop6</title>
+<path fill="none" stroke="red" d="M396.87,-172.17C401.36,-178.63 405.94,-185.46 410,-192 426.23,-218.13 442.79,-249.22 453.84,-270.75"/>
+<polygon fill="red" stroke="red" points="450.85,-272.6 458.51,-279.92 457.09,-269.42 450.85,-272.6"/>
+<text text-anchor="middle" x="393.87" y="-174.77" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
+</g>
+<!-- If11&#45;&gt;LT10 -->
+<g id="edge7" class="edge">
+<title>If11&#45;&gt;LT10</title>
+<path fill="none" stroke="black" d="M374,-172.28C374,-180.58 374,-189.48 374,-197.56"/>
+<polygon fill="black" stroke="black" points="370.5,-197.6 374,-207.6 377.5,-197.6 370.5,-197.6"/>
+<text text-anchor="middle" x="371" y="-174.88" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- Con_15 -->
+<g id="node7" class="node">
+<title>Con_15</title>
+<ellipse fill="none" stroke="black" cx="231" cy="-298" rx="27" ry="18"/>
+<text text-anchor="middle" x="231" y="-294.3" font-family="Times,serif" font-size="14.00">1</text>
+</g>
+<!-- Add16 -->
+<g id="node8" class="node">
+<title>Add16</title>
+<ellipse fill="none" stroke="black" cx="241" cy="-226" rx="27" ry="18"/>
+<text text-anchor="middle" x="241" y="-222.3" font-family="Times,serif" font-size="14.00">+</text>
+</g>
+<!-- Add16&#45;&gt;Phi_arg8 -->
+<g id="edge8" class="edge">
+<title>Add16&#45;&gt;Phi_arg8</title>
+<path fill="none" stroke="black" d="M262.25,-237.33C285.48,-248.62 323.06,-266.89 350.53,-280.24"/>
+<polygon fill="black" stroke="black" points="349.26,-283.52 359.78,-284.74 352.32,-277.22 349.26,-283.52"/>
+<text text-anchor="middle" x="265.25" y="-239.93" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- Add16&#45;&gt;Con_15 -->
+<g id="edge9" class="edge">
+<title>Add16&#45;&gt;Con_15</title>
+<path fill="none" stroke="black" d="M238.58,-243.95C237.48,-251.65 236.15,-260.97 234.91,-269.64"/>
+<polygon fill="black" stroke="black" points="231.42,-269.33 233.47,-279.72 238.35,-270.32 231.42,-269.33"/>
+<text text-anchor="middle" x="235.58" y="-246.55" font-family="Helvetica,sans-Serif" font-size="8.00">2</text>
+</g>
+<!-- Scope7_1 -->
+<g id="node9" class="node">
+<title>Scope7_1</title>
+<polygon fill="cyan" stroke="transparent" points="35,-39 35,-60 51,-60 51,-39 35,-39"/>
+<polygon fill="none" stroke="black" points="35,-39 35,-60 51,-60 51,-39 35,-39"/>
+<text text-anchor="start" x="38" y="-45.8" font-family="Times,serif" font-size="14.00">2</text>
+</g>
+<!-- Scope7_2 -->
+<g id="node10" class="node">
+<title>Scope7_2</title>
+<polygon fill="cyan" stroke="transparent" points="107,-39 107,-60 123,-60 123,-39 107,-39"/>
+<polygon fill="none" stroke="black" points="107,-39 107,-60 123,-60 123,-39 107,-39"/>
+<text text-anchor="start" x="110" y="-45.8" font-family="Times,serif" font-size="14.00">1</text>
+</g>
+<!-- Scope7_3 -->
+<g id="node11" class="node">
+<title>Scope7_3</title>
+<polygon fill="cyan" stroke="transparent" points="169,-39 169,-60 185,-60 185,-39 169,-39"/>
+<polygon fill="none" stroke="black" points="169,-39 169,-60 185,-60 185,-39 169,-39"/>
+<text text-anchor="start" x="172" y="-45.8" font-family="Times,serif" font-size="14.00">0</text>
+<polygon fill="none" stroke="black" points="185,-39 185,-60 225,-60 225,-39 185,-39"/>
+<text text-anchor="start" x="188" y="-45.8" font-family="Times,serif" font-size="14.00">$ctrl</text>
+<polygon fill="none" stroke="black" points="225,-39 225,-60 256,-60 256,-39 225,-39"/>
+<text text-anchor="start" x="228" y="-45.8" font-family="Times,serif" font-size="14.00">arg</text>
+</g>
+<!-- Scope7_3&#45;&gt;If11 -->
+<g id="edge10" class="edge">
+<title>Scope7_3:Scope7_3_$ctrl&#45;&gt;If11:p0</title>
+<path fill="none" stroke="cornflowerblue" stroke-dasharray="5,2" d="M205,-61C205,-74.83 289.37,-114.99 321.05,-124.81"/>
+<polygon fill="cornflowerblue" stroke="cornflowerblue" points="320.48,-128.27 331,-127 321.98,-121.43 320.48,-128.27"/>
+</g>
+<!-- Scope7_3&#45;&gt;Add16 -->
+<g id="edge11" class="edge">
+<title>Scope7_3:Scope7_3_arg&#45;&gt;Add16</title>
+<path fill="none" stroke="cornflowerblue" stroke-dasharray="5,2" d="M241,-61C241,-109.09 241,-165.12 241,-197.67"/>
+<polygon fill="cornflowerblue" stroke="cornflowerblue" points="237.5,-197.76 241,-207.76 244.5,-197.76 237.5,-197.76"/>
+</g>
+<!-- Scope1_1 -->
+<g id="node12" class="node">
+<title>Scope1_1</title>
+<polygon fill="cyan" stroke="transparent" points="537,-215 537,-236 553,-236 553,-215 537,-215"/>
+<polygon fill="none" stroke="black" points="537,-215 537,-236 553,-236 553,-215 537,-215"/>
+<text text-anchor="start" x="540" y="-221.8" font-family="Times,serif" font-size="14.00">1</text>
+</g>
+<!-- Scope1_2 -->
+<g id="node13" class="node">
+<title>Scope1_2</title>
+<polygon fill="cyan" stroke="transparent" points="599,-215 599,-236 615,-236 615,-215 599,-215"/>
+<polygon fill="none" stroke="black" points="599,-215 599,-236 615,-236 615,-215 599,-215"/>
+<text text-anchor="start" x="602" y="-221.8" font-family="Times,serif" font-size="14.00">0</text>
+<polygon fill="none" stroke="black" points="615,-215 615,-236 655,-236 655,-215 615,-215"/>
+<text text-anchor="start" x="618" y="-221.8" font-family="Times,serif" font-size="14.00">$ctrl</text>
+<polygon fill="none" stroke="black" points="655,-215 655,-236 686,-236 686,-215 655,-215"/>
+<text text-anchor="start" x="658" y="-221.8" font-family="Times,serif" font-size="14.00">arg</text>
+</g>
+<!-- Scope1_2&#45;&gt;Loop6 -->
+<g id="edge12" class="edge">
+<title>Scope1_2:Scope1_2_$ctrl&#45;&gt;Loop6</title>
+<path fill="none" stroke="cornflowerblue" stroke-dasharray="5,2" d="M635,-237C635,-264.54 553.92,-282.77 504.41,-291.34"/>
+<polygon fill="cornflowerblue" stroke="cornflowerblue" points="503.65,-287.92 494.37,-293.02 504.81,-294.82 503.65,-287.92"/>
+</g>
+<!-- Scope1_2&#45;&gt;Phi_arg8 -->
+<g id="edge13" class="edge">
+<title>Scope1_2:Scope1_2_arg&#45;&gt;Phi_arg8</title>
+<path fill="none" stroke="cornflowerblue" stroke-dasharray="5,2" d="M671,-237C671,-275.71 624.96,-252.41 587,-260 518.46,-273.71 498.88,-263.33 431,-280 428.04,-280.73 425,-281.58 421.97,-282.52"/>
+<polygon fill="cornflowerblue" stroke="cornflowerblue" points="420.79,-279.23 412.41,-285.72 423.01,-285.86 420.79,-279.23"/>
+</g>
+<!-- Scope14_1 -->
+<g id="node14" class="node">
+<title>Scope14_1</title>
+<polygon fill="cyan" stroke="transparent" points="323,-39 323,-60 339,-60 339,-39 323,-39"/>
+<polygon fill="none" stroke="black" points="323,-39 323,-60 339,-60 339,-39 323,-39"/>
+<text text-anchor="start" x="326" y="-45.8" font-family="Times,serif" font-size="14.00">1</text>
+</g>
+<!-- Scope14_2 -->
+<g id="node15" class="node">
+<title>Scope14_2</title>
+<polygon fill="cyan" stroke="transparent" points="385,-39 385,-60 401,-60 401,-39 385,-39"/>
+<polygon fill="none" stroke="black" points="385,-39 385,-60 401,-60 401,-39 385,-39"/>
+<text text-anchor="start" x="388" y="-45.8" font-family="Times,serif" font-size="14.00">0</text>
+<polygon fill="none" stroke="black" points="401,-39 401,-60 441,-60 441,-39 401,-39"/>
+<text text-anchor="start" x="404" y="-45.8" font-family="Times,serif" font-size="14.00">$ctrl</text>
+<polygon fill="none" stroke="black" points="441,-39 441,-60 472,-60 472,-39 441,-39"/>
+<text text-anchor="start" x="444" y="-45.8" font-family="Times,serif" font-size="14.00">arg</text>
+</g>
+<!-- Scope14_2&#45;&gt;Phi_arg8 -->
+<g id="edge15" class="edge">
+<title>Scope14_2:Scope14_2_arg&#45;&gt;Phi_arg8</title>
+<path fill="none" stroke="cornflowerblue" stroke-dasharray="5,2" d="M457,-61C457,-151.88 449.62,-178.21 410,-260 408.01,-264.11 405.61,-268.3 403.09,-272.3"/>
+<polygon fill="cornflowerblue" stroke="cornflowerblue" points="400.15,-270.4 397.51,-280.67 405.97,-274.28 400.15,-270.4"/>
+</g>
+<!-- Scope14_2&#45;&gt;If11 -->
+<g id="edge14" class="edge">
+<title>Scope14_2:Scope14_2_$ctrl&#45;&gt;If11:p1</title>
+<path fill="none" stroke="cornflowerblue" stroke-dasharray="5,2" d="M421,-61C421,-84.4 401.06,-88.09 395.45,-105.95"/>
+<polygon fill="cornflowerblue" stroke="cornflowerblue" points="391.97,-105.6 394,-116 398.89,-106.6 391.97,-105.6"/>
+</g>
+</g>
+</svg>
diff --git a/chapter07/docs/07-graph2.gv b/chapter07/docs/07-graph2.gv
new file mode 100644
index 00000000..f4cfcb45
--- /dev/null
+++ b/chapter07/docs/07-graph2.gv
@@ -0,0 +1,62 @@
+digraph chapter07 {
+/*
+while(arg < 10) {
+ arg = arg + 1;
+ #showGraph;
+}
+return arg;
+
+*/
+ rankdir=BT;
+ ordering="in";
+ concentrate="true";
+ compound="true";
+ subgraph cluster_Nodes {
+ Start2 [ shape=plaintext label=<
+ <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4">
+ <TR><TD BGCOLOR="yellow">Start</TD></TR>
+ <TR><TD>
+ <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
+ <TR><TD PORT="p0" BGCOLOR="yellow">$ctrl</TD><TD PORT="p1">arg</TD></TR>
+ </TABLE>
+ </TD></TR>
+ </TABLE>>
+ ];
+ Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ];
+ Loop6 [ shape=box style=filled fillcolor=yellow label="Loop" ];
+ Phi_arg8 [ style=filled fillcolor=lightyellow label="&phi;_arg" ];
+ Con_9 [ label="10" ];
+ LT10 [ label="<" ];
+ If11 [ shape=plaintext label=<
+ <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4">
+ <TR><TD BGCOLOR="yellow">If</TD></TR>
+ <TR><TD>
+ <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
+ <TR><TD PORT="p0" BGCOLOR="yellow">True</TD><TD PORT="p1" BGCOLOR="yellow">False</TD></TR>
+ </TABLE>
+ </TD></TR>
+ </TABLE>>
+ ];
+ Con_15 [ label="1" ];
+ Add16 [ label="+" ];
+ Return17 [ shape=box style=filled fillcolor=yellow label="Return" ];
+ { rank=same; Loop6;Phi_arg8;}
+ }
+ node [shape=plaintext];
+ edge [ fontname=Helvetica, fontsize=8 ];
+ Stop3 -> Return17[taillabel=0 color=red];
+ Loop6 -> Start2:p0[taillabel=1 color=red];
+ Loop6 -> If11:p0[taillabel=2 color=red constraint=false];
+ Phi_arg8 -> Loop6 [style=dotted taillabel=0];
+ Phi_arg8 -> Start2:p1[taillabel=1];
+ Phi_arg8 -> Add16[taillabel=2 constraint=false];
+ LT10 -> Phi_arg8[taillabel=1];
+ LT10 -> Con_9[taillabel=2];
+ If11 -> Loop6[taillabel=0 color=red];
+ If11 -> LT10[taillabel=1];
+ Add16 -> Phi_arg8[taillabel=1];
+ Add16 -> Con_15[taillabel=2];
+ Return17 -> If11:p1[taillabel=0 color=red];
+ Return17 -> Phi_arg8[taillabel=1];
+ edge [style=dashed color=cornflowerblue];
+} \ No newline at end of file
diff --git a/chapter07/docs/07-graph2.svg b/chapter07/docs/07-graph2.svg
new file mode 100644
index 00000000..1fec9556
--- /dev/null
+++ b/chapter07/docs/07-graph2.svg
@@ -0,0 +1,184 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.43.0 (0)
+ -->
+<!-- Title: chapter07 Pages: 1 -->
+<svg width="330pt" height="492pt"
+ viewBox="0.00 0.00 330.00 492.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 488)">
+<title>chapter07</title>
+<polygon fill="white" stroke="transparent" points="-4,4 -4,-488 326,-488 326,4 -4,4"/>
+<g id="clust1" class="cluster">
+<title>cluster_Nodes</title>
+<polygon fill="none" stroke="black" points="8,-8 8,-476 314,-476 314,-8 8,-8"/>
+</g>
+<!-- Start2 -->
+<g id="node1" class="node">
+<title>Start2</title>
+<polygon fill="yellow" stroke="transparent" points="141,-439 141,-464 222,-464 222,-439 141,-439"/>
+<polygon fill="none" stroke="black" points="141,-439 141,-464 222,-464 222,-439 141,-439"/>
+<text text-anchor="start" x="163" y="-447.8" font-family="Times,serif" font-size="14.00">Start</text>
+<polygon fill="none" stroke="black" points="141,-408 141,-439 222,-439 222,-408 141,-408"/>
+<polygon fill="yellow" stroke="transparent" points="146,-413 146,-434 186,-434 186,-413 146,-413"/>
+<polygon fill="none" stroke="black" points="146,-413 146,-434 186,-434 186,-413 146,-413"/>
+<text text-anchor="start" x="149" y="-419.8" font-family="Times,serif" font-size="14.00">$ctrl</text>
+<polygon fill="none" stroke="black" points="186,-413 186,-434 217,-434 217,-413 186,-413"/>
+<text text-anchor="start" x="189" y="-419.8" font-family="Times,serif" font-size="14.00">arg</text>
+</g>
+<!-- Stop3 -->
+<g id="node2" class="node">
+<title>Stop3</title>
+<polygon fill="yellow" stroke="black" points="153,-52 99,-52 99,-16 153,-16 153,-52"/>
+<text text-anchor="middle" x="126" y="-30.3" font-family="Times,serif" font-size="14.00">Stop</text>
+</g>
+<!-- Return17 -->
+<g id="node10" class="node">
+<title>Return17</title>
+<polygon fill="yellow" stroke="black" points="159,-124 93,-124 93,-88 159,-88 159,-124"/>
+<text text-anchor="middle" x="126" y="-102.3" font-family="Times,serif" font-size="14.00">Return</text>
+</g>
+<!-- Stop3&#45;&gt;Return17 -->
+<g id="edge1" class="edge">
+<title>Stop3&#45;&gt;Return17</title>
+<path fill="none" stroke="red" d="M126,-52.3C126,-60.02 126,-69.29 126,-77.89"/>
+<polygon fill="red" stroke="red" points="122.5,-77.9 126,-87.9 129.5,-77.9 122.5,-77.9"/>
+<text text-anchor="middle" x="123" y="-54.9" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
+</g>
+<!-- Loop6 -->
+<g id="node3" class="node">
+<title>Loop6</title>
+<polygon fill="yellow" stroke="black" points="306,-368 252,-368 252,-332 306,-332 306,-368"/>
+<text text-anchor="middle" x="279" y="-346.3" font-family="Times,serif" font-size="14.00">Loop</text>
+</g>
+<!-- Loop6&#45;&gt;Start2 -->
+<g id="edge2" class="edge">
+<title>Loop6&#45;&gt;Start2:p0</title>
+<path fill="none" stroke="red" d="M251.93,-364.13C226.34,-376.71 189.56,-395.24 173.73,-405.41"/>
+<polygon fill="red" stroke="red" points="171.34,-402.85 166,-412 175.88,-408.18 171.34,-402.85"/>
+<text text-anchor="middle" x="248.93" y="-357.73" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- If11 -->
+<g id="node7" class="node">
+<title>If11</title>
+<polygon fill="yellow" stroke="transparent" points="32,-195 32,-220 126,-220 126,-195 32,-195"/>
+<polygon fill="none" stroke="black" points="32,-195 32,-220 126,-220 126,-195 32,-195"/>
+<text text-anchor="start" x="73.5" y="-203.8" font-family="Times,serif" font-size="14.00">If</text>
+<polygon fill="none" stroke="black" points="32,-164 32,-195 126,-195 126,-164 32,-164"/>
+<polygon fill="yellow" stroke="transparent" points="37,-169 37,-190 77,-190 77,-169 37,-169"/>
+<polygon fill="none" stroke="black" points="37,-169 37,-190 77,-190 77,-169 37,-169"/>
+<text text-anchor="start" x="40" y="-175.8" font-family="Times,serif" font-size="14.00">True</text>
+<polygon fill="yellow" stroke="transparent" points="77,-169 77,-190 121,-190 121,-169 77,-169"/>
+<polygon fill="none" stroke="black" points="77,-169 77,-190 121,-190 121,-169 77,-169"/>
+<text text-anchor="start" x="80" y="-175.8" font-family="Times,serif" font-size="14.00">False</text>
+</g>
+<!-- Loop6&#45;&gt;If11 -->
+<g id="edge3" class="edge">
+<title>Loop6&#45;&gt;If11:p0</title>
+<path fill="none" stroke="red" d="M251.78,-335.24C248.86,-334.04 245.9,-332.93 243,-332 156.98,-304.48 103.76,-362.82 43,-296 18.41,-268.96 14.64,-203.69 27.49,-184.44"/>
+<polygon fill="red" stroke="red" points="29.46,-187.33 36,-179 25.69,-181.43 29.46,-187.33"/>
+<text text-anchor="middle" x="248.78" y="-328.84" font-family="Helvetica,sans-Serif" font-size="8.00">2</text>
+</g>
+<!-- Phi_arg8 -->
+<g id="node4" class="node">
+<title>Phi_arg8</title>
+<ellipse fill="lightyellow" stroke="black" cx="125" cy="-350" rx="37.09" ry="18"/>
+<text text-anchor="middle" x="125" y="-346.3" font-family="Times,serif" font-size="14.00">φ_arg</text>
+</g>
+<!-- Phi_arg8&#45;&gt;Start2 -->
+<g id="edge5" class="edge">
+<title>Phi_arg8&#45;&gt;Start2:p1</title>
+<path fill="none" stroke="black" d="M154.55,-360.93C173.21,-369 194.84,-382.27 200.56,-401.87"/>
+<polygon fill="black" stroke="black" points="197.12,-402.59 202,-412 204.05,-401.61 197.12,-402.59"/>
+<text text-anchor="middle" x="151.55" y="-363.53" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- Phi_arg8&#45;&gt;Loop6 -->
+<g id="edge4" class="edge">
+<title>Phi_arg8&#45;&gt;Loop6</title>
+<path fill="none" stroke="black" stroke-dasharray="1,5" d="M143.47,-334.01C153.54,-326.34 166.66,-317.98 180,-314 203,-307.14 211.35,-306.06 234,-314 241.14,-316.5 247.99,-320.67 254.09,-325.3"/>
+<polygon fill="black" stroke="black" points="251.99,-328.1 261.92,-331.78 256.45,-322.71 251.99,-328.1"/>
+<text text-anchor="middle" x="140.47" y="-327.61" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
+</g>
+<!-- Add16 -->
+<g id="node9" class="node">
+<title>Add16</title>
+<ellipse fill="none" stroke="black" cx="203" cy="-278" rx="27" ry="18"/>
+<text text-anchor="middle" x="203" y="-274.3" font-family="Times,serif" font-size="14.00">+</text>
+</g>
+<!-- Con_9 -->
+<g id="node5" class="node">
+<title>Con_9</title>
+<ellipse fill="none" stroke="black" cx="43" cy="-350" rx="27" ry="18"/>
+<text text-anchor="middle" x="43" y="-346.3" font-family="Times,serif" font-size="14.00">10</text>
+</g>
+<!-- LT10 -->
+<g id="node6" class="node">
+<title>LT10</title>
+<ellipse fill="none" stroke="black" cx="79" cy="-278" rx="27" ry="18"/>
+<text text-anchor="middle" x="79" y="-274.3" font-family="Times,serif" font-size="14.00">&lt;</text>
+</g>
+<!-- LT10&#45;&gt;Phi_arg8 -->
+<g id="edge7" class="edge">
+<title>LT10&#45;&gt;Phi_arg8</title>
+<path fill="none" stroke="black" d="M89.44,-294.88C95.12,-303.53 102.28,-314.42 108.68,-324.17"/>
+<polygon fill="black" stroke="black" points="105.87,-326.26 114.28,-332.69 111.72,-322.41 105.87,-326.26"/>
+<text text-anchor="middle" x="86.44" y="-297.48" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- LT10&#45;&gt;Con_9 -->
+<g id="edge8" class="edge">
+<title>LT10&#45;&gt;Con_9</title>
+<path fill="none" stroke="black" d="M70.65,-295.24C66.29,-303.72 60.85,-314.29 55.96,-323.8"/>
+<polygon fill="black" stroke="black" points="52.77,-322.36 51.3,-332.85 58.99,-325.56 52.77,-322.36"/>
+<text text-anchor="middle" x="67.65" y="-297.84" font-family="Helvetica,sans-Serif" font-size="8.00">2</text>
+</g>
+<!-- If11&#45;&gt;Loop6 -->
+<g id="edge9" class="edge">
+<title>If11&#45;&gt;Loop6</title>
+<path fill="none" stroke="red" d="M134.07,-205.07C167.93,-214.93 210.21,-231.94 239,-260 256.28,-276.84 266.71,-302.48 272.57,-321.93"/>
+<polygon fill="red" stroke="red" points="269.24,-323.03 275.3,-331.72 275.98,-321.15 269.24,-323.03"/>
+<text text-anchor="middle" x="137.07" y="-198.67" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
+</g>
+<!-- If11&#45;&gt;LT10 -->
+<g id="edge10" class="edge">
+<title>If11&#45;&gt;LT10</title>
+<path fill="none" stroke="black" d="M79,-224.28C79,-232.58 79,-241.48 79,-249.56"/>
+<polygon fill="black" stroke="black" points="75.5,-249.6 79,-259.6 82.5,-249.6 75.5,-249.6"/>
+<text text-anchor="middle" x="76" y="-226.88" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- Con_15 -->
+<g id="node8" class="node">
+<title>Con_15</title>
+<ellipse fill="none" stroke="black" cx="207" cy="-350" rx="27" ry="18"/>
+<text text-anchor="middle" x="207" y="-346.3" font-family="Times,serif" font-size="14.00">1</text>
+</g>
+<!-- Add16&#45;&gt;Phi_arg8 -->
+<g id="edge11" class="edge">
+<title>Add16&#45;&gt;Phi_arg8</title>
+<path fill="none" stroke="black" d="M180.01,-299.63C170.41,-308.25 159.23,-318.28 149.47,-327.04"/>
+<polygon fill="black" stroke="black" points="182.48,-302.11 187.59,-292.83 177.81,-296.9 182.48,-302.11"/>
+<polygon fill="black" stroke="black" points="147.11,-324.46 142,-333.74 151.78,-329.67 147.11,-324.46"/>
+<text text-anchor="middle" x="184.59" y="-295.43" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- Add16&#45;&gt;Con_15 -->
+<g id="edge12" class="edge">
+<title>Add16&#45;&gt;Con_15</title>
+<path fill="none" stroke="black" d="M203.99,-296.3C204.43,-304.02 204.96,-313.29 205.45,-321.89"/>
+<polygon fill="black" stroke="black" points="201.96,-322.11 206.02,-331.9 208.95,-321.71 201.96,-322.11"/>
+<text text-anchor="middle" x="200.99" y="-298.9" font-family="Helvetica,sans-Serif" font-size="8.00">2</text>
+</g>
+<!-- Return17&#45;&gt;Phi_arg8 -->
+<g id="edge14" class="edge">
+<title>Return17&#45;&gt;Phi_arg8</title>
+<path fill="none" stroke="black" d="M133.07,-124.33C136.84,-134.53 141.09,-147.78 143,-160 151.97,-217.35 140.08,-285.24 131.79,-322.03"/>
+<polygon fill="black" stroke="black" points="128.33,-321.44 129.46,-331.97 135.14,-323.03 128.33,-321.44"/>
+<text text-anchor="middle" x="130.07" y="-126.93" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- Return17&#45;&gt;If11 -->
+<g id="edge13" class="edge">
+<title>Return17&#45;&gt;If11:p1</title>
+<path fill="none" stroke="red" d="M113.92,-124C108.25,-133.3 102.24,-145.44 99.95,-157.94"/>
+<polygon fill="red" stroke="red" points="96.46,-157.71 99,-168 103.43,-158.38 96.46,-157.71"/>
+<text text-anchor="middle" x="110.92" y="-126.6" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
+</g>
+</g>
+</svg>
diff --git a/chapter07/docs/07-graph3.gv b/chapter07/docs/07-graph3.gv
new file mode 100644
index 00000000..4482100b
--- /dev/null
+++ b/chapter07/docs/07-graph3.gv
@@ -0,0 +1,108 @@
+digraph chapter07 {
+/*
+int sum = 0;
+int i = 0;
+while(i < arg) {
+ i = i + 1;
+ int j = 0;
+ while( j < arg ) {
+ sum = sum + j;
+ j = j + 1;
+ }
+}
+return sum;
+
+*/
+ rankdir=BT;
+ ordering="in";
+ concentrate="true";
+ compound="true";
+ subgraph cluster_Nodes {
+ Start2 [ shape=plaintext label=<
+ <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4">
+ <TR><TD BGCOLOR="yellow">Start</TD></TR>
+ <TR><TD>
+ <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
+ <TR><TD PORT="p0" BGCOLOR="yellow">$ctrl</TD><TD PORT="p1">arg</TD></TR>
+ </TABLE>
+ </TD></TR>
+ </TABLE>>
+ ];
+ Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ];
+ Con_6 [ label="0" ];
+ Con_7 [ label="0" ];
+ Loop8 [ shape=box style=filled fillcolor=yellow label="Loop" ];
+ Phi_sum11 [ style=filled fillcolor=lightyellow label="&phi;_sum" ];
+ Phi_i12 [ style=filled fillcolor=lightyellow label="&phi;_i" ];
+ LT13 [ label="<" ];
+ If14 [ shape=plaintext label=<
+ <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4">
+ <TR><TD BGCOLOR="yellow">If</TD></TR>
+ <TR><TD>
+ <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
+ <TR><TD PORT="p0" BGCOLOR="yellow">True</TD><TD PORT="p1" BGCOLOR="yellow">False</TD></TR>
+ </TABLE>
+ </TD></TR>
+ </TABLE>>
+ ];
+ Con_18 [ label="1" ];
+ Add19 [ label="+" ];
+ Con_20 [ label="0" ];
+ Loop21 [ shape=box style=filled fillcolor=yellow label="Loop" ];
+ Phi_sum24 [ style=filled fillcolor=lightyellow label="&phi;_sum" ];
+ Phi_j26 [ style=filled fillcolor=lightyellow label="&phi;_j" ];
+ LT27 [ label="<" ];
+ If28 [ shape=plaintext label=<
+ <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4">
+ <TR><TD BGCOLOR="yellow">If</TD></TR>
+ <TR><TD>
+ <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
+ <TR><TD PORT="p0" BGCOLOR="yellow">True</TD><TD PORT="p1" BGCOLOR="yellow">False</TD></TR>
+ </TABLE>
+ </TD></TR>
+ </TABLE>>
+ ];
+ Add32 [ label="+" ];
+ Con_33 [ label="1" ];
+ Add34 [ label="+" ];
+ Return35 [ shape=box style=filled fillcolor=yellow label="Return" ];
+ { rank=same; Loop8;Phi_sum11;Phi_i12;}
+ { rank=same; Loop21;Phi_sum24;Phi_j26;}
+ }
+ node [shape=plaintext];
+ edge [ fontname=Helvetica, fontsize=8 ];
+ Stop3 -> Return35[taillabel=0 color=red];
+ Loop8 -> Start2:p0[taillabel=1 color=red];
+ Loop8 -> If28:p1[taillabel=2 color=red constraint=false];
+ Phi_sum11 -> Loop8 [style=dotted taillabel=0];
+ Phi_sum11 -> Con_6[taillabel=1];
+ Phi_sum11 -> Phi_sum24[taillabel=2 constraint=false];
+ Phi_i12 -> Loop8 [style=dotted taillabel=0];
+ Phi_i12 -> Con_7[taillabel=1];
+ Phi_i12 -> Add19[taillabel=2 constraint=false];
+ LT13 -> Phi_i12[taillabel=1];
+ LT13 -> Start2:p1[taillabel=2];
+ If14 -> Loop8[taillabel=0 color=red];
+ If14 -> LT13[taillabel=1];
+ Add19 -> Phi_i12[taillabel=1];
+ Add19 -> Con_18[taillabel=2];
+ Loop21 -> If14:p0[taillabel=1 color=red];
+ Loop21 -> If28:p0[taillabel=2 color=red constraint=false];
+ Phi_sum24 -> Loop21 [style=dotted taillabel=0];
+ Phi_sum24 -> Phi_sum11[taillabel=1];
+ Phi_sum24 -> Add32[taillabel=2 constraint=false];
+ Phi_j26 -> Loop21 [style=dotted taillabel=0];
+ Phi_j26 -> Con_20[taillabel=1];
+ Phi_j26 -> Add34[taillabel=2 constraint=false];
+ LT27 -> Phi_j26[taillabel=1];
+ LT27 -> Start2:p1[taillabel=2];
+ If28 -> Loop21[taillabel=0 color=red];
+ If28 -> LT27[taillabel=1];
+ Add32 -> Phi_j26[taillabel=1];
+ Add32 -> Phi_sum24[taillabel=2];
+ Add34 -> Phi_j26[taillabel=1];
+ Add34 -> Con_33[taillabel=2];
+ Return35 -> If14:p1[taillabel=0 color=red];
+ Return35 -> Phi_sum11[taillabel=1];
+ edge [style=dashed color=cornflowerblue];
+} \ No newline at end of file
diff --git a/chapter07/docs/07-graph3.svg b/chapter07/docs/07-graph3.svg
new file mode 100644
index 00000000..a9a0e8d0
--- /dev/null
+++ b/chapter07/docs/07-graph3.svg
@@ -0,0 +1,373 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.43.0 (0)
+ -->
+<!-- Title: chapter07 Pages: 1 -->
+<svg width="462pt" height="592pt"
+ viewBox="0.00 0.00 462.00 592.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 588)">
+<title>chapter07</title>
+<polygon fill="white" stroke="transparent" points="-4,4 -4,-588 458,-588 458,4 -4,4"/>
+<g id="clust1" class="cluster">
+<title>cluster_Nodes</title>
+<polygon fill="none" stroke="black" points="8,-8 8,-576 446,-576 446,-8 8,-8"/>
+</g>
+<!-- Start2 -->
+<g id="node1" class="node">
+<title>Start2</title>
+<polygon fill="yellow" stroke="transparent" points="148,-539 148,-564 229,-564 229,-539 148,-539"/>
+<polygon fill="none" stroke="black" points="148,-539 148,-564 229,-564 229,-539 148,-539"/>
+<text text-anchor="start" x="170" y="-547.8" font-family="Times,serif" font-size="14.00">Start</text>
+<polygon fill="none" stroke="black" points="148,-508 148,-539 229,-539 229,-508 148,-508"/>
+<polygon fill="yellow" stroke="transparent" points="153,-513 153,-534 193,-534 193,-513 153,-513"/>
+<polygon fill="none" stroke="black" points="153,-513 153,-534 193,-534 193,-513 153,-513"/>
+<text text-anchor="start" x="156" y="-519.8" font-family="Times,serif" font-size="14.00">$ctrl</text>
+<polygon fill="none" stroke="black" points="193,-513 193,-534 224,-534 224,-513 193,-513"/>
+<text text-anchor="start" x="196" y="-519.8" font-family="Times,serif" font-size="14.00">arg</text>
+</g>
+<!-- Stop3 -->
+<g id="node2" class="node">
+<title>Stop3</title>
+<polygon fill="yellow" stroke="black" points="432,-152 378,-152 378,-116 432,-116 432,-152"/>
+<text text-anchor="middle" x="405" y="-130.3" font-family="Times,serif" font-size="14.00">Stop</text>
+</g>
+<!-- Return35 -->
+<g id="node21" class="node">
+<title>Return35</title>
+<polygon fill="yellow" stroke="black" points="438,-224 372,-224 372,-188 438,-188 438,-224"/>
+<text text-anchor="middle" x="405" y="-202.3" font-family="Times,serif" font-size="14.00">Return</text>
+</g>
+<!-- Stop3&#45;&gt;Return35 -->
+<g id="edge1" class="edge">
+<title>Stop3&#45;&gt;Return35</title>
+<path fill="none" stroke="red" d="M405,-152.3C405,-160.02 405,-169.29 405,-177.89"/>
+<polygon fill="red" stroke="red" points="401.5,-177.9 405,-187.9 408.5,-177.9 401.5,-177.9"/>
+<text text-anchor="middle" x="402" y="-154.9" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
+</g>
+<!-- Con_6 -->
+<g id="node3" class="node">
+<title>Con_6</title>
+<ellipse fill="none" stroke="black" cx="94" cy="-536" rx="27" ry="18"/>
+<text text-anchor="middle" x="94" y="-532.3" font-family="Times,serif" font-size="14.00">0</text>
+</g>
+<!-- Con_7 -->
+<g id="node4" class="node">
+<title>Con_7</title>
+<ellipse fill="none" stroke="black" cx="282" cy="-536" rx="27" ry="18"/>
+<text text-anchor="middle" x="282" y="-532.3" font-family="Times,serif" font-size="14.00">0</text>
+</g>
+<!-- Loop8 -->
+<g id="node5" class="node">
+<title>Loop8</title>
+<polygon fill="yellow" stroke="black" points="426,-468 372,-468 372,-432 426,-432 426,-468"/>
+<text text-anchor="middle" x="399" y="-446.3" font-family="Times,serif" font-size="14.00">Loop</text>
+</g>
+<!-- Loop8&#45;&gt;Start2 -->
+<g id="edge2" class="edge">
+<title>Loop8&#45;&gt;Start2:p0</title>
+<path fill="none" stroke="red" d="M371.74,-464.61C368.82,-465.84 365.87,-467 363,-468 325.58,-481 197.82,-470.79 176.14,-502.03"/>
+<polygon fill="red" stroke="red" points="172.67,-501.41 173,-512 179.34,-503.51 172.67,-501.41"/>
+<text text-anchor="middle" x="368.74" y="-458.21" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- If28 -->
+<g id="node17" class="node">
+<title>If28</title>
+<polygon fill="yellow" stroke="transparent" points="142,-51 142,-76 236,-76 236,-51 142,-51"/>
+<polygon fill="none" stroke="black" points="142,-51 142,-76 236,-76 236,-51 142,-51"/>
+<text text-anchor="start" x="183.5" y="-59.8" font-family="Times,serif" font-size="14.00">If</text>
+<polygon fill="none" stroke="black" points="142,-20 142,-51 236,-51 236,-20 142,-20"/>
+<polygon fill="yellow" stroke="transparent" points="147,-25 147,-46 187,-46 187,-25 147,-25"/>
+<polygon fill="none" stroke="black" points="147,-25 147,-46 187,-46 187,-25 147,-25"/>
+<text text-anchor="start" x="150" y="-31.8" font-family="Times,serif" font-size="14.00">True</text>
+<polygon fill="yellow" stroke="transparent" points="187,-25 187,-46 231,-46 231,-25 187,-25"/>
+<polygon fill="none" stroke="black" points="187,-25 187,-46 231,-46 231,-25 187,-25"/>
+<text text-anchor="start" x="190" y="-31.8" font-family="Times,serif" font-size="14.00">False</text>
+</g>
+<!-- Loop8&#45;&gt;If28 -->
+<g id="edge3" class="edge">
+<title>Loop8&#45;&gt;If28:p1</title>
+<path fill="none" stroke="red" d="M371.72,-435.44C368.81,-434.2 365.86,-433.02 363,-432 300.87,-409.81 259.6,-448 219,-396 176.78,-341.93 243.11,-308.12 292,-260 311.77,-240.54 330.24,-248.63 343,-224 350.36,-209.79 353.65,-199.94 343,-188 248.88,-82.54 121.12,-257.46 27,-152 16.35,-140.06 16.65,-128.2 27,-116 90.22,-41.43 180.78,-154.57 244,-80 259.81,-61.35 261.04,-42.53 244,-25 236.29,-17.07 221.85,-12.04 214.15,-15.39"/>
+<polygon fill="red" stroke="red" points="211.13,-13.62 209,-24 217.14,-17.22 211.13,-13.62"/>
+<text text-anchor="middle" x="368.72" y="-429.04" font-family="Helvetica,sans-Serif" font-size="8.00">2</text>
+</g>
+<!-- Phi_sum11 -->
+<g id="node6" class="node">
+<title>Phi_sum11</title>
+<ellipse fill="lightyellow" stroke="black" cx="149" cy="-450" rx="40.89" ry="18"/>
+<text text-anchor="middle" x="149" y="-446.3" font-family="Times,serif" font-size="14.00">φ_sum</text>
+</g>
+<!-- Phi_sum11&#45;&gt;Con_6 -->
+<g id="edge5" class="edge">
+<title>Phi_sum11&#45;&gt;Con_6</title>
+<path fill="none" stroke="black" d="M138.13,-467.6C130.07,-479.91 118.95,-496.89 109.89,-510.73"/>
+<polygon fill="black" stroke="black" points="106.84,-509.01 104.29,-519.29 112.69,-512.84 106.84,-509.01"/>
+<text text-anchor="middle" x="135.13" y="-470.2" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- Phi_sum11&#45;&gt;Loop8 -->
+<g id="edge4" class="edge">
+<title>Phi_sum11&#45;&gt;Loop8</title>
+<path fill="none" stroke="black" stroke-dasharray="1,5" d="M173.66,-435.44C188.78,-427.38 208.85,-418.16 228,-414 282.73,-402.12 301.16,-395.46 354,-414 361.14,-416.5 367.99,-420.67 374.09,-425.3"/>
+<polygon fill="black" stroke="black" points="371.99,-428.1 381.92,-431.78 376.45,-422.71 371.99,-428.1"/>
+<text text-anchor="middle" x="170.66" y="-429.04" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
+</g>
+<!-- Phi_sum24 -->
+<g id="node14" class="node">
+<title>Phi_sum24</title>
+<ellipse fill="lightyellow" stroke="black" cx="149" cy="-206" rx="40.89" ry="18"/>
+<text text-anchor="middle" x="149" y="-202.3" font-family="Times,serif" font-size="14.00">φ_sum</text>
+</g>
+<!-- Phi_i12 -->
+<g id="node7" class="node">
+<title>Phi_i12</title>
+<ellipse fill="lightyellow" stroke="black" cx="255" cy="-450" rx="27" ry="18"/>
+<text text-anchor="middle" x="255" y="-446.3" font-family="Times,serif" font-size="14.00">φ_i</text>
+</g>
+<!-- Phi_i12&#45;&gt;Con_7 -->
+<g id="edge8" class="edge">
+<title>Phi_i12&#45;&gt;Con_7</title>
+<path fill="none" stroke="black" d="M260.46,-468C264.22,-479.69 269.28,-495.44 273.56,-508.73"/>
+<polygon fill="black" stroke="black" points="270.24,-509.86 276.64,-518.31 276.91,-507.72 270.24,-509.86"/>
+<text text-anchor="middle" x="257.46" y="-470.6" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- Phi_i12&#45;&gt;Loop8 -->
+<g id="edge7" class="edge">
+<title>Phi_i12&#45;&gt;Loop8</title>
+<path fill="none" stroke="black" stroke-dasharray="1,5" d="M269.22,-434.43C277.37,-426.68 288.29,-418.11 300,-414 322.65,-406.06 331.35,-406.06 354,-414 361.14,-416.5 367.99,-420.67 374.09,-425.3"/>
+<polygon fill="black" stroke="black" points="371.99,-428.1 381.92,-431.78 376.45,-422.71 371.99,-428.1"/>
+<text text-anchor="middle" x="266.22" y="-428.03" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
+</g>
+<!-- Add19 -->
+<g id="node11" class="node">
+<title>Add19</title>
+<ellipse fill="none" stroke="black" cx="327" cy="-378" rx="27" ry="18"/>
+<text text-anchor="middle" x="327" y="-374.3" font-family="Times,serif" font-size="14.00">+</text>
+</g>
+<!-- LT13 -->
+<g id="node8" class="node">
+<title>LT13</title>
+<ellipse fill="none" stroke="black" cx="255" cy="-378" rx="27" ry="18"/>
+<text text-anchor="middle" x="255" y="-374.3" font-family="Times,serif" font-size="14.00">&lt;</text>
+</g>
+<!-- LT13&#45;&gt;Start2 -->
+<g id="edge11" class="edge">
+<title>LT13&#45;&gt;Start2:p1</title>
+<path fill="none" stroke="black" d="M241.85,-394.14C233.87,-404.19 224.26,-418.05 219,-432 207.64,-462.12 208.62,-473.77 208.94,-501.81"/>
+<polygon fill="black" stroke="black" points="205.44,-502.02 209,-512 212.44,-501.98 205.44,-502.02"/>
+<text text-anchor="middle" x="238.85" y="-396.74" font-family="Helvetica,sans-Serif" font-size="8.00">2</text>
+</g>
+<!-- LT13&#45;&gt;Phi_i12 -->
+<g id="edge10" class="edge">
+<title>LT13&#45;&gt;Phi_i12</title>
+<path fill="none" stroke="black" d="M255,-396.3C255,-404.02 255,-413.29 255,-421.89"/>
+<polygon fill="black" stroke="black" points="251.5,-421.9 255,-431.9 258.5,-421.9 251.5,-421.9"/>
+<text text-anchor="middle" x="252" y="-398.9" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- If14 -->
+<g id="node9" class="node">
+<title>If14</title>
+<polygon fill="yellow" stroke="transparent" points="309,-295 309,-320 403,-320 403,-295 309,-295"/>
+<polygon fill="none" stroke="black" points="309,-295 309,-320 403,-320 403,-295 309,-295"/>
+<text text-anchor="start" x="350.5" y="-303.8" font-family="Times,serif" font-size="14.00">If</text>
+<polygon fill="none" stroke="black" points="309,-264 309,-295 403,-295 403,-264 309,-264"/>
+<polygon fill="yellow" stroke="transparent" points="314,-269 314,-290 354,-290 354,-269 314,-269"/>
+<polygon fill="none" stroke="black" points="314,-269 314,-290 354,-290 354,-269 314,-269"/>
+<text text-anchor="start" x="317" y="-275.8" font-family="Times,serif" font-size="14.00">True</text>
+<polygon fill="yellow" stroke="transparent" points="354,-269 354,-290 398,-290 398,-269 354,-269"/>
+<polygon fill="none" stroke="black" points="354,-269 354,-290 398,-290 398,-269 354,-269"/>
+<text text-anchor="start" x="357" y="-275.8" font-family="Times,serif" font-size="14.00">False</text>
+</g>
+<!-- If14&#45;&gt;Loop8 -->
+<g id="edge12" class="edge">
+<title>If14&#45;&gt;Loop8</title>
+<path fill="none" stroke="red" d="M364.6,-324.19C372.47,-352.77 384.02,-394.64 391.52,-421.86"/>
+<polygon fill="red" stroke="red" points="388.24,-423.15 394.27,-431.86 394.99,-421.29 388.24,-423.15"/>
+<text text-anchor="middle" x="361.6" y="-326.79" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
+</g>
+<!-- If14&#45;&gt;LT13 -->
+<g id="edge13" class="edge">
+<title>If14&#45;&gt;LT13</title>
+<path fill="none" stroke="black" d="M318.68,-324.04C305.44,-335.05 290.9,-347.14 279.06,-356.99"/>
+<polygon fill="black" stroke="black" points="276.79,-354.32 271.34,-363.41 281.27,-359.7 276.79,-354.32"/>
+<text text-anchor="middle" x="315.68" y="-326.64" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- Con_18 -->
+<g id="node10" class="node">
+<title>Con_18</title>
+<ellipse fill="none" stroke="black" cx="327" cy="-450" rx="27" ry="18"/>
+<text text-anchor="middle" x="327" y="-446.3" font-family="Times,serif" font-size="14.00">1</text>
+</g>
+<!-- Add19&#45;&gt;Phi_i12 -->
+<g id="edge14" class="edge">
+<title>Add19&#45;&gt;Phi_i12</title>
+<path fill="none" stroke="black" d="M304.96,-400.43C296.13,-409.01 285.99,-418.87 277.15,-427.46"/>
+<polygon fill="black" stroke="black" points="307.7,-402.65 312.43,-393.17 302.82,-397.63 307.7,-402.65"/>
+<polygon fill="black" stroke="black" points="274.53,-425.13 269.8,-434.62 279.41,-430.15 274.53,-425.13"/>
+<text text-anchor="middle" x="309.43" y="-395.77" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- Add19&#45;&gt;Con_18 -->
+<g id="edge15" class="edge">
+<title>Add19&#45;&gt;Con_18</title>
+<path fill="none" stroke="black" d="M327,-396.3C327,-404.02 327,-413.29 327,-421.89"/>
+<polygon fill="black" stroke="black" points="323.5,-421.9 327,-431.9 330.5,-421.9 323.5,-421.9"/>
+<text text-anchor="middle" x="324" y="-398.9" font-family="Helvetica,sans-Serif" font-size="8.00">2</text>
+</g>
+<!-- Con_20 -->
+<g id="node12" class="node">
+<title>Con_20</title>
+<ellipse fill="none" stroke="black" cx="63" cy="-292" rx="27" ry="18"/>
+<text text-anchor="middle" x="63" y="-288.3" font-family="Times,serif" font-size="14.00">0</text>
+</g>
+<!-- Loop21 -->
+<g id="node13" class="node">
+<title>Loop21</title>
+<polygon fill="yellow" stroke="black" points="334,-224 280,-224 280,-188 334,-188 334,-224"/>
+<text text-anchor="middle" x="307" y="-202.3" font-family="Times,serif" font-size="14.00">Loop</text>
+</g>
+<!-- Loop21&#45;&gt;If14 -->
+<g id="edge16" class="edge">
+<title>Loop21&#45;&gt;If14:p0</title>
+<path fill="none" stroke="red" d="M319.08,-224C324.75,-233.3 330.76,-245.44 333.05,-257.94"/>
+<polygon fill="red" stroke="red" points="329.57,-258.38 334,-268 336.54,-257.71 329.57,-258.38"/>
+<text text-anchor="middle" x="316.08" y="-226.6" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- Loop21&#45;&gt;If28 -->
+<g id="edge17" class="edge">
+<title>Loop21&#45;&gt;If28:p0</title>
+<path fill="none" stroke="red" d="M279.81,-191.15C276.88,-189.96 273.91,-188.88 271,-188 218.54,-172.13 62.82,-193.49 27,-152 -19.29,-98.38 63.73,-40.46 135.9,-35.36"/>
+<polygon fill="red" stroke="red" points="136.13,-38.86 146,-35 135.88,-31.86 136.13,-38.86"/>
+<text text-anchor="middle" x="276.81" y="-184.75" font-family="Helvetica,sans-Serif" font-size="8.00">2</text>
+</g>
+<!-- Phi_sum24&#45;&gt;Phi_sum11 -->
+<g id="edge19" class="edge">
+<title>Phi_sum24&#45;&gt;Phi_sum11</title>
+<path fill="none" stroke="black" d="M149,-234.1C149,-280.5 149,-374.66 149,-421.4"/>
+<polygon fill="black" stroke="black" points="152.5,-234.02 149,-224.02 145.5,-234.02 152.5,-234.02"/>
+<polygon fill="black" stroke="black" points="145.5,-421.68 149,-431.68 152.5,-421.68 145.5,-421.68"/>
+<text text-anchor="middle" x="146" y="-226.62" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- Phi_sum24&#45;&gt;Loop21 -->
+<g id="edge18" class="edge">
+<title>Phi_sum24&#45;&gt;Loop21</title>
+<path fill="none" stroke="black" stroke-dasharray="1,5" d="M168.72,-190.18C179.62,-182.46 193.79,-173.99 208,-170 231.11,-163.51 239.35,-162.06 262,-170 269.14,-172.5 275.99,-176.67 282.09,-181.3"/>
+<polygon fill="black" stroke="black" points="279.99,-184.1 289.92,-187.78 284.45,-178.71 279.99,-184.1"/>
+<text text-anchor="middle" x="165.72" y="-183.78" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
+</g>
+<!-- Add32 -->
+<g id="node18" class="node">
+<title>Add32</title>
+<ellipse fill="none" stroke="black" cx="142" cy="-134" rx="27" ry="18"/>
+<text text-anchor="middle" x="142" y="-130.3" font-family="Times,serif" font-size="14.00">+</text>
+</g>
+<!-- Phi_j26 -->
+<g id="node15" class="node">
+<title>Phi_j26</title>
+<ellipse fill="lightyellow" stroke="black" cx="63" cy="-206" rx="27" ry="18"/>
+<text text-anchor="middle" x="63" y="-202.3" font-family="Times,serif" font-size="14.00">φ_j</text>
+</g>
+<!-- Phi_j26&#45;&gt;Con_20 -->
+<g id="edge22" class="edge">
+<title>Phi_j26&#45;&gt;Con_20</title>
+<path fill="none" stroke="black" d="M63,-224.4C63,-235.74 63,-250.77 63,-263.68"/>
+<polygon fill="black" stroke="black" points="59.5,-263.9 63,-273.9 66.5,-263.9 59.5,-263.9"/>
+<text text-anchor="middle" x="60" y="-227" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- Phi_j26&#45;&gt;Loop21 -->
+<g id="edge21" class="edge">
+<title>Phi_j26&#45;&gt;Loop21</title>
+<path fill="none" stroke="black" stroke-dasharray="1,5" d="M77.22,-190.43C85.37,-182.68 96.29,-174.11 108,-170 172.59,-147.34 197.41,-147.34 262,-170 269.14,-172.5 275.99,-176.67 282.09,-181.3"/>
+<polygon fill="black" stroke="black" points="279.99,-184.1 289.92,-187.78 284.45,-178.71 279.99,-184.1"/>
+<text text-anchor="middle" x="74.22" y="-184.03" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
+</g>
+<!-- Add34 -->
+<g id="node20" class="node">
+<title>Add34</title>
+<ellipse fill="none" stroke="black" cx="225" cy="-134" rx="27" ry="18"/>
+<text text-anchor="middle" x="225" y="-130.3" font-family="Times,serif" font-size="14.00">+</text>
+</g>
+<!-- LT27 -->
+<g id="node16" class="node">
+<title>LT27</title>
+<ellipse fill="none" stroke="black" cx="63" cy="-134" rx="27" ry="18"/>
+<text text-anchor="middle" x="63" y="-130.3" font-family="Times,serif" font-size="14.00">&lt;</text>
+</g>
+<!-- LT27&#45;&gt;Start2 -->
+<g id="edge25" class="edge">
+<title>LT27&#45;&gt;Start2:p1</title>
+<path fill="none" stroke="black" d="M49.72,-150.09C41.69,-160.12 32.07,-173.98 27,-188 11.37,-231.26 17,-245.01 17,-291 17,-291 17,-291 17,-379 17,-432.79 54.71,-437.48 99,-468 139.14,-495.66 199.24,-464.69 207.94,-501.72"/>
+<polygon fill="black" stroke="black" points="204.49,-502.41 209,-512 211.45,-501.69 204.49,-502.41"/>
+<text text-anchor="middle" x="46.72" y="-152.69" font-family="Helvetica,sans-Serif" font-size="8.00">2</text>
+</g>
+<!-- LT27&#45;&gt;Phi_j26 -->
+<g id="edge24" class="edge">
+<title>LT27&#45;&gt;Phi_j26</title>
+<path fill="none" stroke="black" d="M63,-152.3C63,-160.02 63,-169.29 63,-177.89"/>
+<polygon fill="black" stroke="black" points="59.5,-177.9 63,-187.9 66.5,-177.9 59.5,-177.9"/>
+<text text-anchor="middle" x="60" y="-154.9" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- If28&#45;&gt;Loop21 -->
+<g id="edge26" class="edge">
+<title>If28&#45;&gt;Loop21</title>
+<path fill="none" stroke="red" d="M227.35,-80.17C239.01,-90.79 251.3,-103.21 261,-116 275.71,-135.38 288.07,-160.15 296.27,-178.63"/>
+<polygon fill="red" stroke="red" points="293.1,-180.12 300.27,-187.92 299.53,-177.35 293.1,-180.12"/>
+<text text-anchor="middle" x="224.35" y="-82.77" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
+</g>
+<!-- If28&#45;&gt;LT27 -->
+<g id="edge27" class="edge">
+<title>If28&#45;&gt;LT27</title>
+<path fill="none" stroke="black" d="M142.44,-80.04C124.74,-91.84 105.18,-104.88 89.9,-115.07"/>
+<polygon fill="black" stroke="black" points="87.82,-112.24 81.44,-120.7 91.71,-118.07 87.82,-112.24"/>
+<text text-anchor="middle" x="139.44" y="-82.64" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- Add32&#45;&gt;Phi_sum24 -->
+<g id="edge29" class="edge">
+<title>Add32&#45;&gt;Phi_sum24</title>
+<path fill="none" stroke="black" d="M144.73,-162.33C145.24,-167.45 145.78,-172.79 146.29,-177.9"/>
+<polygon fill="black" stroke="black" points="148.21,-161.91 143.73,-152.3 141.24,-162.6 148.21,-161.91"/>
+<polygon fill="black" stroke="black" points="142.81,-178.29 147.29,-187.9 149.78,-177.6 142.81,-178.29"/>
+<text text-anchor="middle" x="140.73" y="-154.9" font-family="Helvetica,sans-Serif" font-size="8.00">2</text>
+</g>
+<!-- Add32&#45;&gt;Phi_j26 -->
+<g id="edge28" class="edge">
+<title>Add32&#45;&gt;Phi_j26</title>
+<path fill="none" stroke="black" d="M126.39,-148.83C114.92,-158.99 99.15,-172.97 86.21,-184.44"/>
+<polygon fill="black" stroke="black" points="83.74,-181.95 78.58,-191.2 88.38,-187.19 83.74,-181.95"/>
+<text text-anchor="middle" x="123.39" y="-151.43" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- Con_33 -->
+<g id="node19" class="node">
+<title>Con_33</title>
+<ellipse fill="none" stroke="black" cx="235" cy="-206" rx="27" ry="18"/>
+<text text-anchor="middle" x="235" y="-202.3" font-family="Times,serif" font-size="14.00">1</text>
+</g>
+<!-- Add34&#45;&gt;Phi_j26 -->
+<g id="edge30" class="edge">
+<title>Add34&#45;&gt;Phi_j26</title>
+<path fill="none" stroke="black" d="M193.01,-148.01C167.45,-158.51 130.76,-173.83 99,-188 97.2,-188.8 95.36,-189.64 93.5,-190.49"/>
+<polygon fill="black" stroke="black" points="194.64,-151.12 202.57,-144.09 191.99,-144.65 194.64,-151.12"/>
+<polygon fill="black" stroke="black" points="91.85,-187.39 84.25,-194.78 94.79,-193.74 91.85,-187.39"/>
+<text text-anchor="middle" x="199.57" y="-146.69" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- Add34&#45;&gt;Con_33 -->
+<g id="edge31" class="edge">
+<title>Add34&#45;&gt;Con_33</title>
+<path fill="none" stroke="black" d="M227.42,-151.95C228.52,-159.65 229.85,-168.97 231.09,-177.64"/>
+<polygon fill="black" stroke="black" points="227.65,-178.32 232.53,-187.72 234.58,-177.33 227.65,-178.32"/>
+<text text-anchor="middle" x="224.42" y="-154.55" font-family="Helvetica,sans-Serif" font-size="8.00">2</text>
+</g>
+<!-- Return35&#45;&gt;Phi_sum11 -->
+<g id="edge33" class="edge">
+<title>Return35&#45;&gt;Phi_sum11</title>
+<path fill="none" stroke="black" d="M371.93,-220.42C343.47,-232.34 305.01,-249.46 292,-260 233.39,-307.47 185.4,-383.86 162.87,-423.42"/>
+<polygon fill="black" stroke="black" points="159.79,-421.75 157.95,-432.19 165.89,-425.18 159.79,-421.75"/>
+<text text-anchor="middle" x="368.93" y="-214.02" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- Return35&#45;&gt;If14 -->
+<g id="edge32" class="edge">
+<title>Return35&#45;&gt;If14:p1</title>
+<path fill="none" stroke="red" d="M391.86,-224.13C385.8,-233.37 379.43,-245.41 377.01,-257.92"/>
+<polygon fill="red" stroke="red" points="373.52,-257.7 376,-268 380.48,-258.4 373.52,-257.7"/>
+<text text-anchor="middle" x="388.86" y="-226.73" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
+</g>
+</g>
+</svg>
diff --git a/chapter07/docs/07-graph4.gv b/chapter07/docs/07-graph4.gv
new file mode 100644
index 00000000..24ace3da
--- /dev/null
+++ b/chapter07/docs/07-graph4.gv
@@ -0,0 +1,96 @@
+digraph chapter07 {
+/*
+int a = 1;
+int b = 2;
+while(a < 10) {
+ if (a == 2) a = 3;
+ else b = 4;
+}
+return b;
+
+*/
+ rankdir=BT;
+ ordering="in";
+ concentrate="true";
+ compound="true";
+ subgraph cluster_Nodes {
+ Start2 [ shape=plaintext label=<
+ <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4">
+ <TR><TD BGCOLOR="yellow">Start</TD></TR>
+ <TR><TD>
+ <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
+ <TR><TD PORT="p0" BGCOLOR="yellow">$ctrl</TD></TR>
+ </TABLE>
+ </TD></TR>
+ </TABLE>>
+ ];
+ Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ];
+ Con_6 [ label="1" ];
+ Con_7 [ label="2" ];
+ Loop8 [ shape=box style=filled fillcolor=yellow label="Loop" ];
+ Phi_a11 [ style=filled fillcolor=lightyellow label="&phi;_a" ];
+ Phi_b12 [ style=filled fillcolor=lightyellow label="&phi;_b" ];
+ Con_13 [ label="10" ];
+ LT14 [ label="<" ];
+ If15 [ shape=plaintext label=<
+ <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4">
+ <TR><TD BGCOLOR="yellow">If</TD></TR>
+ <TR><TD>
+ <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
+ <TR><TD PORT="p0" BGCOLOR="yellow">True</TD><TD PORT="p1" BGCOLOR="yellow">False</TD></TR>
+ </TABLE>
+ </TD></TR>
+ </TABLE>>
+ ];
+ Con_19 [ label="2" ];
+ EQ20 [ label="==" ];
+ If21 [ shape=plaintext label=<
+ <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4">
+ <TR><TD BGCOLOR="yellow">If</TD></TR>
+ <TR><TD>
+ <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
+ <TR><TD PORT="p0" BGCOLOR="yellow">True</TD><TD PORT="p1" BGCOLOR="yellow">False</TD></TR>
+ </TABLE>
+ </TD></TR>
+ </TABLE>>
+ ];
+ Con_25 [ label="3" ];
+ Con_26 [ label="4" ];
+ Region27 [ shape=box style=filled fillcolor=yellow label="Region" ];
+ Phi_a28 [ style=filled fillcolor=lightyellow label="&phi;_a" ];
+ Phi_b29 [ style=filled fillcolor=lightyellow label="&phi;_b" ];
+ Return30 [ shape=box style=filled fillcolor=yellow label="Return" ];
+ { rank=same; Loop8;Phi_a11;Phi_b12;}
+ { rank=same; Region27;Phi_b29;Phi_a28;}
+ }
+ node [shape=plaintext];
+ edge [ fontname=Helvetica, fontsize=8 ];
+ Stop3 -> Return30[taillabel=0 color=red];
+ Loop8 -> Start2:p0[taillabel=1 color=red];
+ Loop8 -> Region27[taillabel=2 color=red constraint=false];
+ Phi_a11 -> Loop8 [style=dotted taillabel=0];
+ Phi_a11 -> Con_6[taillabel=1];
+ Phi_a11 -> Phi_a28[taillabel=2 constraint=false];
+ Phi_b12 -> Loop8 [style=dotted taillabel=0];
+ Phi_b12 -> Con_7[taillabel=1];
+ Phi_b12 -> Phi_b29[taillabel=2 constraint=false];
+ LT14 -> Phi_a11[taillabel=1];
+ LT14 -> Con_13[taillabel=2];
+ If15 -> Loop8[taillabel=0 color=red];
+ If15 -> LT14[taillabel=1];
+ EQ20 -> Phi_a11[taillabel=1];
+ EQ20 -> Con_19[taillabel=2];
+ If21 -> If15:p0[taillabel=0 color=red];
+ If21 -> EQ20[taillabel=1];
+ Region27 -> If21:p0[taillabel=1 color=red];
+ Region27 -> If21:p1[taillabel=2 color=red];
+ Phi_a28 -> Region27 [style=dotted taillabel=0];
+ Phi_a28 -> Con_25[taillabel=1];
+ Phi_a28 -> Phi_a11[taillabel=2 constraint=false];
+ Phi_b29 -> Region27 [style=dotted taillabel=0];
+ Phi_b29 -> Phi_b12[taillabel=1];
+ Phi_b29 -> Con_26[taillabel=2 constraint=false];
+ Return30 -> If15:p1[taillabel=0 color=red];
+ Return30 -> Phi_b12[taillabel=1];
+ edge [style=dashed color=cornflowerblue];
+}
diff --git a/chapter07/docs/07-graph4.svg b/chapter07/docs/07-graph4.svg
new file mode 100644
index 00000000..cf42453d
--- /dev/null
+++ b/chapter07/docs/07-graph4.svg
@@ -0,0 +1,329 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.43.0 (0)
+ -->
+<!-- Title: chapter07 Pages: 1 -->
+<svg width="394pt" height="545pt"
+ viewBox="0.00 0.00 394.00 545.35" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 541.35)">
+<title>chapter07</title>
+<polygon fill="white" stroke="transparent" points="-4,4 -4,-541.35 390,-541.35 390,4 -4,4"/>
+<g id="clust1" class="cluster">
+<title>cluster_Nodes</title>
+<polygon fill="none" stroke="black" points="8,-33.35 8,-529.35 378,-529.35 378,-33.35 8,-33.35"/>
+</g>
+<!-- Start2 -->
+<g id="node1" class="node">
+<title>Start2</title>
+<polygon fill="yellow" stroke="transparent" points="312,-492.35 312,-517.35 362,-517.35 362,-492.35 312,-492.35"/>
+<polygon fill="none" stroke="black" points="312,-492.35 312,-517.35 362,-517.35 362,-492.35 312,-492.35"/>
+<text text-anchor="start" x="318.5" y="-501.15" font-family="Times,serif" font-size="14.00">Start</text>
+<polygon fill="none" stroke="black" points="312,-461.35 312,-492.35 362,-492.35 362,-461.35 312,-461.35"/>
+<polygon fill="yellow" stroke="transparent" points="317,-466.35 317,-487.35 357,-487.35 357,-466.35 317,-466.35"/>
+<polygon fill="none" stroke="black" points="317,-466.35 317,-487.35 357,-487.35 357,-466.35 317,-466.35"/>
+<text text-anchor="start" x="320" y="-473.15" font-family="Times,serif" font-size="14.00">$ctrl</text>
+</g>
+<!-- Stop3 -->
+<g id="node2" class="node">
+<title>Stop3</title>
+<polygon fill="yellow" stroke="black" points="370,-77.35 316,-77.35 316,-41.35 370,-41.35 370,-77.35"/>
+<text text-anchor="middle" x="343" y="-55.65" font-family="Times,serif" font-size="14.00">Stop</text>
+</g>
+<!-- Return30 -->
+<g id="node19" class="node">
+<title>Return30</title>
+<polygon fill="yellow" stroke="black" points="370,-163.35 304,-163.35 304,-127.35 370,-127.35 370,-163.35"/>
+<text text-anchor="middle" x="337" y="-141.65" font-family="Times,serif" font-size="14.00">Return</text>
+</g>
+<!-- Stop3&#45;&gt;Return30 -->
+<g id="edge1" class="edge">
+<title>Stop3&#45;&gt;Return30</title>
+<path fill="none" stroke="red" d="M341.76,-77.75C340.95,-89.09 339.87,-104.12 338.95,-117.03"/>
+<polygon fill="red" stroke="red" points="335.44,-117.03 338.22,-127.25 342.42,-117.53 335.44,-117.03"/>
+<text text-anchor="middle" x="338.76" y="-80.35" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
+</g>
+<!-- Con_6 -->
+<g id="node3" class="node">
+<title>Con_6</title>
+<ellipse fill="none" stroke="black" cx="196" cy="-489.35" rx="27" ry="18"/>
+<text text-anchor="middle" x="196" y="-485.65" font-family="Times,serif" font-size="14.00">1</text>
+</g>
+<!-- Con_7 -->
+<g id="node4" class="node">
+<title>Con_7</title>
+<ellipse fill="none" stroke="black" cx="124" cy="-489.35" rx="27" ry="18"/>
+<text text-anchor="middle" x="124" y="-485.65" font-family="Times,serif" font-size="14.00">2</text>
+</g>
+<!-- Loop8 -->
+<g id="node5" class="node">
+<title>Loop8</title>
+<polygon fill="yellow" stroke="black" points="367,-421.35 313,-421.35 313,-385.35 367,-385.35 367,-421.35"/>
+<text text-anchor="middle" x="340" y="-399.65" font-family="Times,serif" font-size="14.00">Loop</text>
+</g>
+<!-- Loop8&#45;&gt;Start2 -->
+<g id="edge2" class="edge">
+<title>Loop8&#45;&gt;Start2:p0</title>
+<path fill="none" stroke="red" d="M338.71,-421.43C338.08,-430.88 337.4,-443.1 337.13,-455.02"/>
+<polygon fill="red" stroke="red" points="333.62,-455.3 337,-465.35 340.62,-455.39 333.62,-455.3"/>
+<text text-anchor="middle" x="335.71" y="-424.03" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- Region27 -->
+<g id="node16" class="node">
+<title>Region27</title>
+<polygon fill="yellow" stroke="black" points="298,-77.35 232,-77.35 232,-41.35 298,-41.35 298,-77.35"/>
+<text text-anchor="middle" x="265" y="-55.65" font-family="Times,serif" font-size="14.00">Region</text>
+</g>
+<!-- Loop8&#45;&gt;Region27 -->
+<g id="edge3" class="edge">
+<title>Loop8&#45;&gt;Region27</title>
+<path fill="none" stroke="red" d="M312.6,-389.27C286.36,-376.62 248.79,-358.1 235,-349.35 191.17,-321.51 165.74,-324.02 143,-277.35 130.54,-251.78 139.66,-241.59 143,-213.35 145.69,-190.65 153.07,-131.47 167,-113.35 174.64,-103.41 199.67,-89.94 222.53,-79.07"/>
+<polygon fill="red" stroke="red" points="224.22,-82.15 231.8,-74.74 221.25,-75.8 224.22,-82.15"/>
+<text text-anchor="middle" x="309.6" y="-382.87" font-family="Helvetica,sans-Serif" font-size="8.00">2</text>
+</g>
+<!-- Phi_a11 -->
+<g id="node6" class="node">
+<title>Phi_a11</title>
+<ellipse fill="lightyellow" stroke="black" cx="196" cy="-403.35" rx="27" ry="18"/>
+<text text-anchor="middle" x="196" y="-399.65" font-family="Times,serif" font-size="14.00">φ_a</text>
+</g>
+<!-- Phi_a11&#45;&gt;Con_6 -->
+<g id="edge5" class="edge">
+<title>Phi_a11&#45;&gt;Con_6</title>
+<path fill="none" stroke="black" d="M196,-421.75C196,-433.09 196,-448.12 196,-461.03"/>
+<polygon fill="black" stroke="black" points="192.5,-461.25 196,-471.25 199.5,-461.25 192.5,-461.25"/>
+<text text-anchor="middle" x="193" y="-424.35" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- Phi_a11&#45;&gt;Loop8 -->
+<g id="edge4" class="edge">
+<title>Phi_a11&#45;&gt;Loop8</title>
+<path fill="none" stroke="black" stroke-dasharray="1,5" d="M210.22,-387.78C218.37,-380.03 229.29,-371.45 241,-367.35 263.65,-359.4 272.35,-359.4 295,-367.35 302.14,-369.85 308.99,-374.02 315.09,-378.65"/>
+<polygon fill="black" stroke="black" points="312.99,-381.44 322.92,-385.13 317.45,-376.05 312.99,-381.44"/>
+<text text-anchor="middle" x="207.22" y="-381.38" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
+</g>
+<!-- Phi_a28 -->
+<g id="node17" class="node">
+<title>Phi_a28</title>
+<ellipse fill="lightyellow" stroke="black" cx="187" cy="-59.35" rx="27" ry="18"/>
+<text text-anchor="middle" x="187" y="-55.65" font-family="Times,serif" font-size="14.00">φ_a</text>
+</g>
+<!-- Phi_b12 -->
+<g id="node7" class="node">
+<title>Phi_b12</title>
+<ellipse fill="lightyellow" stroke="black" cx="124" cy="-403.35" rx="27.1" ry="18"/>
+<text text-anchor="middle" x="124" y="-399.65" font-family="Times,serif" font-size="14.00">φ_b</text>
+</g>
+<!-- Phi_b12&#45;&gt;Con_7 -->
+<g id="edge8" class="edge">
+<title>Phi_b12&#45;&gt;Con_7</title>
+<path fill="none" stroke="black" d="M124,-421.75C124,-433.09 124,-448.12 124,-461.03"/>
+<polygon fill="black" stroke="black" points="120.5,-461.25 124,-471.25 127.5,-461.25 120.5,-461.25"/>
+<text text-anchor="middle" x="121" y="-424.35" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- Phi_b12&#45;&gt;Loop8 -->
+<g id="edge7" class="edge">
+<title>Phi_b12&#45;&gt;Loop8</title>
+<path fill="none" stroke="black" stroke-dasharray="1,5" d="M138.22,-387.78C146.37,-380.03 157.29,-371.45 169,-367.35 221.84,-348.81 242.16,-348.81 295,-367.35 302.14,-369.85 308.99,-374.02 315.09,-378.65"/>
+<polygon fill="black" stroke="black" points="312.99,-381.44 322.92,-385.13 317.45,-376.05 312.99,-381.44"/>
+<text text-anchor="middle" x="135.22" y="-381.38" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
+</g>
+<!-- Phi_b29 -->
+<g id="node18" class="node">
+<title>Phi_b29</title>
+<ellipse fill="lightyellow" stroke="black" cx="43" cy="-59.35" rx="27.1" ry="18"/>
+<text text-anchor="middle" x="43" y="-55.65" font-family="Times,serif" font-size="14.00">φ_b</text>
+</g>
+<!-- Con_13 -->
+<g id="node8" class="node">
+<title>Con_13</title>
+<ellipse fill="none" stroke="black" cx="268" cy="-403.35" rx="27" ry="18"/>
+<text text-anchor="middle" x="268" y="-399.65" font-family="Times,serif" font-size="14.00">10</text>
+</g>
+<!-- LT14 -->
+<g id="node9" class="node">
+<title>LT14</title>
+<ellipse fill="none" stroke="black" cx="271" cy="-331.35" rx="27" ry="18"/>
+<text text-anchor="middle" x="271" y="-327.65" font-family="Times,serif" font-size="14.00">&lt;</text>
+</g>
+<!-- LT14&#45;&gt;Phi_a11 -->
+<g id="edge10" class="edge">
+<title>LT14&#45;&gt;Phi_a11</title>
+<path fill="none" stroke="black" d="M255.82,-346.51C245.15,-356.47 230.68,-369.98 218.65,-381.21"/>
+<polygon fill="black" stroke="black" points="216.08,-378.82 211.15,-388.2 220.85,-383.94 216.08,-378.82"/>
+<text text-anchor="middle" x="252.82" y="-349.11" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- LT14&#45;&gt;Con_13 -->
+<g id="edge11" class="edge">
+<title>LT14&#45;&gt;Con_13</title>
+<path fill="none" stroke="black" d="M270.26,-349.65C269.93,-357.36 269.53,-366.63 269.16,-375.23"/>
+<polygon fill="black" stroke="black" points="265.66,-375.1 268.73,-385.24 272.66,-375.4 265.66,-375.1"/>
+<text text-anchor="middle" x="267.26" y="-352.25" font-family="Helvetica,sans-Serif" font-size="8.00">2</text>
+</g>
+<!-- If15 -->
+<g id="node10" class="node">
+<title>If15</title>
+<polygon fill="yellow" stroke="transparent" points="260,-248.35 260,-273.35 354,-273.35 354,-248.35 260,-248.35"/>
+<polygon fill="none" stroke="black" points="260,-248.35 260,-273.35 354,-273.35 354,-248.35 260,-248.35"/>
+<text text-anchor="start" x="301.5" y="-257.15" font-family="Times,serif" font-size="14.00">If</text>
+<polygon fill="none" stroke="black" points="260,-217.35 260,-248.35 354,-248.35 354,-217.35 260,-217.35"/>
+<polygon fill="yellow" stroke="transparent" points="265,-222.35 265,-243.35 305,-243.35 305,-222.35 265,-222.35"/>
+<polygon fill="none" stroke="black" points="265,-222.35 265,-243.35 305,-243.35 305,-222.35 265,-222.35"/>
+<text text-anchor="start" x="268" y="-229.15" font-family="Times,serif" font-size="14.00">True</text>
+<polygon fill="yellow" stroke="transparent" points="305,-222.35 305,-243.35 349,-243.35 349,-222.35 305,-222.35"/>
+<polygon fill="none" stroke="black" points="305,-222.35 305,-243.35 349,-243.35 349,-222.35 305,-222.35"/>
+<text text-anchor="start" x="308" y="-229.15" font-family="Times,serif" font-size="14.00">False</text>
+</g>
+<!-- If15&#45;&gt;Loop8 -->
+<g id="edge12" class="edge">
+<title>If15&#45;&gt;Loop8</title>
+<path fill="none" stroke="red" d="M314.79,-277.64C317.5,-288.87 320.49,-301.65 323,-313.35 327.43,-333.98 331.89,-357.43 335.12,-375.08"/>
+<polygon fill="red" stroke="red" points="331.74,-376.02 336.97,-385.24 338.63,-374.77 331.74,-376.02"/>
+<text text-anchor="middle" x="311.79" y="-280.24" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
+</g>
+<!-- If15&#45;&gt;LT14 -->
+<g id="edge13" class="edge">
+<title>If15&#45;&gt;LT14</title>
+<path fill="none" stroke="black" d="M293.59,-277.62C289.8,-286.48 285.72,-296.01 282.08,-304.5"/>
+<polygon fill="black" stroke="black" points="278.78,-303.32 278.05,-313.89 285.21,-306.08 278.78,-303.32"/>
+<text text-anchor="middle" x="290.59" y="-280.22" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- Con_19 -->
+<g id="node11" class="node">
+<title>Con_19</title>
+<ellipse fill="none" stroke="black" cx="130" cy="-331.35" rx="27" ry="18"/>
+<text text-anchor="middle" x="130" y="-327.65" font-family="Times,serif" font-size="14.00">2</text>
+</g>
+<!-- EQ20 -->
+<g id="node12" class="node">
+<title>EQ20</title>
+<ellipse fill="none" stroke="black" cx="179" cy="-245.35" rx="27" ry="18"/>
+<text text-anchor="middle" x="179" y="-241.65" font-family="Times,serif" font-size="14.00">==</text>
+</g>
+<!-- EQ20&#45;&gt;Phi_a11 -->
+<g id="edge14" class="edge">
+<title>EQ20&#45;&gt;Phi_a11</title>
+<path fill="none" stroke="black" d="M180.85,-263.34C183.79,-290.3 189.54,-343.11 193.05,-375.31"/>
+<polygon fill="black" stroke="black" points="189.59,-375.81 194.15,-385.38 196.55,-375.06 189.59,-375.81"/>
+<text text-anchor="middle" x="177.85" y="-265.94" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- EQ20&#45;&gt;Con_19 -->
+<g id="edge15" class="edge">
+<title>EQ20&#45;&gt;Con_19</title>
+<path fill="none" stroke="black" d="M169.55,-262.54C162.5,-274.63 152.74,-291.37 144.67,-305.19"/>
+<polygon fill="black" stroke="black" points="141.43,-303.8 139.42,-314.2 147.48,-307.33 141.43,-303.8"/>
+<text text-anchor="middle" x="166.55" y="-265.14" font-family="Helvetica,sans-Serif" font-size="8.00">2</text>
+</g>
+<!-- If21 -->
+<g id="node13" class="node">
+<title>If21</title>
+<polygon fill="yellow" stroke="transparent" points="184,-148.35 184,-173.35 278,-173.35 278,-148.35 184,-148.35"/>
+<polygon fill="none" stroke="black" points="184,-148.35 184,-173.35 278,-173.35 278,-148.35 184,-148.35"/>
+<text text-anchor="start" x="225.5" y="-157.15" font-family="Times,serif" font-size="14.00">If</text>
+<polygon fill="none" stroke="black" points="184,-117.35 184,-148.35 278,-148.35 278,-117.35 184,-117.35"/>
+<polygon fill="yellow" stroke="transparent" points="189,-122.35 189,-143.35 229,-143.35 229,-122.35 189,-122.35"/>
+<polygon fill="none" stroke="black" points="189,-122.35 189,-143.35 229,-143.35 229,-122.35 189,-122.35"/>
+<text text-anchor="start" x="192" y="-129.15" font-family="Times,serif" font-size="14.00">True</text>
+<polygon fill="yellow" stroke="transparent" points="229,-122.35 229,-143.35 273,-143.35 273,-122.35 229,-122.35"/>
+<polygon fill="none" stroke="black" points="229,-122.35 229,-143.35 273,-143.35 273,-122.35 229,-122.35"/>
+<text text-anchor="start" x="232" y="-129.15" font-family="Times,serif" font-size="14.00">False</text>
+</g>
+<!-- If21&#45;&gt;If15 -->
+<g id="edge16" class="edge">
+<title>If21&#45;&gt;If15:p0</title>
+<path fill="none" stroke="red" d="M266.11,-177.35C274.25,-187.13 281.31,-198.68 283.92,-211.16"/>
+<polygon fill="red" stroke="red" points="280.47,-211.77 285,-221.35 287.43,-211.03 280.47,-211.77"/>
+<text text-anchor="middle" x="263.11" y="-179.95" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
+</g>
+<!-- If21&#45;&gt;EQ20 -->
+<g id="edge17" class="edge">
+<title>If21&#45;&gt;EQ20</title>
+<path fill="none" stroke="black" d="M214.55,-177.35C207.43,-190.76 199.21,-206.25 192.47,-218.96"/>
+<polygon fill="black" stroke="black" points="189.24,-217.58 187.65,-228.05 195.42,-220.86 189.24,-217.58"/>
+<text text-anchor="middle" x="211.55" y="-179.95" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- Con_25 -->
+<g id="node14" class="node">
+<title>Con_25</title>
+<ellipse fill="none" stroke="black" cx="111" cy="-145.35" rx="27" ry="18"/>
+<text text-anchor="middle" x="111" y="-141.65" font-family="Times,serif" font-size="14.00">3</text>
+</g>
+<!-- Con_26 -->
+<g id="node15" class="node">
+<title>Con_26</title>
+<ellipse fill="none" stroke="black" cx="115" cy="-59.35" rx="27" ry="18"/>
+<text text-anchor="middle" x="115" y="-55.65" font-family="Times,serif" font-size="14.00">4</text>
+</g>
+<!-- Region27&#45;&gt;If21 -->
+<g id="edge18" class="edge">
+<title>Region27&#45;&gt;If21:p0</title>
+<path fill="none" stroke="red" d="M236.34,-77.55C225.26,-86.1 214.31,-97.52 210.45,-111.09"/>
+<polygon fill="red" stroke="red" points="206.94,-110.95 209,-121.35 213.87,-111.94 206.94,-110.95"/>
+<text text-anchor="middle" x="233.34" y="-80.15" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- Region27&#45;&gt;If21 -->
+<g id="edge19" class="edge">
+<title>Region27&#45;&gt;If21:p1</title>
+<path fill="none" stroke="red" d="M258.9,-77.46C255.95,-86.91 252.79,-99.16 251.55,-111.27"/>
+<polygon fill="red" stroke="red" points="248.05,-111.17 251,-121.35 255.04,-111.55 248.05,-111.17"/>
+<text text-anchor="middle" x="255.9" y="-80.06" font-family="Helvetica,sans-Serif" font-size="8.00">2</text>
+</g>
+<!-- Phi_a28&#45;&gt;Phi_a11 -->
+<g id="edge22" class="edge">
+<title>Phi_a28&#45;&gt;Phi_a11</title>
+<path fill="none" stroke="black" d="M156.38,-74.81C123.24,-90.46 75.08,-113.23 75,-113.35 59.78,-137.38 66.63,-150.16 75,-177.35 90.81,-228.71 98.49,-247.23 143,-277.35 204.81,-319.16 260.23,-255.2 307,-313.35 317.03,-325.81 315.54,-335.81 307,-349.35 306.7,-349.82 259.29,-372.35 226.54,-387.88"/>
+<polygon fill="black" stroke="black" points="158.14,-77.85 165.69,-70.41 155.15,-71.52 158.14,-77.85"/>
+<polygon fill="black" stroke="black" points="224.86,-384.81 217.32,-392.25 227.86,-391.13 224.86,-384.81"/>
+<text text-anchor="middle" x="162.69" y="-73.01" font-family="Helvetica,sans-Serif" font-size="8.00">2</text>
+</g>
+<!-- Phi_a28&#45;&gt;Con_25 -->
+<g id="edge21" class="edge">
+<title>Phi_a28&#45;&gt;Con_25</title>
+<path fill="none" stroke="black" d="M173.41,-75.36C161.74,-88.26 144.74,-107.05 131.43,-121.77"/>
+<polygon fill="black" stroke="black" points="128.58,-119.7 124.47,-129.46 133.77,-124.39 128.58,-119.7"/>
+<text text-anchor="middle" x="170.41" y="-77.96" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- Phi_a28&#45;&gt;Region27 -->
+<g id="edge20" class="edge">
+<title>Phi_a28&#45;&gt;Region27</title>
+<path fill="none" stroke="black" stroke-dasharray="1,5" d="M214.12,-59.35C216.58,-59.35 219.04,-59.35 221.5,-59.35"/>
+<polygon fill="black" stroke="black" points="221.61,-62.85 231.61,-59.35 221.61,-55.85 221.61,-62.85"/>
+<text text-anchor="middle" x="217.12" y="-52.95" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
+</g>
+<!-- Phi_b29&#45;&gt;Phi_b12 -->
+<g id="edge24" class="edge">
+<title>Phi_b29&#45;&gt;Phi_b12</title>
+<path fill="none" stroke="black" d="M44.59,-87.64C48.34,-139.92 60.11,-257.16 94,-349.35 97.54,-358.97 102.7,-368.9 107.74,-377.53"/>
+<polygon fill="black" stroke="black" points="48.07,-87.25 43.91,-77.51 41.09,-87.72 48.07,-87.25"/>
+<polygon fill="black" stroke="black" points="104.9,-379.59 113.09,-386.31 110.88,-375.95 104.9,-379.59"/>
+<text text-anchor="middle" x="40.91" y="-80.11" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- Phi_b29&#45;&gt;Con_26 -->
+<g id="edge25" class="edge">
+<title>Phi_b29&#45;&gt;Con_26</title>
+<path fill="none" stroke="black" d="M70.56,-59.35C73,-59.35 75.44,-59.35 77.89,-59.35"/>
+<polygon fill="black" stroke="black" points="77.92,-62.85 87.92,-59.35 77.92,-55.85 77.92,-62.85"/>
+<text text-anchor="middle" x="73.56" y="-52.95" font-family="Helvetica,sans-Serif" font-size="8.00">2</text>
+</g>
+<!-- Phi_b29&#45;&gt;Region27 -->
+<g id="edge23" class="edge">
+<title>Phi_b29&#45;&gt;Region27</title>
+<path fill="none" stroke="black" stroke-dasharray="1,5" d="M54.68,-42.66C62.75,-32.67 74.44,-20.87 88,-15.35 139.86,5.79 161.51,4.17 214,-15.35 224.63,-19.3 234.53,-26.57 242.72,-34.08"/>
+<polygon fill="black" stroke="black" points="240.44,-36.75 250.02,-41.26 245.34,-31.75 240.44,-36.75"/>
+<text text-anchor="middle" x="51.68" y="-36.26" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
+</g>
+<!-- Return30&#45;&gt;Phi_b12 -->
+<g id="edge27" class="edge">
+<title>Return30&#45;&gt;Phi_b12</title>
+<path fill="none" stroke="black" d="M313.98,-163.53C307.84,-168.06 301.19,-172.92 295,-177.35 272.13,-193.69 260.87,-191.65 243,-213.35 223.27,-237.31 230.02,-250.17 215,-277.35 196.27,-311.22 188.92,-318.16 166,-349.35 158.65,-359.34 150.22,-370.11 142.84,-379.32"/>
+<polygon fill="black" stroke="black" points="139.98,-377.29 136.43,-387.27 145.43,-381.68 139.98,-377.29"/>
+<text text-anchor="middle" x="310.98" y="-166.13" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- Return30&#45;&gt;If15 -->
+<g id="edge26" class="edge">
+<title>Return30&#45;&gt;If15:p1</title>
+<path fill="none" stroke="red" d="M333.39,-163.54C331,-176.2 328.14,-194.14 327.27,-211.34"/>
+<polygon fill="red" stroke="red" points="323.77,-211.26 327,-221.35 330.76,-211.44 323.77,-211.26"/>
+<text text-anchor="middle" x="330.39" y="-166.14" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
+</g>
+</g>
+</svg>
diff --git a/chapter07/docs/build-graphs.sh b/chapter07/docs/build-graphs.sh
new file mode 100644
index 00000000..27b2b72f
--- /dev/null
+++ b/chapter07/docs/build-graphs.sh
@@ -0,0 +1,6 @@
+dot -Tsvg 07-graph1.gv > 07-graph1.svg
+dot -Tsvg 07-graph2.gv > 07-graph2.svg
+dot -Tsvg 07-graph3.gv > 07-graph3.svg
+dot -Tsvg 07-graph4.gv > 07-graph4.svg
+
+
diff --git a/chapter07/pom.xml b/chapter07/pom.xml
new file mode 100644
index 00000000..28d119b5
--- /dev/null
+++ b/chapter07/pom.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>com.seaofnodes</groupId>
+ <artifactId>simple</artifactId>
+ <version>1.0</version>
+ </parent>
+ <artifactId>chapter07</artifactId>
+ <packaging>jar</packaging>
+ <name>Chapter 7</name>
+</project> \ No newline at end of file
diff --git a/chapter07/src/main/java/com/seaofnodes/simple/GraphVisualizer.java b/chapter07/src/main/java/com/seaofnodes/simple/GraphVisualizer.java
new file mode 100644
index 00000000..ce3522bc
--- /dev/null
+++ b/chapter07/src/main/java/com/seaofnodes/simple/GraphVisualizer.java
@@ -0,0 +1,256 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.node.*;
+
+import java.util.*;
+
+/**
+ * Simple visualizer that outputs GraphViz dot format.
+ * The dot output must be saved to a file and run manually via dot to generate the SVG output.
+ * Currently, this is done manually.
+ */
+public class GraphVisualizer {
+
+ /**
+ * If set to true we put the control nodes in a separate cluster from
+ * data nodes.
+ */
+ boolean _separateControlCluster = false;
+
+ public GraphVisualizer(boolean separateControlCluster) { this._separateControlCluster = separateControlCluster; }
+ public GraphVisualizer() { this(false); }
+
+ public String generateDotOutput(Parser parser) {
+
+ // Since the graph has cycles, we need to create a flat list of all the
+ // nodes in the graph.
+ Collection<Node> all = findAll(parser);
+ StringBuilder sb = new StringBuilder();
+ sb.append("digraph chapter07 {\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
+ // 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");
+
+ // Force nested scopes to order
+ sb.append("\tcompound=\"true\";\n");
+
+ // Just the Nodes first, in a cluster no edges
+ nodes(sb, all);
+
+ // Now the scopes, in a cluster no edges
+ scope(sb,parser._scope );
+ for( ScopeNode scope : parser._xScopes )
+ scope( sb, scope );
+
+ // Walk the Node edges
+ nodeEdges(sb, all);
+
+ // Walk the active Scope edges
+ scopeEdges( sb, parser._scope );
+ for( ScopeNode scope : parser._xScopes )
+ scopeEdges( sb, scope );
+
+ sb.append("}\n");
+ return sb.toString();
+ }
+
+ private void nodesByCluster(StringBuilder sb, boolean doCtrl, Collection<Node> all) {
+ if (!_separateControlCluster && doCtrl) // all nodes in 1 cluster
+ return;
+ // Just the Nodes first, in a cluster no edges
+ sb.append(doCtrl ? "\tsubgraph cluster_Controls {\n" : "\tsubgraph cluster_Nodes {\n"); // Magic "cluster_" in the subgraph name
+ for (Node n : all) {
+ if (n instanceof ProjNode || n instanceof ScopeNode)
+ continue; // Do not emit, rolled into MultiNode or Scope cluster already
+ if (_separateControlCluster && doCtrl && !n.isCFG()) continue;
+ if (_separateControlCluster && !doCtrl && n.isCFG()) continue;
+ sb.append("\t\t").append(n.uniqueName()).append(" [ ");
+ 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");
+ sb.append("\t\t\t<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\" CELLPADDING=\"4\">\n");
+ sb.append("\t\t\t<TR><TD BGCOLOR=\"yellow\">").append(lab).append("</TD></TR>\n");
+ sb.append("\t\t\t<TR>");
+ boolean doProjTable = false;
+ for (Node use : n._outputs) {
+ if (use instanceof ProjNode proj) {
+ if (!doProjTable) {
+ doProjTable = true;
+ sb.append("<TD>").append("\n");
+ sb.append("\t\t\t\t<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">").append("\n");
+ sb.append("\t\t\t\t<TR>");
+ }
+ sb.append("<TD PORT=\"p").append(proj._idx).append("\"");
+ if (proj.isCFG()) sb.append(" BGCOLOR=\"yellow\"");
+ sb.append(">").append(proj.glabel()).append("</TD>");
+ }
+ }
+ if (doProjTable) {
+ sb.append("</TR>").append("\n");
+ sb.append("\t\t\t\t</TABLE>").append("\n");
+ sb.append("\t\t\t</TD>");
+ }
+ sb.append("</TR>\n");
+ sb.append("\t\t\t</TABLE>>\n\t\t");
+
+ } else {
+ // control nodes have box shape
+ // other nodes are ellipses, i.e. default shape
+ if (n.isCFG()) sb.append("shape=box style=filled fillcolor=yellow ");
+ else if (n instanceof PhiNode) sb.append("style=filled fillcolor=lightyellow ");
+ sb.append("label=\"").append(lab).append("\" ");
+ }
+ sb.append("];\n");
+ }
+ if (!_separateControlCluster) {
+ // Force Region & Phis to line up
+ for (Node n : all) {
+ if (n instanceof RegionNode region) {
+ sb.append("\t\t{ rank=same; ");
+ sb.append(region).append(";");
+ for (Node phi : region._outputs)
+ if (phi instanceof PhiNode) sb.append(phi.uniqueName()).append(";");
+ sb.append("}\n");
+ }
+ }
+ }
+ sb.append("\t}\n"); // End Node cluster
+ }
+
+ private void nodes(StringBuilder sb, Collection<Node> all) {
+ nodesByCluster(sb, true, all);
+ nodesByCluster(sb, false, all);
+ }
+
+ // Build a nested scope display, walking the _prev edge
+ private void scope( StringBuilder sb, ScopeNode scope ) {
+ sb.append("\tnode [shape=plaintext];\n");
+ int level=1;
+ for( int idx = scope._scopes.size()-1; idx>=0; idx-- ) {
+ var syms = scope._scopes.get(idx);
+ String scopeName = makeScopeName(scope, 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<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">\n");
+ // Add the scope level
+ int scopeLevel = scope._scopes.size()-level;
+ sb.append("\t\t\t<TR><TD BGCOLOR=\"cyan\">").append(scopeLevel).append("</TD>");
+ for(String name: syms.keySet())
+ sb.append("<TD PORT=\"").append(makePortName(scopeName, name)).append("\">").append(name).append("</TD>");
+ sb.append("</TR>\n");
+ sb.append("\t\t\t</TABLE>>];\n");
+ level++;
+ }
+ // Scope clusters nest, so the graphics shows the nested scopes, so
+ // they are not closed as they are printed; so they just keep nesting.
+ // We close them all at once here.
+ sb.append( "\t}\n".repeat( level-1 ) ); // End all Scope clusters
+ }
+
+ 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<Node> all) {
+ // All them edge labels
+ sb.append("\tedge [ fontname=Helvetica, fontsize=8 ];\n");
+ for( Node n : all ) {
+ // 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;
+ for( int i=0; i<n.nIns(); i++ ) {
+ Node def = n.in(i);
+ if( n instanceof PhiNode && def instanceof RegionNode ) {
+ // Draw a dotted use->def edge from Phi to Region.
+ sb.append('\t').append(n.uniqueName());
+ sb.append(" -> ");
+ 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(" -> ");
+ if( def instanceof ProjNode proj ) {
+ String mname = proj.ctrl().uniqueName();
+ sb.append(mname).append(":p").append(proj._idx);
+ } else sb.append(def.uniqueName());
+ // 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");
+ // Backedges do not add a ranking constraint
+ if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) )
+ sb.append(" constraint=false");
+ sb.append("];\n");
+ }
+ }
+ }
+ }
+
+ // Walk the scope edges
+ private void scopeEdges( StringBuilder sb, ScopeNode scope ) {
+ sb.append("\tedge [style=dashed color=cornflowerblue];\n");
+ int level=1;
+ for( int i = scope._scopes.size()-1; i>=0; i-- ) {
+ var syms = scope._scopes.get(i);
+ String scopeName = makeScopeName(scope, level);
+ for( String name : syms.keySet() ) {
+ int idx = syms.get(name);
+ Node def = scope.in(idx);
+ 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(" -> ");
+ if( def instanceof ProjNode proj ) {
+ String mname = proj.ctrl().uniqueName();
+ sb.append(mname).append(":p").append(proj._idx);
+ } else sb.append(def.uniqueName());
+ sb.append(";\n");
+ }
+ level++;
+ }
+ }
+
+ /**
+ * Finds all nodes in the graph.
+ */
+ private Collection<Node> findAll(Parser parser) {
+ final HashMap<Integer, Node> all = new HashMap<>();
+ for( Node n : Parser.START._outputs )
+ walk(all, n);
+ for( Node n : parser._scope._inputs )
+ walk(all, n);
+ return all.values();
+ }
+
+ /**
+ * Walk a subgraph and populate distinct nodes in the all list.
+ */
+ private void walk(HashMap<Integer,Node> all, Node n) {
+ 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)
+ walk(all, c);
+ for( Node c : n._outputs )
+ walk(all, c);
+ }
+}
diff --git a/chapter07/src/main/java/com/seaofnodes/simple/IRPrinter.java b/chapter07/src/main/java/com/seaofnodes/simple/IRPrinter.java
new file mode 100644
index 00000000..dc6d9f4f
--- /dev/null
+++ b/chapter07/src/main/java/com/seaofnodes/simple/IRPrinter.java
@@ -0,0 +1,124 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.node.LoopNode;
+import com.seaofnodes.simple.node.Node;
+
+import java.util.ArrayList;
+import java.util.BitSet;
+
+public class IRPrinter {
+
+ // Another bulk pretty-printer. Makes more effort at basic-block grouping.
+ public static String prettyPrint(Node node, int depth) {
+ // First, a Breadth First Search at a fixed depth.
+ BFS bfs = new BFS(node,depth);
+ // Convert just that set to a post-order
+ ArrayList<Node> rpos = new ArrayList<>();
+ BitSet visit = new BitSet();
+ for( int i=bfs._lim; i< bfs._bfs.size(); i++ )
+ postOrd( bfs._bfs.get(i), rpos, visit, bfs._bs);
+ // Reverse the post-order walk
+ StringBuilder sb = new StringBuilder();
+ boolean gap=false;
+ for( int i=rpos.size()-1; i>=0; i-- ) {
+ Node n = rpos.get(i);
+ if( n.isCFG() || n.isMultiHead() ) {
+ if( !gap ) sb.append("\n"); // Blank before multihead
+ n._print_line(sb); // Print head
+ while( --i >= 0 ) {
+ Node t = rpos.get(i);
+ if( !t.isMultiTail() ) { i++; break; }
+ t._print_line(sb);
+ }
+ sb.append("\n"); // Blank after multitail
+ gap = true;
+ } else {
+ n._print_line( sb );
+ gap = false;
+ }
+ }
+ return sb.toString();
+ }
+
+ private static void postOrd(Node n, ArrayList<Node> rpos, BitSet visit, BitSet bfs) {
+ if( !bfs.get(n._nid) )
+ return; // Not in the BFS visit
+ if( visit.get(n._nid) ) return; // Already post-order walked
+ visit.set(n._nid);
+ // First walk the CFG, then everything
+ if( n.isCFG() ) {
+ for( Node use : n._outputs )
+ if( use.isCFG() && use.nOuts()>=1 && !(use._outputs.get(0) instanceof LoopNode) )
+ postOrd(use, rpos,visit,bfs);
+ for( Node use : n._outputs )
+ if( use.isCFG() )
+ postOrd(use,rpos,visit,bfs);
+ }
+ for( Node use : n._outputs )
+ postOrd(use, rpos,visit,bfs);
+ // Post-order
+ rpos.add(n);
+ }
+
+ // Breadth-first search, broken out in a class to keep in more independent.
+ // Maintains a root-set of Nodes at the limit (or past by 1 if MultiHead).
+ public static class BFS {
+ // A breadth first search, plus MultiHeads for any MultiTails
+ public final ArrayList<Node> _bfs;
+ public final BitSet _bs; // Visited members by node id
+ public final int _depth; // Depth limit
+ public final int _lim; // From here to _bfs._len can be roots for a reverse search
+ public BFS( Node base, int d ) {
+ _depth = d;
+ _bfs = new ArrayList<>();
+ _bs = new BitSet();
+
+ add(base); // Prime the pump
+ int idx=0, lim=1; // Limit is where depth counter changes
+ while( idx < _bfs.size() ) { // Ran out of nodes below depth
+ Node n = _bfs.get(idx++);
+ for( Node def : n._inputs )
+ if( def!=null && !_bs.get(def._nid) )
+ add(def);
+ if( idx==lim ) { // Depth counter changes at limit
+ if( --d < 0 )
+ break; // Ran out of depth
+ lim = _bfs.size(); // New depth limit
+ }
+ }
+ // Toss things past the limit except multi-heads
+ while( idx < _bfs.size() ) {
+ Node n = _bfs.get(idx);
+ if( n.isMultiHead() ) idx++;
+ else del(idx);
+ }
+ // Root set is any node with no inputs in the visited set
+ lim = _bfs.size();
+ for( int i=_bfs.size()-1; i>=0; i-- )
+ if( !any_visited(_bfs.get(i)) )
+ swap( i,--lim);
+ _lim = lim;
+ }
+ void swap( int x, int y ) {
+ if( x==y ) return;
+ Node tx = _bfs.get(x);
+ Node ty = _bfs.get(y);
+ _bfs.set(x,ty);
+ _bfs.set(y,tx);
+ }
+ void add(Node n) {
+ _bfs.add(n);
+ _bs.set(n._nid);
+ }
+ void del(int idx) {
+ Node n = Utils.del(_bfs, idx);
+ _bs.clear(n._nid);
+ }
+ boolean any_visited( Node n ) {
+ for( Node def : n._inputs )
+ if( def!=null && _bs.get(def._nid) )
+ return true;
+ return false;
+ }
+ }
+}
diff --git a/chapter07/src/main/java/com/seaofnodes/simple/Parser.java b/chapter07/src/main/java/com/seaofnodes/simple/Parser.java
new file mode 100644
index 00000000..99594c95
--- /dev/null
+++ b/chapter07/src/main/java/com/seaofnodes/simple/Parser.java
@@ -0,0 +1,572 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+
+import java.util.*;
+
+/**
+ * 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.
+ * <p>
+ * This is a simple recursive descent parser. All lexical analysis is done here as well.
+ */
+public class Parser {
+ /**
+ * A Global Static, unique to each compilation. This is a public, so we
+ * can make constants everywhere without having to thread the StartNode
+ * through the entire parser and optimizer.
+ * <p>
+ * To make the compiler multithreaded, this field will have to move into a TLS.
+ */
+ public static StartNode START;
+
+ public StopNode STOP;
+
+ // The Lexer. Thin wrapper over a byte[] buffer with a cursor.
+ private final Lexer _lexer;
+
+ /**
+ * Current ScopeNode - ScopeNodes change as we parse code, but at any point of time
+ * there is one current ScopeNode. The reason the current ScopeNode can change is to do with how
+ * we handle branching. See {@link #parseIf()}.
+ * <p>
+ * Each ScopeNode contains a stack of lexical scopes, each scope is a symbol table that binds
+ * variable names to Nodes. The top of this stack represents current scope.
+ * <p>
+ * We keep a list of all ScopeNodes so that we can show them in graphs.
+ * @see #parseIf()
+ * @see #_xScopes
+ */
+ public ScopeNode _scope;
+
+ /**
+ * List of keywords disallowed as identifiers
+ */
+ private final HashSet<String> KEYWORDS = new HashSet<>(){{
+ add("else");
+ add("false");
+ add("if");
+ add("int");
+ add("return");
+ add("true");
+ add("while");
+ }};
+
+
+ /**
+ * We clone ScopeNodes when control flows branch; it is useful to have
+ * a list of all active ScopeNodes for purposes of visualization of the SoN graph
+ */
+ public final Stack<ScopeNode> _xScopes = new Stack<>();
+
+ public Parser(String source, TypeInteger arg) {
+ Node.reset();
+ _lexer = new Lexer(source);
+ _scope = new ScopeNode();
+ START = new StartNode(new Type[]{ Type.CONTROL, arg });
+ STOP = new StopNode();
+ }
+
+ public Parser(String source) {
+ this(source, TypeInteger.BOT);
+ }
+
+ @Override
+ public String toString() { return _lexer.toString(); }
+
+ String src() { return new String( _lexer._input ); }
+
+ // Debugging utility to find a Node by index
+ public static Node find(int nid) { return START.find(nid); }
+
+ private Node ctrl() { return _scope.ctrl(); }
+
+ private Node ctrl(Node n) { return _scope.ctrl(n); }
+
+ public StopNode parse() { return parse(false); }
+ public StopNode parse(boolean show) {
+ // Enter a new scope for the initial control and arguments
+ _scope.push();
+ _scope.define(ScopeNode.CTRL, new ProjNode(START, 0, ScopeNode.CTRL).peephole());
+ _scope.define(ScopeNode.ARG0, new ProjNode(START, 1, ScopeNode.ARG0).peephole());
+ parseBlock();
+ _scope.pop();
+ if (!_lexer.isEOF()) throw error("Syntax error, unexpected " + _lexer.getAnyNextToken());
+ STOP.peephole();
+ if( show ) showGraph();
+ return STOP;
+ }
+
+
+ /**
+ * Parses a block
+ *
+ * <pre>
+ * '{' statements '}'
+ * </pre>
+ * Does not parse the opening or closing '{}'
+ * @return a {@link Node} or {@code null}
+ */
+ private Node parseBlock() {
+ // Enter a new scope
+ _scope.push();
+ while (!peek('}') && !_lexer.isEOF())
+ parseStatement();
+ // Exit scope
+ _scope.pop();
+ return null;
+ }
+
+ /**
+ * Parses a statement
+ *
+ * <pre>
+ * returnStatement | declStatement | blockStatement | ifStatement | expressionStatement
+ * </pre>
+ * @return a {@link Node} or {@code null}
+ */
+ private Node parseStatement() {
+ if (matchx("return") ) return parseReturn();
+ else if (matchx("int")) return parseDecl();
+ else if (match ("{" )) return require(parseBlock(),"}");
+ else if (matchx("if" )) return parseIf();
+ else if (matchx("while")) return parseWhile();
+ else if (matchx("#showGraph")) return require(showGraph(),";");
+ else return parseExpressionStatement();
+ }
+
+ /**
+ * Parses a while statement
+ *
+ * <pre>
+ * while ( expression ) statement
+ * </pre>
+ * @return a {@link Node}, never {@code null}
+ */
+ private Node parseWhile() {
+ require("(");
+
+ // Loop region has two control inputs, the first one is the entry
+ // point, and second one is back edge that is set after loop is parsed
+ // (see end_loop() call below). Note that the absence of back edge is
+ // used as an indicator to switch off peepholes of the region and
+ // associated phis.
+
+ ctrl(new LoopNode(ctrl(),null).peephole());
+ ScopeNode head = _xScopes.push(_scope).keep(); // Save the current scope as the loop head
+ // Make a new Scope for the body.
+ _scope = _scope.dup(true);
+
+ // Parse predicate
+ var pred = require(parseExpression(), ")");
+ // IfNode takes current control and predicate
+ IfNode ifNode = (IfNode)new IfNode(ctrl(), pred).<IfNode>keep().peephole();
+ // Setup projection nodes
+ Node ifT = new ProjNode(ifNode, 0, "True" ).peephole();
+ ifNode.unkeep();
+ Node ifF = new ProjNode(ifNode, 1, "False").peephole();
+ // The exit scope, accounting for any side effects in the predicate
+ var exit = _scope.dup();
+ _xScopes.push(exit);
+ exit.ctrl(ifF);
+
+ // Parse the true side, which corresponds to loop body
+ ctrl(ifT); // set ctrl token to ifTrue projection
+ parseStatement(); // Parse loop body
+
+ // The true branch loops back, so whatever is current control gets
+ // added to head loop as input
+ head.endLoop(_scope, exit);
+ head.unkeep().kill();
+ _xScopes.pop();
+ _xScopes.pop(); // Discard pushed from graph display
+
+ // At exit the false control is the current control, and
+ // the scope is the exit scope after the exit test.
+ return (_scope = exit);
+ }
+
+ /**
+ * Parses a statement
+ *
+ * <pre>
+ * if ( expression ) statement [else statement]
+ * </pre>
+ * @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).<IfNode>keep().peephole();
+ // Setup projection nodes
+ Node ifT = new ProjNode(ifNode, 0, "True" ).peephole();
+ ifNode.unkeep();
+ 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
+ _xScopes.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();
+ fScope = _scope;
+ }
+
+ if( tScope.nIns() != ndefs || fScope.nIns() != ndefs )
+ throw error("Cannot define a new name on one arm of an if");
+
+ // Merge results
+ _scope = tScope;
+ _xScopes.pop(); // Discard pushed from graph display
+
+ return ctrl(tScope.mergeScopes(fScope));
+ }
+
+
+ /**
+ * Parses a return statement; "return" already parsed.
+ * The $ctrl edge is killed.
+ *
+ * <pre>
+ * 'return' expr ;
+ * </pre>
+ * @return an expression {@link Node}, never {@code null}
+ */
+ private Node parseReturn() {
+ var expr = require(parseExpression(), ";");
+ Node ret = STOP.addReturn(new ReturnNode(ctrl(), expr).peephole());
+ ctrl(new ConstantNode(Type.XCONTROL).peephole()); // Kill control
+ return ret;
+ }
+
+ /**
+ * Dumps out the node graph
+ * @return {@code null}
+ */
+ private Node showGraph() {
+ System.out.println(new GraphVisualizer().generateDotOutput(this));
+ return null;
+ }
+
+ /**
+ * Parses an expression statement
+ *
+ * <pre>
+ * name '=' expression ';'
+ * </pre>
+ * @return an expression {@link Node}, never {@code null}
+ */
+ private Node parseExpressionStatement() {
+ var name = requireId();
+ require("=");
+ var expr = require(parseExpression(), ";");
+ if( _scope.update(name, expr)==null )
+ throw error("Undefined name '" + name + "'");
+ return expr;
+ }
+
+ /**
+ * Parses a declStatement
+ *
+ * <pre>
+ * 'int' name = expression ';'
+ * </pre>
+ * @return an expression {@link Node}, never {@code null}
+ */
+ private Node parseDecl() {
+ // Type is 'int' for now
+ var name = requireId();
+ require("=");
+ var expr = require(parseExpression(), ";");
+ if( _scope.define(name,expr) == null )
+ throw error("Redefining name '" + name + "'");
+ return expr;
+ }
+
+ /**
+ * Parse an expression of the form:
+ *
+ * <pre>
+ * expr : compareExpr
+ * </pre>
+ * @return an expression {@link Node}, never {@code null}
+ */
+ private Node parseExpression() { return parseComparison(); }
+
+ /**
+ * Parse an expression of the form:
+ *
+ * <pre>
+ * expr : additiveExpr op additiveExpr
+ * </pre>
+ * @return an comparator expression {@link Node}, never {@code null}
+ */
+ private Node parseComparison() {
+ var lhs = parseAddition();
+ if (match("==")) return new BoolNode.EQ(lhs, parseComparison()).peephole();
+ if (match("!=")) return new NotNode(new BoolNode.EQ(lhs, parseComparison()).peephole()).peephole();
+ if (match("<" )) return new BoolNode.LT(lhs, parseComparison()).peephole();
+ if (match("<=")) return new BoolNode.LE(lhs, parseComparison()).peephole();
+ if (match(">" )) return new BoolNode.LT(parseComparison(), lhs).peephole();
+ if (match(">=")) return new BoolNode.LE(parseComparison(), lhs).peephole();
+ return lhs;
+ }
+
+ /**
+ * Parse an additive expression
+ *
+ * <pre>
+ * additiveExpr : multiplicativeExpr (('+' | '-') multiplicativeExpr)*
+ * </pre>
+ * @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();
+ return lhs;
+ }
+
+ /**
+ * Parse an multiplicativeExpr expression
+ *
+ * <pre>
+ * multiplicativeExpr : unaryExpr (('*' | '/') unaryExpr)*
+ * </pre>
+ * @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();
+ return lhs;
+ }
+
+ /**
+ * Parse a unary minus expression.
+ *
+ * <pre>
+ * unaryExpr : ('-') unaryExpr | primaryExpr
+ * </pre>
+ * @return a unary expression {@link Node}, never {@code null}
+ */
+ private Node parseUnary() {
+ if (match("-")) return new MinusNode(parseUnary()).peephole();
+ return parsePrimary();
+ }
+
+ /**
+ * Parse a primary expression:
+ *
+ * <pre>
+ * primaryExpr : integerLiteral | Identifier | true | false | '(' expression ')'
+ * </pre>
+ * @return a primary {@link Node}, never {@code null}
+ */
+ private Node parsePrimary() {
+ if( _lexer.isNumber() ) return parseIntegerLiteral();
+ if( match("(") ) return require(parseExpression(), ")");
+ if( matchx("true" ) ) return new ConstantNode(TypeInteger.constant(1)).peephole();
+ if( matchx("false") ) return new ConstantNode(TypeInteger.constant(0)).peephole();
+ 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 + "'");
+ }
+
+ /**
+ * Parse integer literal
+ *
+ * <pre>
+ * integerLiteral: [1-9][0-9]* | [0]
+ * </pre>
+ */
+ private ConstantNode parseIntegerLiteral() {
+ return (ConstantNode) new ConstantNode(_lexer.parseNumber()).peephole();
+ }
+
+ //////////////////////////////////
+ // Utilities for lexical analysis
+
+ // Return true and skip if "syntax" is next in the stream.
+ 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 && !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 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);
+ }
+
+ ////////////////////////////////////
+ // Lexer components
+
+ private static class Lexer {
+
+ // Input buffer; an array of text bytes read from a file or a string
+ private final byte[] _input;
+ // Tracks current position in input buffer
+ private int _position = 0;
+
+ /**
+ * Record the source text for lexing
+ */
+ public Lexer(String source) {
+ this(source.getBytes());
+ }
+
+ /**
+ * Direct from disk file source
+ */
+ public Lexer(byte[] buf) {
+ _input = buf;
+ }
+
+ // Very handy in the debugger, shows the unparsed program
+ @Override
+ public String toString() {
+ return new String(_input, _position, _input.length - _position);
+ }
+
+ // True if at EOF
+ private boolean isEOF() {
+ return _position >= _input.length;
+ }
+
+ // Peek next character, or report EOF
+ private char peek() {
+ return isEOF() ? Character.MAX_VALUE // Special value that causes parsing to terminate
+ : (char) _input[_position];
+ }
+
+ private char nextChar() {
+ char ch = peek();
+ _position++;
+ return ch;
+ }
+
+ // True if a white space
+ private boolean isWhiteSpace() {
+ return peek() <= ' '; // Includes all the use space, tab, newline, CR
+ }
+
+ /**
+ * Return the next non-white-space character
+ */
+ private void skipWhiteSpace() {
+ while (isWhiteSpace()) _position++;
+ }
+
+
+ // 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) {
+ 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))
+ 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();
+ return isIdStart(peek()) ? parseId() : null;
+ }
+
+ // Used for errors
+ String getAnyNextToken() {
+ 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);}
+
+ private Type parseNumber() {
+ int start = _position;
+ while (isNumber(nextChar())) ;
+ 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 TypeInteger.constant(Long.parseLong(snum));
+ }
+
+ // First letter of an identifier
+ private boolean isIdStart(char ch) {
+ return Character.isAlphabetic(ch) || ch == '_';
+ }
+
+ // All characters of an identifier, e.g. "_x123"
+ private boolean isIdLetter(char ch) {
+ return Character.isLetterOrDigit(ch) || ch == '_';
+ }
+
+ private String parseId() {
+ int start = _position;
+ while (isIdLetter(nextChar())) ;
+ return new String(_input, start, --_position - start);
+ }
+
+ //
+ private boolean isPunctuation(char ch) {
+ return "=;[]<>()+-/*".indexOf(ch) != -1;
+ }
+
+ private String parsePunctuation() {
+ int start = _position;
+ return new String(_input, start, 1);
+ }
+ }
+}
diff --git a/chapter07/src/main/java/com/seaofnodes/simple/Utils.java b/chapter07/src/main/java/com/seaofnodes/simple/Utils.java
new file mode 100644
index 00000000..4d663c25
--- /dev/null
+++ b/chapter07/src/main/java/com/seaofnodes/simple/Utils.java
@@ -0,0 +1,39 @@
+package com.seaofnodes.simple;
+
+import java.util.ArrayList;
+
+public class Utils {
+ public static RuntimeException TODO() { return TODO("Not yet implemented"); }
+ public static RuntimeException TODO(String msg) { return new RuntimeException(msg); }
+
+ /**
+ * Fast, constant-time, element removal. Does not preserve order
+ *
+ * @param array ArrayList to modify
+ * @param i element to be removed
+ * @return element removed
+ */
+ public static <E> E del(ArrayList<E> array, int i) {
+ if ( i >= 0 && i < array.size() ) {
+ E tmp = array.get(i);
+ E last = array.removeLast();
+ if (i < array.size()) array.set(i, last);
+ return tmp;
+ }
+ return null;
+ }
+
+ /**
+ * Search a list for an element by reference
+ *
+ * @param ary List to search in
+ * @param x Object to be searched
+ * @return >= 0 on success, -1 on failure
+ */
+ public static <E> int find( ArrayList<E> ary, E x ) {
+ for( int i=0; i<ary.size(); i++ )
+ if( ary.get(i)==x )
+ return i;
+ return -1;
+ }
+} \ No newline at end of file
diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/AddNode.java
new file mode 100644
index 00000000..85d7b20e
--- /dev/null
+++ b/chapter07/src/main/java/com/seaofnodes/simple/node/AddNode.java
@@ -0,0 +1,158 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.*;
+
+import java.util.BitSet;
+
+public class AddNode extends Node {
+ public AddNode(Node lhs, Node rhs) {
+ super(null, lhs, rhs);
+ }
+
+ @Override public String label() { return "Add"; }
+
+ @Override public String glabel() { return "+"; }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ in(1)._print0(sb.append("("), visited);
+ in(2)._print0(sb.append("+"), visited);
+ return sb.append(")");
+ }
+
+
+ @Override
+ public Type compute() {
+ if( in(1)._type instanceof TypeInteger i0 &&
+ in(2)._type instanceof TypeInteger i1 ) {
+ if (i0.isConstant() && i1.isConstant())
+ return TypeInteger.constant(i0.value()+i1.value());
+ return i0.meet(i1);
+ }
+ return Type.BOTTOM;
+ }
+
+ @Override
+ public Node idealize () {
+ Node lhs = in(1);
+ Node rhs = in(2);
+ 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 instanceof TypeInteger i && i.value()==0 )
+ return lhs;
+
+ // 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).
+
+ // Move non-adds to RHS
+ if( !(lhs instanceof AddNode) && rhs instanceof AddNode )
+ return swap12();
+
+ // Now we might see (add add non) or (add non non) or (add add add) but never (add non add)
+
+ // 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) )
+ // Rotate; look for (add (phi cons) con/(phi cons))
+ return spline_cmp(lhs,rhs) ? swap12() : phiCon(this,true);
+
+ // 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());
+
+
+ // Do we have ((x + (phi cons)) + con) ?
+ // Do we have ((x + (phi cons)) + (phi cons)) ?
+ // Push constant up through the phi: x + (phi con0+con0 con1+con1...)
+ Node phicon = phiCon(this,true);
+ if( phicon!=null ) return phicon;
+
+ // 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;
+ }
+
+ // Rotation is only valid for associative ops, e.g. Add, Mul, And, Or.
+ // Do we have ((phi cons)|(x + (phi cons)) + con|(phi cons)) ?
+ // Push constant up through the phi: x + (phi con0+con0 con1+con1...)
+ static Node phiCon(Node op, boolean rotate) {
+ Node lhs = op.in(1);
+ Node rhs = op.in(2);
+ // LHS is either a Phi of constants, or another op with Phi of constants
+ PhiNode lphi = pcon(lhs);
+ if( rotate && lphi==null && lhs.nIns() > 2 ) {
+ // Only valid to rotate constants if both are same associative ops
+ if( lhs.getClass() != op.getClass() ) return null;
+ lphi = pcon(lhs.in(2)); // Will rotate with the Phi push
+ }
+ if( lphi==null ) return null;
+
+ // RHS is a constant or a Phi of constants
+ if( !(rhs instanceof ConstantNode con) && pcon(rhs)==null )
+ return null;
+
+ // If both are Phis, must be same Region
+ if( rhs instanceof PhiNode && lphi.in(0) != rhs.in(0) )
+ return null;
+
+ // Note that this is the exact reverse of Phi pulling a common op down
+ // to reduce total op-count. We don't get in an endless push-up
+ // push-down peephole cycle because the constants all fold first.
+ Node[] ns = new Node[lphi.nIns()];
+ ns[0] = lphi.in(0);
+ // Push constant up through the phi: x + (phi con0+con0 con1+con1...)
+ for( int i=1; i<ns.length; i++ )
+ ns[i] = op.copy(lphi.in(i), rhs instanceof PhiNode ? rhs.in(i) : rhs).peephole();
+ String label = lphi._label + (rhs instanceof PhiNode rphi ? rphi._label : "");
+ Node phi = new PhiNode(label,ns).peephole();
+ // Rotate needs another op, otherwise just the phi
+ return lhs==lphi ? phi : op.copy(lhs.in(1),phi);
+ }
+
+ static PhiNode pcon(Node op) {
+ return op instanceof PhiNode phi && phi.all_cons() ? phi : 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 Phi-of-constants, then muls, 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 ;
+
+ if( lo instanceof PhiNode && lo.all_cons() ) return false;
+ if( hi instanceof PhiNode && hi.all_cons() ) return true ;
+
+ if( lo instanceof PhiNode && !(hi instanceof PhiNode) ) return true;
+ if( hi instanceof PhiNode && !(lo instanceof PhiNode) ) return false;
+
+ // Same category of "others"
+ return lo._nid > hi._nid;
+ }
+
+ @Override Node copy(Node lhs, Node rhs) { return new AddNode(lhs,rhs); }
+}
diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/BoolNode.java
new file mode 100644
index 00000000..d65fbf14
--- /dev/null
+++ b/chapter07/src/main/java/com/seaofnodes/simple/node/BoolNode.java
@@ -0,0 +1,60 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+
+import java.util.BitSet;
+
+abstract public class BoolNode extends Node {
+
+ public BoolNode(Node lhs, Node rhs) {
+ super(null, lhs, rhs);
+ }
+
+ abstract String op(); // String opcode name
+
+ @Override
+ public String label() { return getClass().getSimpleName(); }
+
+ @Override
+ public String glabel() { return op(); }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ in(1)._print0(sb.append("("), visited);
+ in(2)._print0(sb.append(op()), visited);
+ return sb.append(")");
+ }
+
+ @Override
+ public Type compute() {
+ if( in(1)._type instanceof TypeInteger i0 &&
+ in(2)._type instanceof TypeInteger i1 ) {
+ if (i0.isConstant() && i1.isConstant())
+ return TypeInteger.constant(doOp(i0.value(), i1.value()) ? 1 : 0);
+ return i0.meet(i1);
+ }
+ return Type.BOTTOM;
+ }
+
+ abstract boolean doOp(long lhs, long rhs);
+
+ @Override
+ public Node idealize() {
+ // Compare of same
+ if( in(1)==in(2) )
+ return new ConstantNode(TypeInteger.constant(doOp(3,3)?1:0));
+
+ // Do we have ((x * (phi cons)) * con) ?
+ // Do we have ((x * (phi cons)) * (phi cons)) ?
+ // Push constant up through the phi: x * (phi con0*con0 con1*con1...)
+ Node phicon = AddNode.phiCon(this,false);
+ if( phicon!=null ) return phicon;
+
+ return null;
+ }
+
+ public static class EQ extends BoolNode { public EQ(Node lhs, Node rhs) { super(lhs,rhs); } String op() { return "=="; } boolean doOp(long lhs, long rhs) { return lhs == rhs; } Node copy(Node lhs, Node rhs) { return new EQ(lhs,rhs); } }
+ public static class LT extends BoolNode { public LT(Node lhs, Node rhs) { super(lhs,rhs); } String op() { return "<" ; } boolean doOp(long lhs, long rhs) { return lhs < rhs; } Node copy(Node lhs, Node rhs) { return new LT(lhs,rhs); } }
+ public static class LE extends BoolNode { public LE(Node lhs, Node rhs) { super(lhs,rhs); } String op() { return "<="; } boolean doOp(long lhs, long rhs) { return lhs <= rhs; } Node copy(Node lhs, Node rhs) { return new LE(lhs,rhs); } }
+}
diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/ConstantNode.java
new file mode 100644
index 00000000..0e13d2f2
--- /dev/null
+++ b/chapter07/src/main/java/com/seaofnodes/simple/node/ConstantNode.java
@@ -0,0 +1,45 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.Type;
+
+import java.util.BitSet;
+
+/**
+ * A Constant node represents a constant value. At present, the only constants
+ * that we allow are integer literals; therefore Constants contain an integer
+ * value. As we add other types of constants, we will refactor how we represent
+ * Constants.
+ * <p>
+ * Constants have no semantic inputs. However, we set Start as an input to
+ * Constants to enable a forward graph walk. This edge carries no semantic
+ * meaning, and it is present <em>solely</em> to allow visitation.
+ * <p>
+ * The Constant's value is the value stored in it.
+ */
+public class ConstantNode extends Node {
+ Type _con;
+ public ConstantNode( Type type ) {
+ super(Parser.START);
+ _con = type;
+ }
+
+ @Override
+ public String label() { return ""+_con; }
+
+ @Override
+ public String uniqueName() { return "Con_" + _nid; }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return _con._print(sb);
+ }
+
+ @Override public boolean isMultiTail() { return true; }
+
+ @Override
+ public Type compute() { return _con; }
+
+ @Override
+ public Node idealize() { return null; }
+}
diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/DivNode.java
new file mode 100644
index 00000000..b0e12547
--- /dev/null
+++ b/chapter07/src/main/java/com/seaofnodes/simple/node/DivNode.java
@@ -0,0 +1,39 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+
+import java.util.BitSet;
+
+public class DivNode extends Node {
+ public DivNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
+
+ @Override public String label() { return "Div"; }
+
+ @Override public String glabel() { return "//"; }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ in(1)._print0(sb.append("("), visited);
+ in(2)._print0(sb.append("/"), visited);
+ return sb.append(")");
+ }
+
+ @Override
+ public Type compute() {
+ if (in(1)._type instanceof TypeInteger i0 &&
+ in(2)._type instanceof TypeInteger i1) {
+ if (i0.isConstant() && i1.isConstant())
+ return i1.value() == 0
+ ? TypeInteger.ZERO
+ : TypeInteger.constant(i0.value()/i1.value());
+ return i0.meet(i1);
+ }
+ return Type.BOTTOM;
+ }
+
+ @Override
+ public Node idealize() { return null; }
+
+ @Override Node copy(Node lhs, Node rhs) { return new DivNode(lhs,rhs); }
+}
diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/IfNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/IfNode.java
new file mode 100644
index 00000000..e573010d
--- /dev/null
+++ b/chapter07/src/main/java/com/seaofnodes/simple/node/IfNode.java
@@ -0,0 +1,58 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.type.TypeTuple;
+
+import java.util.BitSet;
+
+public class IfNode extends MultiNode {
+
+ public IfNode(Node ctrl, Node pred) {
+ super(ctrl, pred);
+ }
+
+ @Override
+ public String label() { return "If"; }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ sb.append("if( ");
+ return in(1)._print0(sb, visited).append(" )");
+ }
+
+ @Override public boolean isCFG() { return true; }
+ @Override public boolean isMultiHead() { return true; }
+
+ public Node ctrl() { return in(0); }
+ public Node pred() { return in(1); }
+
+ @Override
+ public Type compute() {
+ // If the If node is not reachable then neither is any following Proj
+ if (ctrl()._type != Type.CONTROL && ctrl()._type != Type.BOTTOM )
+ return TypeTuple.IF_NEITHER;
+ // If constant is 0 then false branch is reachable
+ // Else true branch is reachable
+ if (pred()._type instanceof TypeInteger ti && ti.isConstant()) {
+ if (ti.value() == 0) return TypeTuple.IF_FALSE;
+ else return TypeTuple.IF_TRUE;
+ }
+
+ // Hunt up the immediate dominator tree. If we find an identical if
+ // test on either the true or false branch, then this test matches.
+ for( Node dom = idom(), prior=this; dom!=null; prior=dom, dom = dom.idom() )
+ if( dom instanceof IfNode iff && iff.pred()==pred() )
+ return prior instanceof ProjNode proj
+ // Repeated test, dominated on one side. Test result is the same.
+ ? (proj._idx==0 ? TypeTuple.IF_TRUE : TypeTuple.IF_FALSE)
+ : null; // Repeated test not dominated on one side
+
+ return TypeTuple.IF_BOTH;
+ }
+
+ @Override
+ public Node idealize() {
+ return null;
+ }
+}
diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/LoopNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/LoopNode.java
new file mode 100644
index 00000000..eaf19199
--- /dev/null
+++ b/chapter07/src/main/java/com/seaofnodes/simple/node/LoopNode.java
@@ -0,0 +1,35 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+
+import java.util.BitSet;
+
+public class LoopNode extends RegionNode {
+
+ public LoopNode( Node entry, Node back) { super(null,entry,back); }
+
+ Node entry() { return in(1); }
+ Node back () { return in(2); }
+
+ @Override
+ public String label() { return "Loop"; }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return sb.append("Loop").append(_nid);
+ }
+
+ @Override
+ public Type compute() {
+ return inProgress() ? Type.CONTROL : super.compute();
+ }
+
+ @Override
+ public Node idealize() {
+ return inProgress() ? null : super.idealize();
+ }
+
+ @Override Node idom() { return entry(); }
+
+ @Override public boolean inProgress() { return back()==null; }
+}
diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/MinusNode.java
new file mode 100644
index 00000000..8a96693d
--- /dev/null
+++ b/chapter07/src/main/java/com/seaofnodes/simple/node/MinusNode.java
@@ -0,0 +1,30 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+
+import java.util.BitSet;
+
+public class MinusNode extends Node {
+ public MinusNode(Node in) { super(null, in); }
+
+ @Override public String label() { return "Minus"; }
+
+ @Override public String glabel() { return "-"; }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ in(1)._print0(sb.append("(-"), visited);
+ return sb.append(")");
+ }
+
+ @Override
+ public Type compute() {
+ if (in(1)._type instanceof TypeInteger i0)
+ return i0.isConstant() ? TypeInteger.constant(-i0.value()) : i0;
+ return Type.BOTTOM;
+ }
+
+ @Override
+ public Node idealize() { return null; }
+}
diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/MulNode.java
new file mode 100644
index 00000000..70e6c0db
--- /dev/null
+++ b/chapter07/src/main/java/com/seaofnodes/simple/node/MulNode.java
@@ -0,0 +1,58 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+
+import java.util.BitSet;
+
+public class MulNode extends Node {
+ public MulNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
+
+ @Override public String label() { return "Mul"; }
+
+ @Override public String glabel() { return "*"; }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ in(1)._print0(sb.append("("), visited);
+ in(2)._print0(sb.append("*"), visited);
+ return sb.append(")");
+ }
+
+ @Override
+ public Type compute() {
+ if (in(1)._type instanceof TypeInteger i0 &&
+ in(2)._type instanceof TypeInteger i1) {
+ if (i0.isConstant() && i1.isConstant())
+ return TypeInteger.constant(i0.value()*i1.value());
+ return i0.meet(i1);
+ }
+ return Type.BOTTOM;
+ }
+
+ @Override
+ public Node idealize() {
+ Node lhs = in(1);
+ Node rhs = in(2);
+ Type t1 = lhs._type;
+ Type t2 = rhs._type;
+
+ // Mul of 1. We do not check for (1*x) because this will already
+ // canonicalize to (x*1)
+ if ( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==1 )
+ return lhs;
+
+ // Move constants to RHS: con*arg becomes arg*con
+ if ( t1.isConstant() && !t2.isConstant() )
+ return swap12();
+
+ // Do we have ((x * (phi cons)) * con) ?
+ // Do we have ((x * (phi cons)) * (phi cons)) ?
+ // Push constant up through the phi: x * (phi con0*con0 con1*con1...)
+ Node phicon = AddNode.phiCon(this,true);
+ if( phicon!=null ) return phicon;
+
+ return null;
+ }
+ @Override Node copy(Node lhs, Node rhs) { return new MulNode(lhs,rhs); }
+}
diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/MultiNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/MultiNode.java
new file mode 100644
index 00000000..81c90f68
--- /dev/null
+++ b/chapter07/src/main/java/com/seaofnodes/simple/node/MultiNode.java
@@ -0,0 +1,7 @@
+package com.seaofnodes.simple.node;
+
+public abstract class MultiNode extends Node {
+
+ public MultiNode(Node... inputs) { super(inputs); }
+
+}
diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter07/src/main/java/com/seaofnodes/simple/node/Node.java
new file mode 100644
index 00000000..fcc8df85
--- /dev/null
+++ b/chapter07/src/main/java/com/seaofnodes/simple/node/Node.java
@@ -0,0 +1,467 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.type.Type;
+
+import java.util.*;
+
+/**
+ * All Nodes in the Sea of Nodes IR inherit from the Node class.
+ * The Node class provides common functionality used by all subtypes.
+ * Subtypes of Node specialize by overriding methods.
+ */
+public abstract class Node {
+
+ /**
+ * Each node has a unique dense Node ID within a compilation context
+ * The ID is useful for debugging, for using as an offset in a bitvector,
+ * as well as for computing equality of nodes (to be implemented later).
+ */
+ public final int _nid;
+
+ /**
+ * Inputs to the node. These are use-def references to Nodes.
+ * <p>
+ * 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 {@link #isCFG} node.
+ */
+ public final ArrayList<Node> _inputs;
+
+ /**
+ * Outputs reference Nodes that are not null and have this Node as an
+ * input. These nodes are users of this node, thus these are def-use
+ * references to Nodes.
+ * <p>
+ * Outputs directly match inputs, making a directed graph that can be
+ * walked in either direction. These outputs are typically used for
+ * efficient optimizations but otherwise have no semantics meaning.
+ */
+ public final ArrayList<Node> _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;
+
+
+ /**
+ * Immediate dominator tree depth, used to approximate a real IDOM during
+ * parsing where we do not have the whole program, and also peepholes
+ * change the CFG incrementally.
+ * <p>
+ * See {@link <a href="https://en.wikipedia.org/wiki/Dominator_(graph_theory)">...</a>}
+ */
+ int _idepth;
+
+ /**
+ * A private Global Static mutable counter, for unique node id generation.
+ * To make the compiler multithreaded, this field will have to move into a TLS.
+ * Starting with value 1, to avoid bugs confusing node ID 0 with uninitialized values.
+ * */
+ private static int UNIQUE_ID = 1;
+
+ 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.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(); }
+
+
+ // ------------------------------------------------------------------------
+
+ // Debugger Printing.
+
+ // {@code toString} is what you get in the debugger. It has to print 1
+ // line (because this is what a debugger typically displays by default) and
+ // has to be robust with broken graph/nodes.
+ @Override
+ public final String toString() { 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. 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 _print0(new StringBuilder(), new BitSet()).toString();
+ }
+ // This is the common print: check for repeats, check for DEAD and print
+ // "DEAD" else call the per-Node print1.
+ final StringBuilder _print0(StringBuilder sb, BitSet visited) {
+ if (visited.get(_nid))
+ return sb.append(label());
+ visited.set(_nid);
+ return isDead()
+ ? sb.append(uniqueName()).append(":DEAD")
+ : _print1(sb, visited);
+ }
+ // Every Node implements this; a partial-line recursive print
+ abstract StringBuilder _print1(StringBuilder sb, BitSet visited);
+
+
+ // Print a node on 1 line, columnar aligned, as:
+ // NNID NNAME DDEF DDEF [[ UUSE UUSE ]] TYPE
+ // 1234 sssss 1234 1234 1234 1234 1234 1234 tttttt
+ public void _print_line( StringBuilder sb ) {
+ sb.append("%4d %-7.7s ".formatted(_nid,label()));
+ if( _inputs==null ) {
+ sb.append("DEAD\n");
+ return;
+ }
+ for( Node def : _inputs )
+ sb.append(def==null ? "____ " : "%4d ".formatted(def._nid));
+ for( int i = _inputs.size(); i<3; i++ )
+ sb.append(" ");
+ sb.append(" [[ ");
+ for( Node use : _outputs )
+ sb.append("%4d ".formatted(use._nid));
+ int lim = 5 - Math.max(_inputs.size(),3);
+ for( int i = _outputs.size(); i<lim; i++ )
+ sb.append(" ");
+ sb.append(" ]] ");
+ if( _type!= null ) _type._print(sb);
+ sb.append("\n");
+ }
+
+ public boolean isMultiHead() { return false; }
+ public boolean isMultiTail() { return false; }
+
+ // ------------------------------------------------------------------------
+ // Graph Node & Edge manipulation
+
+ /**
+ * Gets the ith input node
+ * @param i Offset of the input node
+ * @return Input node or null
+ */
+ public Node in(int i) { return _inputs.get(i); }
+
+ public int nIns() { return _inputs.size(); }
+
+ 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; }
+
+ /**
+ * Change a <em>def</em> into a Node. Keeps the edges correct, by removing
+ * the corresponding <em>use->def</em> edge. This may make the original
+ * <em>def</em> go dead. This function is co-recursive with {@link #kill}.
+ * <p>
+
+ * This method is the normal path for altering a Node, because it does the
+ * proper default edge maintenance. It also <em>immediately</em> kills
+ * Nodes that lose their last use; at times care must be taken to avoid
+ * killing Nodes that are being used without having an output Node. This
+ * definitely happens in the middle of recursive {@link #peephole} calls.
+ *
+ * @param idx which def to set
+ * @param new_def the new definition
+ * @return new_def for flow coding
+ */
+ Node set_def(int idx, Node new_def ) {
+ Node old_def = in(idx);
+ 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.
+ if( new_def != null )
+ 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
+ // Set the new_def over the old (killed) edge
+ _inputs.set(idx,new_def);
+ // Return self for easy flow-coding
+ return new_def;
+ }
+
+ // Remove the numbered input, compressing the inputs in-place. This
+ // shuffles the order deterministically - which is suitable for Region and
+ // Phi, but not for every Node.
+ void delDef(int idx) {
+ Node old_def = in(idx);
+ 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
+ Utils.del(_inputs, idx);
+ }
+
+ /**
+ * Add a new def to an existing Node. Keep the edges correct by
+ * adding the corresponding <em>def->use</em> edge.
+ *
+ * @param new_def the new definition, appended to the end of existing definitions
+ * @return new_def for flow coding
+ */
+ public Node add_def(Node new_def) {
+ // Add use->def edge
+ _inputs.add(new_def);
+ // If new def is not null, add the corresponding def->use edge
+ if( new_def != null )
+ new_def.addUse(this);
+ return new_def;
+ }
+
+ // Breaks the edge invariants, used temporarily
+ protected <N extends Node> N addUse(Node n) { _outputs.add(n); return (N)this; }
+
+ // Remove node 'use' from 'def's (i.e. our) output list, by compressing the list in-place.
+ // Return true if the output list is empty afterward.
+ // Error is 'use' does not exist; ok for 'use' to be null.
+ protected boolean delUse( Node use ) {
+ Utils.del(_outputs, Utils.find(_outputs, use));
+ return _outputs.size() == 0;
+ }
+
+ // Shortcut for "popping" n nodes. A "pop" is basically a
+ // set_def(last,null) followed by lowering the nIns() count.
+ void pop_n(int n) {
+ for( int i=0; i<n; i++ ) {
+ Node old_def = _inputs.removeLast();
+ if( old_def != null && // If it exists and
+ old_def.delUse(this) ) // If we removed the last use, the old def is now dead
+ old_def.kill(); // Kill old def
+ }
+ }
+
+ /**
+ * Kill a Node with no <em>uses</em>, by setting all of its <em>defs</em>
+ * to null. This may recursively kill more Nodes and is basically dead
+ * code elimination. This function is co-recursive with {@link #pop_n}.
+ */
+ 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; }
+
+ // Shortcuts to stop DCE mid-parse
+ // Add bogus null use to keep node alive
+ public <N extends Node> N keep() { return addUse(null); }
+ // Remove bogus null.
+ public <N extends Node> N unkeep() { delUse(null); return (N)this; }
+
+
+ // Replace self with nnn in the graph, making 'this' go dead
+ void subsume( Node nnn ) {
+ assert nnn!=this;
+ while( nOuts() > 0 ) {
+ Node n = _outputs.removeLast();
+ int idx = Utils.find(n._inputs, this);
+ n._inputs.set(idx,nnn);
+ nnn.addUse(n);
+ }
+ kill();
+ }
+
+ // ------------------------------------------------------------------------
+ // Graph-based optimizations
+
+ /**
+ * 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:
+ * <ul>
+ * <li>if the Type {@link Type#isConstant}, we replace with a {@link ConstantNode}</li>
+ * <li>in a future chapter we will look for a
+ * <a href="https://en.wikipedia.org/wiki/Common_subexpression_elimination">Common Subexpression</a>
+ * to eliminate.</li>
+ * <li>we ask the Node for a better replacement. The "better replacement"
+ * is things like {@code (1+2)} becomes {@code 3} and {@code (1+(x+2))} becomes
+ * {@code (x+(1+2))}. By canonicalizing expressions we fold common addressing
+ * math constants, remove algebraic identities and generally simplify the
+ * code. </li>
+ * </ul>
+ */
+ 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
+ if (!(this instanceof ConstantNode) && type.isConstant())
+ return deadCodeElim(new ConstantNode(type).peephole());
+
+ // Future chapter: Global Value Numbering goes here
+
+ // Ask each node for a better replacement
+ Node n = idealize();
+ if( n != null ) // Something changed
+ // Recursively optimize
+ return deadCodeElim(n.peephole());
+
+ return this; // No progress
+ }
+
+ // m is the new Node, self is the old.
+ // Return 'm', which may have zero uses but is alive nonetheless.
+ // If self has zero uses (and is not 'm'), {@link #kill} self.
+ private Node deadCodeElim(Node m) {
+ // If self is going dead and not being returned here (Nodes returned
+ // from peephole commonly have no uses (yet)), then kill self.
+ if( m != this && isUnused() ) {
+ // Killing self - and since self recursively kills self's inputs we
+ // might end up killing 'm', which we are returning as a live Node.
+ // So we add a bogus extra null output edge to stop kill().
+ m.keep(); // Keep m alive
+ kill(); // Kill self because replacing with 'm'
+ m.unkeep(); // Okay to peephole m
+ }
+ return m;
+ }
+
+ /**
+ * This function needs to be
+ * <a href="https://en.wikipedia.org/wiki/Monotonic_function">Monotonic</a>
+ * as it is part of a Monotone Analysis Framework.
+ * <a href="https://www.cse.psu.edu/~gxt29/teaching/cse597s21/slides/08monotoneFramework.pdf">See for example this set of slides</a>.
+ * <p>
+ * For Chapter 2, all our Types are really integer constants, and so all
+ * the needed properties are trivially true, and we can ignore the high
+ * theory. Much later on, this will become important and allow us to do
+ * many fancy complex optimizations trivially... because theory.
+ * <p>
+ * compute() needs to be stand-alone, and cannot recursively call compute
+ * on its inputs programs are cyclic (have loops!) and this will just
+ * infinitely recurse until stack overflow. Instead, compute typically
+ * computes a new type from the {@link #_type} field of its inputs.
+ */
+ public abstract Type compute();
+
+ /**
+ * This function rewrites the current Node into a more "idealized" form.
+ * This is the bulk of our peephole rewrite rules, and we use this to
+ * e.g. turn arbitrary collections of adds and multiplies with mixed
+ * constants into a normal form that's easy for hardware to implement.
+ * Example: An array addressing expression:
+ * <pre> ary[idx+1]</pre>
+ * might turn into Sea-of-Nodes IR:
+ * <pre> (ary+12)+((idx+1) * 4)</pre>
+ * This expression can then be idealized into:
+ * <pre> ary + ((idx*4) + (12 + (1*4)))</pre>
+ * And more folding:
+ * <pre> ary + ((idx<<2) + 16)</pre>
+ * And during code-gen:
+ * <pre> MOV4 Rary,Ridx,16 // or some such hardware-specific notation </pre>
+ * <p>
+ * {@link #idealize} has a very specific calling convention:
+ * <ul>
+ * <li>If NO change is made, return {@code null}
+ * <li>If ANY change is made, return not-null; this can be {@code this}
+ * <li>The returned Node does NOT call {@link #peephole} on itself; the {@link #peephole} call will recursively peephole it.
+ * <li>Any NEW nodes that are not directly returned DO call {@link #peephole}.
+ * </ul>
+ * <p>
+ * Examples:
+ * <table border="3">
+ * <tr><th> before </th><th> after </th><th>return </th><th>comment </th></tr>
+ * <tr><td>{@code (x+5) }</td><td>{@code (x+5) }</td><td>{@code null }</td><td>No change</td></tr>
+ * <tr><td>{@code (5+x) }</td><td>{@code (x+5) }</td><td>{@code this }</td><td>Swapped arguments</td></tr>
+ * <tr><td>{@code ((x+1)+2)}</td><td>{@code (x+(1+2))}</td><td>{@code (x+_) }</td><td>Returns 2 new Nodes</td></tr>
+ * </table>
+ *
+ * The last entry deserves more discussion. The new Node {@code (1+2)}
+ * created in {@link #idealize} calls {@link #peephole} (which then folds
+ * into a constant). The other new Node {@code (x+3)} does not call
+ * peephole, because it is returned and peephole itself will recursively
+ * call peephole.
+ * <p>
+ * Since idealize calls peephole and peephole calls idealize, you must be
+ * careful that all idealizations are <em>monotonic</em>: all transforms remove
+ * some feature, so that the set of available transforms always shrinks.
+ * If you don't, you risk an infinite peephole loop!
+ *
+ * @return Either a new or changed node, or null for no changes.
+ */
+ 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;
+ }
+
+ // does this node contain all constants?
+ // Ignores in(0), as is usually control.
+ boolean all_cons() {
+ for( int i=1; i<nIns(); i++ )
+ if( !(in(i)._type.isConstant()) )
+ return false;
+ return true;
+ }
+
+ // Return the immediate dominator of this Node and compute dom tree depth.
+ Node idom() {
+ Node idom = in(0);
+ if( idom._idepth==0 ) idom.idom(); // Recursively set _idepth
+ if( _idepth==0 ) _idepth = idom._idepth+1;
+ return idom;
+ }
+
+ // Make a shallow copy (same class) of this Node, with given inputs and
+ // empty outputs and a new Node ID. The original inputs are ignored.
+ // Does not need to be implemented in isCFG() nodes.
+ Node copy(Node lhs, Node rhs) { throw Utils.TODO("Binary ops need to implement copy"); }
+
+ /**
+ * 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; _disablePeephole=false; }
+
+ /**
+ * Debugging utility to find a Node by index
+ */
+ public Node find(int nid) { return _find(new BitSet(),nid); }
+ private Node _find(BitSet visit, int nid) {
+ if( _nid==nid ) return this;
+ if( visit.get(_nid) ) return null;
+ visit.set(_nid);
+ for( Node def : _inputs )
+ if( def!=null ) {
+ Node rez = def._find(visit,nid);
+ if( rez != null ) return rez;
+ }
+ for( Node use : _outputs ) {
+ Node rez = use._find(visit,nid);
+ if( rez != null ) return rez;
+ }
+ return null;
+ }
+}
diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/NotNode.java
new file mode 100644
index 00000000..04e6c75b
--- /dev/null
+++ b/chapter07/src/main/java/com/seaofnodes/simple/node/NotNode.java
@@ -0,0 +1,30 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+
+import java.util.BitSet;
+
+public class NotNode extends Node {
+ public NotNode(Node in) { super(null, in); }
+
+ @Override public String label() { return "Not"; }
+
+ @Override public String glabel() { return "!"; }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ in(1)._print0(sb.append("(!"), visited);
+ return sb.append(")");
+ }
+
+ @Override
+ public Type compute() {
+ if( in(1)._type instanceof TypeInteger i0 )
+ return i0.isConstant() ? TypeInteger.constant(i0.value()==0 ? 1 : 0) : i0;
+ return Type.BOTTOM;
+ }
+
+ @Override
+ public Node idealize() { return null; }
+}
diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/PhiNode.java
new file mode 100644
index 00000000..202dbe37
--- /dev/null
+++ b/chapter07/src/main/java/com/seaofnodes/simple/node/PhiNode.java
@@ -0,0 +1,101 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+
+import java.util.BitSet;
+
+public class PhiNode extends Node {
+
+ final String _label;
+
+ public PhiNode(String label, Node... inputs) { super(inputs); _label = label; }
+
+ @Override public String label() { return "Phi_"+_label; }
+
+ @Override public String glabel() { return "&phi;_"+_label; }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ if( region().inProgress() )
+ sb.append("Z");
+ sb.append("Phi(");
+ for( Node in : _inputs ) {
+ if (in == null) sb.append("____");
+ else in._print0(sb, visited);
+ sb.append(",");
+ }
+ sb.setLength(sb.length()-1);
+ sb.append(")");
+ return sb;
+ }
+
+ RegionNode region() { return (RegionNode)in(0); }
+ @Override public boolean isMultiTail() { return true; }
+
+ @Override
+ public Type compute() {
+ if( !(region() instanceof RegionNode r) || r.inProgress() )
+ return Type.BOTTOM;
+ Type t = Type.TOP;
+ for (int i = 1; i < nIns(); i++)
+ t = t.meet(in(i)._type);
+ return t;
+ }
+
+ @Override
+ public Node idealize() {
+ if( !(region() instanceof RegionNode r ) || r.inProgress() )
+ return null;
+
+ // If we have only a single unique input, become it.
+ Node live = singleUniqueInput();
+ if (live != null)
+ return live;
+
+ // Pull "down" a common data op. One less op in the world. One more
+ // Phi, but Phis do not make code.
+ // Phi(op(A,B),op(Q,R),op(X,Y)) becomes
+ // op(Phi(A,Q,X), Phi(B,R,Y)).
+ Node op = in(1);
+ if( op.nIns()==3 && op.in(0)==null && !op.isCFG() && same_op() ) {
+ Node[] lhss = new Node[nIns()];
+ Node[] rhss = new Node[nIns()];
+ lhss[0] = rhss[0] = in(0); // Set Region
+ for( int i=1; i<nIns(); i++ ) {
+ lhss[i] = in(i).in(1);
+ rhss[i] = in(i).in(2);
+ }
+ Node phi_lhs = new PhiNode(_label,lhss).peephole();
+ Node phi_rhs = new PhiNode(_label,rhss).peephole();
+ return op.copy(phi_lhs,phi_rhs);
+ }
+
+ return null;
+ }
+
+ private boolean same_op() {
+ for( int i=2; i<nIns(); i++ )
+ if( in(1).getClass() != in(i).getClass() )
+ return false;
+ return true;
+ }
+
+ /**
+ * If only single unique input, return it
+ */
+ private Node singleUniqueInput() {
+ Node live = null;
+ for( int i=1; i<nIns(); i++ )
+ if( region().in(i)._type != Type.XCONTROL && in(i) != this )
+ if( live == null || live == in(i) ) live = in(i);
+ else return null;
+ return live;
+ }
+
+ @Override
+ boolean all_cons() {
+ if( !(region() instanceof RegionNode r) || r.inProgress() )
+ return false;
+ return super.all_cons();
+ }
+}
diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/ProjNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/ProjNode.java
new file mode 100644
index 00000000..79ba9d13
--- /dev/null
+++ b/chapter07/src/main/java/com/seaofnodes/simple/node/ProjNode.java
@@ -0,0 +1,49 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeTuple;
+
+import java.util.BitSet;
+
+public class ProjNode extends Node {
+
+ // Which slice of the incoming multi-part value
+ public final int _idx;
+
+ // Debugging label
+ public final String _label;
+
+ public ProjNode(MultiNode ctrl, int idx, String label) {
+ super(ctrl);
+ _idx = idx;
+ _label = label;
+ }
+
+ @Override
+ public String label() { return _label; }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(_label); }
+
+ @Override public boolean isCFG() { return _idx==0 || ctrl() instanceof IfNode; }
+ @Override public boolean isMultiTail() { return in(0).isMultiHead(); }
+
+ public MultiNode ctrl() { return (MultiNode)in(0); }
+
+ @Override
+ public Type compute() {
+ Type t = ctrl()._type;
+ return t instanceof TypeTuple tt ? tt._types[_idx] : Type.BOTTOM;
+ }
+
+ @Override
+ public Node idealize() {
+ if( ctrl()._type instanceof TypeTuple tt ) {
+ if( tt._types[_idx]==Type.XCONTROL )
+ return new ConstantNode(Type.XCONTROL).peephole(); // We are dead
+ if( tt._types[1-_idx]==Type.XCONTROL ) // Only true for IfNodes
+ return ctrl().in(0); // We become our input control
+ }
+ return null;
+ }
+}
diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/RegionNode.java
new file mode 100644
index 00000000..3b793758
--- /dev/null
+++ b/chapter07/src/main/java/com/seaofnodes/simple/node/RegionNode.java
@@ -0,0 +1,81 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.type.Type;
+
+import java.util.Collection;
+import java.util.BitSet;
+
+public class RegionNode extends Node {
+ public RegionNode(Node... inputs) { super(inputs); }
+
+ @Override
+ public String label() { return "Region"; }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return sb.append(label()).append(_nid);
+ }
+
+ @Override public boolean isCFG() { return true; }
+ @Override public boolean isMultiHead() { return true; }
+
+ @Override
+ public Type compute() {
+ Type t = Type.XCONTROL;
+ for (int i = 1; i < nIns(); i++)
+ t = t.meet(in(i)._type);
+ return t;
+ }
+
+ @Override
+ public Node idealize() {
+ int path = findDeadInput();
+ if( path != 0 ) {
+ for( Node phi : _outputs )
+ if( phi instanceof PhiNode )
+ phi.delDef(path);
+ delDef(path);
+
+ // If down to a single input, become that input - but also make all
+ // Phis an identity on *their* single input.
+ if( nIns()==2 ) {
+ for( Node phi : _outputs )
+ if( phi instanceof PhiNode )
+ // Currently does not happen, because no loops
+ throw Utils.TODO();
+ return in(1);
+ }
+ return this;
+ }
+ return null;
+ }
+
+ private int findDeadInput() {
+ for( int i=1; i<nIns(); i++ )
+ if( in(i)._type==Type.XCONTROL )
+ return i;
+ return 0; // All inputs alive
+ }
+
+ // Immediate dominator of Region is a little more complicated.
+ private Node _idom; // Immediate dominator cache
+ @Override Node idom() {
+ if( _idom != null ) return _idom; // Return cached copy
+ if( nIns()!=3 ) return null; // Fails for anything other than 2-inputs
+ // Walk the LHS & RHS idom trees in parallel until they match, or either fails
+ Node lhs = in(1).idom();
+ Node rhs = in(2).idom();
+ while( lhs != rhs ) {
+ if( lhs==null || rhs==null ) return null;
+ var comp = lhs._idepth - rhs._idepth;
+ if( comp >= 0 ) lhs = lhs.idom();
+ if( comp <= 0 ) rhs = rhs.idom();
+ }
+ if( lhs==null ) return null;
+ _idepth = lhs._idepth+1;
+ return (_idom=lhs);
+ }
+
+ public boolean inProgress() { return false; }
+}
diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
new file mode 100644
index 00000000..4dea773a
--- /dev/null
+++ b/chapter07/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
@@ -0,0 +1,46 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.*;
+
+import java.util.BitSet;
+
+/**
+ * The Return node has two inputs. The first input is a control node and the
+ * second is the data node that supplies the return value.
+ * <p>
+ * In this presentation, Return functions as a Stop node, since multiple <code>return</code> statements are not possible.
+ * The Stop node will be introduced in Chapter 6 when we implement <code>if</code> statements.
+ * <p>
+ * The Return's output is the value from the data node.
+ */
+public class ReturnNode extends Node {
+
+ public ReturnNode(Node ctrl, Node data) {
+ super(ctrl, data);
+ }
+
+ public Node ctrl() { return in(0); }
+ public Node expr() { return in(1); }
+
+ @Override
+ public String label() { return "Return"; }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return expr()._print0(sb.append("return "), visited).append(";");
+ }
+
+ @Override public boolean isCFG() { return true; }
+
+ @Override
+ public Type compute() {
+ return new TypeTuple(ctrl()._type,expr()._type);
+ }
+
+ @Override
+ public Node idealize() {
+ if( ctrl()._type==Type.XCONTROL )
+ return ctrl();
+ return null;
+ }
+}
diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
new file mode 100644
index 00000000..cc6af152
--- /dev/null
+++ b/chapter07/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
@@ -0,0 +1,169 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+
+import java.util.*;
+
+/**
+ * The Scope node is purely a parser helper - it tracks names to nodes with a
+ * stack of scopes.
+ */
+public class ScopeNode extends Node {
+
+ /**
+ * The control is a name that binds to the currently active control
+ * node in the graph
+ */
+ public static final String CTRL = "$ctrl";
+ public static final String ARG0 = "arg";
+
+ /**
+ * Names for every input edge
+ */
+ public final Stack<HashMap<String, Integer>> _scopes;
+
+
+ // A new ScopeNode
+ public ScopeNode() {
+ _scopes = new Stack<>();
+ _type = Type.BOTTOM;
+ }
+
+
+ @Override public String label() { return "Scope"; }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ sb.append("Scope[ ");
+ String[] names = reverse_names();
+ for( int j=0; j<nIns(); j++ ) {
+ sb.append(names[j]).append(":");
+ Node n = in(j);
+ n._print0(sb, visited).append(" ");
+ }
+ sb.setLength(sb.length()-1);
+ return sb.append("]");
+ }
+
+ // Expensive...
+ public String[] reverse_names() {
+ String[] names = new String[nIns()];
+ for( HashMap<String,Integer> syms : _scopes )
+ for( String name : syms.keySet() )
+ names[syms.get(name)] = name;
+ return names;
+ }
+
+ @Override public Type compute() { return Type.BOTTOM; }
+
+ @Override public Node idealize() { return null; }
+
+ public void push() { _scopes.push(new HashMap<>()); }
+ public void pop() { pop_n(_scopes.pop().size()); }
+
+ // Create a new name in the current scope
+ public Node define( String name, Node n ) {
+ HashMap<String,Integer> syms = _scopes.lastElement();
+ if( syms.put(name,nIns()) != null )
+ return null; // Double define
+ return add_def(n);
+ }
+
+ // Lookup a name. It is recursive to support lazy Phis on loops (coming in chapter 8).
+ public Node lookup(String name) { return update(name,null,_scopes.size()-1); }
+ // If the name is present in any scope, then redefine else null
+ public Node update(String name, Node n) { return update(name,n,_scopes.size()-1); }
+ // Both recursive lookup and update.
+ private Node update( String name, Node n, int i ) {
+ if( i<0 ) return null; // Missed in all scopes, not found
+ var syms = _scopes.get(i);
+ var idx = syms.get(name);
+ if( idx == null ) return update(name,n,i-1); // Missed in this scope, recursively look
+ Node old = in(idx);
+ // If n is null we are looking up rather than updating
+ return n==null ? old : set_def(idx,n);
+ }
+
+ public Node ctrl() { return in(0); }
+
+ /**
+ * The ctrl of a ScopeNode is always bound to the currently active
+ * control node in the graph, via a special name '$ctrl' that is not
+ * a valid identifier in the language grammar and hence cannot be
+ * referenced in Simple code.
+ *
+ * @param n The node to be bound to '$ctrl'
+ *
+ * @return Node that was bound
+ */
+ public Node ctrl(Node n) { return set_def(0,n); }
+
+ /**
+ * 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).
+ * <p>
+ * The new Scope is a full-fledged Node with proper use<->def edges.
+ */
+ public ScopeNode dup() { return dup(false); }
+ public ScopeNode dup(boolean loop) {
+ ScopeNode dup = new ScopeNode();
+ // 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
+ for( HashMap<String,Integer> syms : _scopes )
+ dup._scopes.push(new HashMap<>(syms));
+ String[] reverse = reverse_names();
+ dup.add_def(ctrl()); // Control input is just copied
+ for( int i=1; i<nIns(); i++ ) {
+ if ( !loop ) { dup.add_def(in(i)); }
+ else {
+ // Create a phi node with second input as null - to be filled in
+ // by endLoop() below
+ dup.add_def(new PhiNode(reverse[i], ctrl(), in(i), null).peephole());
+ // Ensure our node has the same phi in case we created one
+ set_def(i, dup.in(i));
+ }
+ }
+ 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 node representing the merge point
+ */
+ public Node mergeScopes(ScopeNode that) {
+ RegionNode r = (RegionNode) ctrl(new RegionNode(null,ctrl(), that.ctrl()).keep());
+ String[] ns = reverse_names();
+ // Note that we skip i==0, which is bound to '$ctrl'
+ for (int i = 1; i < nIns(); i++)
+ if( in(i) != that.in(i) ) // No need for redundant Phis
+ set_def(i, new PhiNode(ns[i], r, in(i), that.in(i)).peephole());
+ that.kill(); // Kill merged scope
+ return r.unkeep().peephole();
+ }
+
+ // Merge the backedge scope into this loop head scope
+ // We set the second input to the phi from the back edge (i.e. loop body)
+ public void endLoop(ScopeNode back, ScopeNode exit ) {
+ Node ctrl = ctrl();
+ assert ctrl instanceof LoopNode loop && loop.inProgress();
+ ctrl.set_def(2,back.ctrl());
+ for( int i=1; i<nIns(); i++ ) {
+ PhiNode phi = (PhiNode)in(i);
+ assert phi.region()==ctrl && phi.in(2)==null;
+ phi.set_def(2,back.in(i));
+ // Do an eager useless-phi removal
+ Node in = phi.peephole();
+ if( in != phi )
+ phi.subsume(in);
+ }
+ back.kill(); // Loop backedge is dead
+ }
+}
diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/StartNode.java
new file mode 100644
index 00000000..19d03e0f
--- /dev/null
+++ b/chapter07/src/main/java/com/seaofnodes/simple/node/StartNode.java
@@ -0,0 +1,46 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeTuple;
+
+import java.util.BitSet;
+
+/**
+ * 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 MultiNode {
+
+ final TypeTuple _args;
+
+ public StartNode(Type[] args) {
+ super();
+ _args = new TypeTuple(args);
+ _type = _args;
+ }
+
+ @Override
+ public String label() { return "Start"; }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return sb.append(label());
+ }
+
+ @Override public boolean isCFG() { return true; }
+ @Override public boolean isMultiHead() { return true; }
+
+ @Override
+ public TypeTuple compute() { return _args; }
+
+ @Override
+ public Node idealize() { return null; }
+
+ // No immediate dominator, and idepth==0
+ @Override
+ Node idom() { return null; }
+
+}
diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/StopNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/StopNode.java
new file mode 100644
index 00000000..c4375771
--- /dev/null
+++ b/chapter07/src/main/java/com/seaofnodes/simple/node/StopNode.java
@@ -0,0 +1,53 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+
+import java.util.BitSet;
+
+public class StopNode extends Node {
+ public StopNode(Node... inputs) {
+ super(inputs);
+ }
+
+ @Override
+ public String label() {
+ return "Stop";
+ }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ if( ret()!=null ) return ret()._print0(sb, visited);
+ sb.append("Stop[ ");
+ for( Node ret : _inputs )
+ ret._print0(sb, visited).append(" ");
+ return sb.append("]");
+ }
+
+ @Override public boolean isCFG() { return true; }
+
+ // If a single Return, return it.
+ // Otherwise, null because ambiguous.
+ public ReturnNode ret() {
+ return nIns()==1 ? (ReturnNode)in(0) : null;
+ }
+
+ @Override
+ public Type compute() {
+ return Type.BOTTOM;
+ }
+
+ @Override
+ public Node idealize() {
+ int len = nIns();
+ for( int i=0; i<nIns(); i++ )
+ if( in(i)._type==Type.XCONTROL )
+ delDef(i--);
+ if( len != nIns() ) return this;
+ return null;
+ }
+
+ public Node addReturn(Node node) {
+ return add_def(node);
+ }
+
+}
diff --git a/chapter07/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter07/src/main/java/com/seaofnodes/simple/node/SubNode.java
new file mode 100644
index 00000000..1484ab8a
--- /dev/null
+++ b/chapter07/src/main/java/com/seaofnodes/simple/node/SubNode.java
@@ -0,0 +1,43 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+
+import java.util.BitSet;
+
+public class SubNode extends Node {
+ public SubNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
+
+ @Override public String label() { return "Sub"; }
+
+ @Override public String glabel() { return "-"; }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ in(1)._print0(sb.append("("), visited);
+ in(2)._print0(sb.append("-"), visited);
+ return sb.append(")");
+ }
+
+ @Override
+ public Type compute() {
+ if (in(1)._type instanceof TypeInteger i0 &&
+ in(2)._type instanceof TypeInteger i1) {
+ if (i0.isConstant() && i1.isConstant())
+ return TypeInteger.constant(i0.value()-i1.value());
+ return i0.meet(i1);
+ }
+ return Type.BOTTOM;
+ }
+
+ @Override
+ public Node idealize() {
+ // Sub of same is 0
+ if( in(1)==in(2) )
+ return new ConstantNode(TypeInteger.constant(0));
+
+ return null;
+ }
+
+ @Override Node copy(Node lhs, Node rhs) { return new SubNode(lhs,rhs); }
+}
diff --git a/chapter07/src/main/java/com/seaofnodes/simple/type/Type.java b/chapter07/src/main/java/com/seaofnodes/simple/type/Type.java
new file mode 100644
index 00000000..f88403a5
--- /dev/null
+++ b/chapter07/src/main/java/com/seaofnodes/simple/type/Type.java
@@ -0,0 +1,77 @@
+package com.seaofnodes.simple.type;
+
+
+/**
+ * These types are part of a Monotone Analysis Framework,
+ * @see <a href="https://www.cse.psu.edu/~gxt29/teaching/cse597s21/slides/08monotoneFramework.pdf">see for example this set of slides</a>.
+ * <p>
+ * The types form a lattice; @see <a href="https://en.wikipedia.org/wiki/Lattice_(order)">a symmetric complete bounded (ranked) lattice.</a>
+ * <p>
+ * This wild lattice theory will be needed later to allow us to easily beef up
+ * the analysis and optimization of the Simple compiler... but we don't need it
+ * now, just know that it is coming along in a later Chapter.
+ * <p>g
+ * One of the fun things here is that while the theory is deep and subtle, the
+ * actual implementation is darn near trivial and is generally really obvious
+ * what we're doing with it. Right now, it's just simple integer math to do
+ * simple constant folding e.g. 1+2 == 3 stuff.
+ */
+
+public class Type {
+
+ // ----------------------------------------------------------
+ // Simple types are implemented fully here. "Simple" means: the code and
+ // type hierarchy are simple, not that the Type is conceptually simple.
+ static final byte TBOT = 0; // Bottom (ALL)
+ static final byte TTOP = 1; // Top (ANY)
+ static final byte TCTRL = 2; // Ctrl flow bottom
+ static final byte TXCTRL = 3; // Ctrl flow top (mini-lattice: any-xctrl-ctrl-all)
+ static final byte TSIMPLE = 4; // End of the Simple Types
+ static final byte TINT = 5; // All Integers; see TypeInteger
+ static final byte TTUPLE = 6; // Tuples; finite collections of unrelated Types, kept in parallel
+
+ public final byte _type;
+
+ public boolean is_simple() { return _type < TSIMPLE; }
+ private static final String[] STRS = new String[]{"Bot","Top","Ctrl","~Ctrl"};
+ protected Type(byte type) { _type = type; }
+
+ public static final Type BOTTOM = new Type( TBOT ); // ALL
+ public static final Type TOP = new Type( TTOP ); // ANY
+ public static final Type CONTROL = new Type( TCTRL ); // Ctrl
+ public static final Type XCONTROL = new Type( TXCTRL ); // ~Ctrl
+
+ public boolean isConstant() { return _type == TTOP || _type == TXCTRL; }
+
+ public StringBuilder _print(StringBuilder sb) {return is_simple() ? sb.append(STRS[_type]) : sb;}
+
+ public final Type meet(Type t) {
+ // Shortcut for the self case
+ if( t == this ) return this;
+ // Same-type is always safe in the subclasses
+ if( _type==t._type ) return xmeet(t);
+ // Reverse; xmeet 2nd arg is never "is_simple" and never equal to "this".
+ if( is_simple() ) return this.xmeet(t );
+ if( t.is_simple() ) return t .xmeet(this);
+ return BOTTOM; // Mixing 2 unrelated types
+ }
+
+ // Compute meet right now. Overridden in subclasses.
+ // Handle cases where 'this.is_simple()' and unequal to 't'.
+ // Subclassed xmeet calls can assert that '!t.is_simple()'.
+ protected Type xmeet(Type t) {
+ assert is_simple(); // Should be overridden in subclass
+ // ANY meet anything is thing; thing meet ALL is ALL
+ if( _type==TBOT || t._type==TTOP ) return this;
+ if( _type==TTOP || t._type==TBOT ) return t;
+ // 'this' is {TCTRL,TXCTRL}
+ if( !t.is_simple() ) return BOTTOM;
+ // 't' is {TCTRL,TXCTRL}
+ return _type==TCTRL || t._type==TCTRL ? CONTROL : XCONTROL;
+ }
+
+ @Override
+ public final String toString() {
+ return _print(new StringBuilder()).toString();
+ }
+}
diff --git a/chapter07/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/chapter07/src/main/java/com/seaofnodes/simple/type/TypeInteger.java
new file mode 100644
index 00000000..93f5eae3
--- /dev/null
+++ b/chapter07/src/main/java/com/seaofnodes/simple/type/TypeInteger.java
@@ -0,0 +1,63 @@
+package com.seaofnodes.simple.type;
+
+/**
+ * Integer Type
+ */
+public class TypeInteger extends Type {
+
+ public final static TypeInteger TOP = new TypeInteger(false, 0);
+ public final static TypeInteger BOT = new TypeInteger(false, 1);
+ public final static TypeInteger ZERO= new TypeInteger(true, 0);
+
+ private final boolean _is_con;
+
+ /**
+ * The constant value or
+ * if not constant then 0=bottom, 1=top.
+ */
+ private final long _con;
+
+ public TypeInteger(boolean is_con, long con) {
+ super(TINT);
+ _is_con = is_con;
+ _con = con;
+ }
+
+ public static TypeInteger constant(long con) { return new TypeInteger(true, con); }
+
+ public boolean isTop() { return !_is_con && _con==0; }
+ public boolean isBot() { return !_is_con && _con==1; }
+
+ @Override
+ public StringBuilder _print(StringBuilder sb) {
+ if (isTop()) return sb.append("IntTop");
+ if (isBot()) return sb.append("IntBot");
+ return sb.append(_con);
+ }
+
+ @Override
+ public boolean isConstant() { return _is_con; }
+
+ public long value() { return _con; }
+
+ @Override
+ public Type xmeet(Type other) {
+ if( this==other ) return this;
+ if (!(other instanceof TypeInteger i)) return super.meet(other);
+ // BOT wins
+ if ( isBot() ) return this;
+ if ( i.isBot() ) return i ;
+ // TOP loses
+ if ( i.isTop() ) return this;
+ if ( isTop() ) return i ;
+ assert isConstant() && i.isConstant();
+ return _con==i._con ? this : TypeInteger.BOT;
+ }
+
+ @Override
+ public boolean equals( Object o ) {
+ if( o==this ) return true;
+ if( !(o instanceof TypeInteger i) ) return false;
+ return _con==i._con && _is_con==i._is_con;
+ }
+}
diff --git a/chapter07/src/main/java/com/seaofnodes/simple/type/TypeTuple.java b/chapter07/src/main/java/com/seaofnodes/simple/type/TypeTuple.java
new file mode 100644
index 00000000..f8c3b3c8
--- /dev/null
+++ b/chapter07/src/main/java/com/seaofnodes/simple/type/TypeTuple.java
@@ -0,0 +1,32 @@
+package com.seaofnodes.simple.type;
+
+public class TypeTuple extends Type {
+
+ public final Type[] _types;
+
+ public TypeTuple(Type... _types) {
+ super(TTUPLE);
+ this._types = _types;
+ }
+
+ @Override
+ public Type xmeet(Type other) {
+ throw new UnsupportedOperationException("Meet on Tuple Type not yet implemented");
+ }
+
+ @Override
+ public StringBuilder _print(StringBuilder sb) {
+ sb.append("[");
+ for( Type t : _types )
+ t._print(sb).append(",");
+ sb.setLength(sb.length()-1);
+ sb.append("]");
+ return sb;
+ }
+
+ public static final TypeTuple IF_BOTH = new TypeTuple(new Type[]{Type.CONTROL, Type.CONTROL});
+ public static final TypeTuple IF_NEITHER = new TypeTuple(new Type[]{Type.XCONTROL,Type.XCONTROL});
+ public static final TypeTuple IF_TRUE = new TypeTuple(new Type[]{Type.CONTROL, Type.XCONTROL});
+ public static final TypeTuple IF_FALSE = new TypeTuple(new Type[]{Type.XCONTROL,Type.CONTROL});
+
+}
diff --git a/chapter07/src/test/java/com/seaofnodes/simple/Chapter07Test.java b/chapter07/src/test/java/com/seaofnodes/simple/Chapter07Test.java
new file mode 100644
index 00000000..efcf90f5
--- /dev/null
+++ b/chapter07/src/test/java/com/seaofnodes/simple/Chapter07Test.java
@@ -0,0 +1,1026 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class Chapter07Test {
+
+ @Test
+ public void testChapter7Example() {
+ Parser parser = new Parser(
+ """
+ while(arg < 10) {
+ arg = arg + 1;
+ #showGraph;
+ }
+ return arg;
+ """);
+ Node._disablePeephole = true;
+ StopNode stop = parser.parse(true);
+ assertEquals("return Phi(Loop6,arg,(Phi_arg+1));", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof ProjNode);
+ Node._disablePeephole = false;
+ }
+
+ @Test
+ public void testChapter7Regression() {
+ Parser parser = new Parser(
+
+"""
+int a = 1;
+if(arg){}else{
+ while(a < 10) {
+ a = a + 1;
+ }
+}
+return a;
+""");
+ StopNode stop = parser.parse(true);
+ assertEquals("return Phi(Region23,1,Phi(Loop11,1,(Phi_a+1)));", stop.toString());
+ }
+
+ @Test
+ public void testChapter7WhileNested() {
+ Parser parser = new Parser(
+
+"""
+int sum = 0;
+int i = 0;
+while(i < arg) {
+ i = i + 1;
+ int j = 0;
+ while( j < arg ) {
+ sum = sum + j;
+ j = j + 1;
+ }
+}
+return sum;
+""");
+ StopNode stop = parser.parse(true);
+ assertEquals("return Phi(Loop8,0,Phi(Loop21,Phi_sum,(Phi(Loop,0,(Phi_j+1))+Phi_sum)));", stop.toString());
+ System.out.println(IRPrinter.prettyPrint(stop,99));
+ }
+
+ @Test
+ public void testChapter7WhileScope() {
+ Parser parser = new Parser(
+"""
+int a = 1;
+int b = 2;
+while(a < 10) {
+ if (a == 2) a = 3;
+ else b = 4;
+}
+return b;
+""");
+ Node._disablePeephole = true;
+ StopNode stop = parser.parse(true);
+ assertEquals("return Phi(Loop8,2,Phi(Region27,Phi_b,4));", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof ProjNode);
+ Node._disablePeephole = false;
+ System.out.println(IRPrinter.prettyPrint(stop,99));
+ }
+
+ @Test
+ public void testChapter7WhileNestedIfAndInc() {
+ Parser parser = new Parser(
+"""
+int a = 1;
+int b = 2;
+while(a < 10) {
+ if (a == 2) a = 3;
+ else b = 4;
+ b = b + 1;
+ a = a + 1;
+}
+return b;
+""");
+ StopNode stop = parser.parse(true);
+ assertEquals("return Phi(Loop8,2,(Phi(Region27,Phi_b,4)+1));", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof ProjNode);
+ System.out.println(IRPrinter.prettyPrint(stop,99));
+ }
+
+
+ @Test
+ public void testChapter7While() {
+ Parser parser = new Parser(
+"""
+int a = 1;
+while(a < 10) {
+ a = a + 1;
+ a = a + 2;
+}
+return a;
+""");
+ Node._disablePeephole = true;
+ StopNode stop = parser.parse(true);
+ assertEquals("return Phi(Loop7,1,((Phi_a+1)+2));", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof ProjNode);
+ Node._disablePeephole = false;
+ }
+
+ @Test
+ public void testChapter7WhilePeep() {
+ Parser parser = new Parser(
+"""
+int a = 1;
+while(a < 10) {
+ a = a + 1;
+ a = a + 2;
+}
+return a;
+""");
+ StopNode stop = parser.parse(true);
+ assertEquals("return Phi(Loop7,1,(Phi_a+3));", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof ProjNode);
+ }
+
+ @Test
+ public void testChapter7While2() {
+ Parser parser = new Parser(
+"""
+int a = 1;
+while(arg) a = 2;
+return a;
+""");
+ Node._disablePeephole = true;
+ StopNode stop = parser.parse(true);
+ assertEquals("return Phi(Loop7,1,2);", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof ProjNode);
+ Node._disablePeephole = false;
+ System.out.println(IRPrinter.prettyPrint(stop,99));
+ }
+
+ @Test
+ public void testChapter7While2Peep() {
+ Parser parser = new Parser(
+"""
+int a = 1;
+while(arg) a = 2;
+return a;
+""");
+ StopNode stop = parser.parse(false);
+ assertEquals("return Phi(Loop7,1,2);", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof ProjNode);
+ System.out.println(IRPrinter.prettyPrint(stop, 99));
+ }
+
+ @Test
+ public void testChapter7While3() {
+ Parser parser = new Parser(
+"""
+int a = 1;
+while(a < 10) {
+ int b = a + 1;
+ a = b + 2;
+}
+return a;
+""");
+ Node._disablePeephole = true;
+ StopNode stop = parser.parse(true);
+ assertEquals("return Phi(Loop7,1,((Phi_a+1)+2));", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof ProjNode);
+ Node._disablePeephole = false;
+ }
+
+ @Test
+ public void testChapter7While3Peep() {
+ Parser parser = new Parser(
+"""
+int a = 1;
+while(a < 10) {
+ int b = a + 1;
+ a = b + 2;
+}
+return a;
+""");
+ StopNode stop = parser.parse(true);
+ assertEquals("return Phi(Loop7,1,(Phi_a+3));", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof ProjNode);
+ }
+
+ @Test
+ public void testChapter7While4() {
+ Parser parser = new Parser(
+"""
+int a = 1;
+int b = 2;
+while(a < 10) {
+ int b = a + 1;
+ a = b + 2;
+}
+return a;
+""");
+ Node._disablePeephole = true;
+ StopNode stop = parser.parse(true);
+ assertEquals("return Phi(Loop8,1,((Phi_a+1)+2));", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof ProjNode);
+ Node._disablePeephole = false;
+ }
+
+ @Test
+ public void testChapter7While4Peep() {
+ Parser parser = new Parser(
+"""
+int a = 1;
+int b = 2;
+while(a < 10) {
+ int b = a + 1;
+ a = b + 2;
+}
+return a;
+""");
+ StopNode stop = parser.parse(true);
+ assertEquals("return Phi(Loop8,1,(Phi_a+3));", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof ProjNode);
+ }
+
+ @Test
+ public void testChapter6PeepholeReturn() {
+ Parser parser = new Parser(
+"""
+if( true ) return 2;
+return 1;
+""");
+ StopNode stop = parser.parse(true);
+ assertEquals("return 2;", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof ProjNode);
+ }
+
+ @Test
+ public void testChapter6PeepholeRotate() {
+ Parser parser = new Parser(
+"""
+int a = 1;
+if (arg)
+ a = 2;
+return (arg < a) < 3;
+""");
+ StopNode stop = parser.parse(false);
+ assertEquals("return ((arg<Phi(Region12,2,1))<3);", stop.toString());
+ }
+
+ @Test
+ public void testChapter6PeepholeCFG() {
+ Parser parser = new Parser(
+"""
+int a=1;
+if( true )
+ a=2;
+else
+ a=3;
+return a;
+""");
+ StopNode stop = parser.parse(true);
+ assertEquals("return 2;", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof ProjNode);
+ }
+
+ @Test
+ public void testChapter6IfIf() {
+ Parser parser = new Parser(
+"""
+int a=1;
+if( arg!=1 )
+ a=2;
+else
+ a=3;
+int b=4;
+if( a==2 )
+ b=42;
+else
+ b=5;
+return b;""", TypeInteger.BOT);
+ StopNode stop = parser.parse(true);
+ assertEquals("return Phi(Region32,42,5);", stop.toString());
+ }
+
+ @Test
+ public void testChapter6IfArgIf() {
+ Parser parser = new Parser(
+"""
+int a=1;
+if( 1==1 )
+ a=2;
+else
+ a=3;
+int b=4;
+if( arg==2 )
+ b=a;
+else
+ b=5;
+return b;""", TypeInteger.BOT);
+ StopNode stop = parser.parse(true);
+ assertEquals("return Phi(Region28,2,5);", stop.toString());
+ }
+
+ @Test
+ public void testChapter6Merge3With2() {
+ Parser parser = new Parser(
+"""
+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;""", TypeInteger.constant(2));
+ StopNode stop = parser.parse();
+ assertEquals("return 5;", stop.toString());
+ }
+
+ @Test
+ public void testChapter6Merge3With1() {
+ Parser parser = new Parser(
+"""
+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;
+""", TypeInteger.constant(1));
+ StopNode stop = parser.parse(true);
+ assertEquals("return 3;", stop.toString());
+ }
+
+ @Test
+ public void testChapter6Merge3Peephole() {
+ Parser parser = new Parser(
+"""
+int a=1;
+if( arg==1 )
+ if( 1==2 )
+ a=2;
+ else
+ a=3;
+else if( arg==3 )
+ a=4;
+else
+ a=5;
+return a;
+""", TypeInteger.BOT);
+ StopNode stop = parser.parse(true);
+ assertEquals("return Phi(Region36,3,Phi(Region34,4,5));", stop.toString());
+ }
+
+ @Test
+ public void testChapter6Merge3Peephole1() {
+ Parser parser = new Parser(
+"""
+int a=1;
+if( arg==1 )
+ if( 1==2 )
+ a=2;
+ else
+ a=3;
+else if( arg==3 )
+ a=4;
+else
+ a=5;
+return a;
+""", TypeInteger.constant(1));
+ StopNode stop = parser.parse(true);
+ assertEquals("return 3;", stop.toString());
+ }
+
+ @Test
+ public void testChapter6Merge3Peephole3() {
+ Parser parser = new Parser(
+"""
+int a=1;
+if( arg==1 )
+ if( 1==2 )
+ a=2;
+ else
+ a=3;
+else if( arg==3 )
+ a=4;
+else
+ a=5;
+return a;
+""", TypeInteger.constant(3));
+ StopNode stop = parser.parse(true);
+ assertEquals("return 4;", stop.toString());
+ }
+
+ @Test
+ public void testChapter6Demo1NonConst() {
+ Parser parser = new Parser("""
+int a = 0;
+int b = 1;
+if( arg ) {
+ a = 2;
+ if( arg ) { b = 2; }
+ else b = 3;
+}
+return a+b;
+""");
+ StopNode ret = parser.parse(true);
+ assertEquals("return Phi(Region22,4,1);", ret.toString());
+ }
+
+
+ @Test
+ public void testChapter6Demo1True() {
+ Parser parser = new Parser("""
+int a = 0;
+int b = 1;
+if( arg ) {
+ a = 2;
+ if( arg ) { b = 2; }
+ else b = 3;
+}
+return a+b;
+""", TypeInteger.constant(1));
+ StopNode ret = parser.parse(true);
+ assertEquals("return 4;", ret.toString());
+ }
+
+ @Test
+ public void testChapter6Demo1False() {
+ Parser parser = new Parser("""
+int a = 0;
+int b = 1;
+if( arg ) {
+ a = 2;
+ if( arg ) { b = 2; }
+ else b = 3;
+}
+return a+b;
+""", TypeInteger.constant(0));
+ StopNode ret = parser.parse(true);
+ assertEquals("return 1;", ret.toString());
+ }
+
+ @Test
+ public void testChapter6Demo2NonConst() {
+ Parser parser = new Parser("""
+int a = 0;
+int b = 1;
+int c = 0;
+if( arg ) {
+ a = 1;
+ if( arg==2 ) { c=2; } else { c=3; }
+ if( arg ) { b = 2; }
+ else b = 3;
+}
+return a+b+c;
+""");
+ StopNode ret = parser.parse(true);
+ assertEquals("return (Phi(Region33,Phi(Region22,2,3),0)+Phi(Region,3,1));", ret.toString());
+ }
+
+
+ @Test
+ public void testChapter6Demo2True() {
+ Parser parser = new Parser("""
+int a = 0;
+int b = 1;
+int c = 0;
+if( arg ) {
+ a = 1;
+ if( arg==2 ) { c=2; } else { c=3; }
+ if( arg ) { b = 2; }
+ else b = 3;
+}
+return a+b+c;
+""", TypeInteger.constant(1));
+ StopNode ret = parser.parse(true);
+ assertEquals("return 6;", ret.toString());
+ }
+
+ @Test
+ public void testChapter6Demo2arg2() {
+ Parser parser = new Parser("""
+int a = 0;
+int b = 1;
+int c = 0;
+if( arg ) {
+ a = 1;
+ if( arg==2 ) { c=2; } else { c=3; }
+ if( arg ) { b = 2; }
+ else b = 3;
+}
+return a+b+c;
+""", TypeInteger.constant(2));
+ StopNode ret = parser.parse(true);
+ assertEquals("return 5;", ret.toString());
+ }
+
+
+
+ @Test
+ public void testChapter5IfStmt() {
+ Parser parser = new Parser(
+"""
+int a = 1;
+if (arg == 1)
+ a = arg+2;
+else {
+ a = arg-3;
+ #showGraph;
+}
+#showGraph;
+return a;""");
+ StopNode ret = parser.parse(true);
+ assertEquals("return Phi(Region17,(arg+2),(arg-3));", ret.toString());
+ }
+
+ @Test
+ public void testChapter5Test() {
+ Parser parser = new Parser(
+"""
+int c = 3;
+int b = 2;
+if (arg == 1) {
+ b = 3;
+ c = 4;
+}
+return c;""", TypeInteger.BOT);
+ StopNode ret = parser.parse(true);
+ assertEquals("return Phi(Region16,4,3);", ret.toString());
+ }
+
+ @Test
+ public void testChapter5Return2() {
+ Parser parser = new Parser(
+"""
+if( arg==1 )
+ return 3;
+else
+ return 4;
+#showGraph;""", TypeInteger.BOT);
+ StopNode stop = parser.parse();
+ assertEquals("Stop[ return 3; return 4; ]", stop.toString());
+ }
+
+ @Test
+ public void testChapter5IfMergeB() {
+ Parser parser = new Parser(
+"""
+int a=arg+1;
+int b=0;
+if( arg==1 )
+ b=a;
+else
+ b=a+1;
+return a+b;""");
+ StopNode ret = parser.parse(true);
+ assertEquals("return ((arg*2)+Phi(Region20,2,3));", ret.toString());
+ }
+
+ @Test
+ public void testChapter5IfMerge2() {
+ Parser parser = new Parser(
+"""
+int a=arg+1;
+int b=arg+2;
+if( arg==1 )
+ b=b+a;
+else
+ a=b+1;
+return a+b;""");
+ StopNode ret = parser.parse(true);
+ assertEquals("return ((Phi(Region31,(arg*2),arg)+arg)+Phi(Region,4,5));", ret.toString());
+ }
+
+ @Test
+ public void testChapter5Merge3() {
+ Parser parser = new Parser(
+"""
+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;""", TypeInteger.BOT);
+ StopNode stop = parser.parse();
+ assertEquals("return Phi(Region33,Phi(Region21,2,3),Phi(Region31,4,5));", stop.toString());
+ }
+
+ @Test
+ public void testChapter5Merge4() {
+ Parser parser = new Parser(
+"""
+int a=0;
+int b=0;
+if( arg )
+ a=1;
+if( arg==0 )
+ b=2;
+return arg+a+b;
+#showGraph;""", TypeInteger.BOT);
+ StopNode stop = parser.parse();
+ assertEquals("return ((arg+Phi(Region13,1,0))+Phi(Region22,2,0));", stop.toString());
+ }
+
+ @Test
+ public void testChapter5Merge5() {
+ Parser parser = new Parser(
+"""
+int a=arg==2;
+if( arg==1 )
+{
+ a=arg==3;
+}
+return a;""");
+ StopNode ret = parser.parse(true);
+ assertEquals("return (arg==Phi(Region16,3,2));", ret.toString());
+ }
+
+ @Test
+ public void testChapter5True() {
+ StopNode stop = new Parser("return true;").parse();
+ assertEquals("return 1;",stop.toString());
+ }
+
+ @Test
+ public void testChapter5HalfDef() {
+ try {
+ new Parser("if( arg==1 ) int b=2; return b;").parse();
+ fail();
+ } catch( RuntimeException e ) {
+ assertEquals("Cannot define a new name on one arm of an if",e.getMessage());
+ }
+ }
+
+ @Test
+ public void testChapter5HalfDef2() {
+ try {
+ new Parser("if( arg==1 ) { int b=2; } else { int b=3; } return b;").parse();
+ fail();
+ } catch( RuntimeException e ) {
+ assertEquals("Undefined name 'b'",e.getMessage());
+ }
+ }
+
+ @Test
+ public void testChapter5BadNum() {
+ try {
+ new Parser("return 1-;").parse();
+ fail();
+ } catch( RuntimeException e ) {
+ assertEquals("Syntax error, expected an identifier or expression: ;",e.getMessage());
+ }
+ }
+
+ @Test
+ public void testChapter5Keyword1() {
+ try {
+ new Parser("int true=0; return true;").parse();
+ fail();
+ } catch( RuntimeException e ) {
+ assertEquals("Expected an identifier, found 'true'",e.getMessage());
+ }
+ }
+
+ @Test
+ public void testChapter5Keyword2() {
+ try {
+ new Parser("int else=arg; if(else) else=2; else else=1; return else;").parse();
+ fail();
+ } catch( RuntimeException e ) {
+ assertEquals("Expected an identifier, found 'else'",e.getMessage());
+ }
+ }
+
+ @Test
+ public void testChapter5Keyword3() {
+ try {
+ new Parser("int a=1; ififif(arg)inta=2;return a;").parse();
+ fail();
+ } catch( RuntimeException e ) {
+ assertEquals("Syntax error, expected =: (",e.getMessage());
+ }
+ }
+
+ @Test
+ public void testChapter4Peephole() {
+ Parser parser = new Parser("return 1+arg+2; #showGraph;");
+ StopNode ret = parser.parse();
+ assertEquals("return (arg+3);", ret.print());
+ }
+
+ @Test
+ public void testChapter4Peephole2() {
+ Parser parser = new Parser("return (1+arg)+2;");
+ StopNode ret = parser.parse();
+ assertEquals("return (arg+3);", ret.print());
+ }
+
+ @Test
+ public void testChapter4Add0() {
+ Parser parser = new Parser("return 0+arg;");
+ StopNode ret = parser.parse();
+ assertEquals("return arg;", ret.print());
+ }
+
+ @Test
+ public void testChapter4AddAddMul() {
+ Parser parser = new Parser("return arg+0+arg;");
+ StopNode ret = parser.parse();
+ assertEquals("return (arg*2);", ret.print());
+ }
+
+ @Test
+ public void testChapter4Peephole3() {
+ Parser parser = new Parser("return 1+arg+2+arg+3; #showGraph;");
+ StopNode ret = parser.parse();
+ assertEquals("return ((arg*2)+6);", ret.print());
+ }
+
+ @Test
+ public void testChapter4Mul1() {
+ Parser parser = new Parser("return 1*arg;");
+ StopNode ret = parser.parse();
+ assertEquals("return arg;", ret.print());
+ }
+
+ @Test
+ public void testChapter4VarArg() {
+ Parser parser = new Parser("return arg; #showGraph;");
+ StopNode stop = parser.parse();
+ ReturnNode ret = stop.ret();
+ assertTrue(ret.in(0) instanceof ProjNode);
+ assertTrue(ret.in(1) instanceof ProjNode);
+ }
+
+ @Test
+ public void testChapter4ConstantArg() {
+ Parser parser = new Parser("return arg; #showGraph;", TypeInteger.constant(2));
+ StopNode ret = parser.parse();
+ assertEquals("return 2;", ret.print());
+ }
+
+ @Test
+ public void testChapter4CompEq() {
+ Parser parser = new Parser("return 3==3; #showGraph;");
+ StopNode ret = parser.parse();
+ assertEquals("return 1;", ret.print());
+ }
+
+ @Test
+ public void testChapter4CompEq2() {
+ Parser parser = new Parser("return 3==4; #showGraph;");
+ StopNode ret = parser.parse();
+ assertEquals("return 0;", ret.print());
+ }
+
+ @Test
+ public void testChapter4CompNEq() {
+ Parser parser = new Parser("return 3!=3; #showGraph;");
+ StopNode ret = parser.parse();
+ assertEquals("return 0;", ret.print());
+ }
+
+ @Test
+ public void testChapter4CompNEq2() {
+ Parser parser = new Parser("return 3!=4; #showGraph;");
+ StopNode 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;");
+ StopNode ret = parser.parse();
+ assertEquals("return (arg+3);", ret.print());
+ }
+
+ @Test
+ public void testChapter4Bug2() {
+ Parser parser = new Parser("int a=arg+1; a=a; return a; #showGraph;");
+ StopNode ret = parser.parse();
+ assertEquals("return (arg+1);", ret.print());
+ }
+
+ @Test
+ public void testChapter4Bug3() {
+ 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;");
+ StopNode ret = parser.parse();
+ assertEquals("return (-arg);", ret.print());
+ }
+
+ @Test
+ public void testVarDecl() {
+ Parser parser = new Parser("int a=1; return a;");
+ StopNode ret = parser.parse();
+ assertEquals("return 1;", ret.print());
+ }
+
+ @Test
+ public void testVarAdd() {
+ Parser parser = new Parser("int a=1; int b=2; return a+b;");
+ StopNode ret = parser.parse();
+ assertEquals("return 3;", ret.print());
+ }
+
+ @Test
+ public void testVarScope() {
+ Parser parser = new Parser("int a=1; int b=2; int c=0; { int b=3; c=a+b; } return c;");
+ StopNode ret = parser.parse();
+ assertEquals("return 4;", ret.print());
+ }
+
+ @Test
+ public void testVarScopeNoPeephole() {
+ Parser parser = new Parser("int a=1; int b=2; int c=0; { int b=3; c=a+b; #showGraph; } return c; #showGraph;");
+ Node._disablePeephole = true;
+ StopNode ret = parser.parse();
+ Node._disablePeephole = false;
+ assertEquals("return (1+3);", ret.print());
+ }
+
+ @Test
+ public void testVarDist() {
+ Parser parser = new Parser("int x0=1; int y0=2; int x1=3; int y1=4; return (x0-x1)*(x0-x1) + (y0-y1)*(y0-y1); #showGraph;");
+ StopNode ret = parser.parse();
+ 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() {
+ Parser parser = new Parser("return 1+2*3+-5;");
+ Node._disablePeephole = true; // disable peephole so we can observe full graph
+ StopNode ret = parser.parse();
+ assertEquals("return (1+((2*3)+(-5)));", ret.print());
+ GraphVisualizer gv = new GraphVisualizer();
+ System.out.println(gv.generateDotOutput(parser));
+ Node._disablePeephole = false;
+ }
+
+ @Test
+ public void testChapter2AddPeephole() {
+ Parser parser = new Parser("return 1+2;");
+ StopNode ret = parser.parse();
+ assertEquals("return 3;", ret.print());
+ }
+
+ @Test
+ public void testChapter2SubPeephole() {
+ Parser parser = new Parser("return 1-2;");
+ StopNode ret = parser.parse();
+ assertEquals("return -1;", ret.print());
+ }
+
+ @Test
+ public void testChapter2MulPeephole() {
+ Parser parser = new Parser("return 2*3;");
+ StopNode ret = parser.parse();
+ assertEquals("return 6;", ret.print());
+ }
+
+ @Test
+ public void testChapter2DivPeephole() {
+ Parser parser = new Parser("return 6/3;");
+ StopNode ret = parser.parse();
+ assertEquals("return 2;", ret.print());
+ }
+
+ @Test
+ public void testChapter2MinusPeephole() {
+ Parser parser = new Parser("return 6/-3;");
+ StopNode ret = parser.parse();
+ assertEquals("return -2;", ret.print());
+ }
+
+ @Test
+ public void testChapter2Example() {
+ Parser parser = new Parser("return 1+2*3+-5;");
+ StopNode ret = parser.parse();
+ assertEquals("return 2;", ret.print());
+ GraphVisualizer gv = new GraphVisualizer();
+ System.out.println(gv.generateDotOutput(parser));
+ }
+
+ @Test
+ public void testSimpleProgram() {
+ Parser parser = new Parser("return 1;");
+ StopNode stop = parser.parse();
+ StartNode start = Parser.START;
+ ReturnNode ret = (ReturnNode)stop.in(0);
+
+ assertTrue(ret.ctrl() instanceof ProjNode);
+ Node expr = ret.expr();
+ if( expr instanceof ConstantNode con ) {
+ assertEquals(start,con.in(0));
+ assertEquals(TypeInteger.constant(1), con._type);
+ } else {
+ fail();
+ }
+ }
+
+ @Test
+ public void testZero() {
+ Parser parser = new Parser("return 0;");
+ parser.parse();
+ StartNode start = Parser.START;
+ for( Node use : start._outputs )
+ if( use instanceof ConstantNode con )
+ assertEquals(TypeInteger.constant(0),con._type);
+ }
+
+ @Test
+ public void testBad1() {
+ try {
+ new Parser("ret").parse();
+ fail();
+ } catch( RuntimeException e ) {
+ assertEquals("Syntax error, expected =: ",e.getMessage());
+ }
+ }
+
+ @Test
+ public void testBad2() {
+ try {
+ new Parser("return 0123;").parse();
+ fail();
+ } catch( RuntimeException e ) {
+ assertEquals("Syntax error: integer values cannot start with '0'",e.getMessage());
+ }
+ }
+
+ @Test
+ public void testNotBad3() {
+ // this test used to fail in chapter 1
+ assertEquals("return 12;", new Parser("return --12;").parse().print());
+ }
+
+ @Test
+ public void testBad4() {
+ try {
+ new Parser("return 100").parse();
+ fail();
+ } catch( RuntimeException e ) {
+ assertEquals("Syntax error, expected ;: ",e.getMessage());
+ }
+ }
+
+ @Test
+ public void testNotBad5() {
+ // this test used to fail in chapter 1
+ assertEquals("return -100;", new Parser("return -100;").parse().print());
+ }
+
+ @Test
+ public void testBad6() {
+ 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/chapter08/Makefile b/chapter08/Makefile
new file mode 100644
index 00000000..56f69e12
--- /dev/null
+++ b/chapter08/Makefile
@@ -0,0 +1,92 @@
+SHELL := /bin/bash
+.DELETE_ON_ERROR:
+
+# for printing variable values
+# usage: make print-VARIABLE
+# > VARIABLE = value_of_variable
+print-% : ; @echo $* = $($*)
+
+# literal space
+space := $() $()
+
+# Decide OS-specific questions
+# jar-file seperator
+ifeq ($(OS),Windows_NT)
+ SEP = ;
+else
+ SEP = :
+endif
+# Find a reasonable ctags.
+CTAGS = $(shell which ctags)
+# Hack for MacOS: /usr/bin/ctags is unfriendly, so look for ctags from brew
+ifeq ($(UNAME),Darwin)
+ CTAGS = $(shell brew list ctags 2> /dev/null | grep bin/ctags)
+endif
+
+# Fun Args to javac. Mostly limit to java8 source definitions, and fairly
+# aggressive lint warnings.
+JAVAC_ARGS = -g
+
+# Source code
+SIMPLE := com/seaofnodes/simple
+SRC := src/main/java
+TST := src/test/java
+CLZDIR:= build/classes
+main_javas := $(wildcard $(SRC)/$(SIMPLE)/*java $(SRC)/$(SIMPLE)/*/*java)
+test_javas := $(wildcard $(TST)/$(SIMPLE)/*java $(TST)/$(SIMPLE)/*/*java)
+main_classes := $(patsubst $(SRC)/%java,$(CLZDIR)/main/%class,$(main_javas))
+test_classes := $(patsubst $(TST)/%java,$(CLZDIR)/test/%class,$(test_javas))
+test_cp := $(patsubst $(TST)/$(SIMPLE)/%.java,com.seaofnodes.simple.%,$(test_javas))
+classes = $(main_classes) $(test_classes)
+# All the libraries
+libs = $(wildcard lib/*jar)
+jars = $(subst $(space),$(SEP),$(libs))
+
+
+default_targets := $(main_classes) $(test_classes)
+# Optionally add ctags to the default target if a reasonable one was found.
+ifneq ($(CTAGS),)
+default_targets += tags
+endif
+
+default: $(default_targets)
+
+# Compile just the out-of-date files
+$(main_classes): build/classes/main/%class: $(SRC)/%java
+ @echo "compiling " $@ " because " $?
+ @[ -d $(CLZDIR)/main ] || mkdir -p $(CLZDIR)/main
+ @javac $(JAVAC_ARGS) -cp "$(CLZDIR)/main$(SEP)$(jars)" -sourcepath $(SRC) -d $(CLZDIR)/main $(main_javas)
+
+$(test_classes): $(CLZDIR)/test/%class: $(TST)/%java $(main_classes)
+ @echo "compiling " $@ " because " $?
+ @[ -d $(CLZDIR)/test ] || mkdir -p $(CLZDIR)/test
+ @javac $(JAVAC_ARGS) -cp "$(CLZDIR)/test$(SEP)$(CLZDIR)/main$(SEP)$(jars)" -sourcepath $(TST) -d $(CLZDIR)/test $(test_javas)
+
+# Base launch line for JVM tests
+JVM=nice java -ea -cp "build/classes/main${SEP}${jars}${SEP}$(CLZDIR)/test"
+
+tests: $(default_targets)
+ $(JVM) org.junit.runner.JUnitCore $(test_cp)
+
+.PHONY: clean
+clean:
+ rm -rf build
+ rm -f TAGS
+ (find . -name "*~" -exec rm {} \; 2>/dev/null; exit 0)
+
+# Download libs from maven
+lib: lib/junit-4.12.jar lib/hamcrest-core-1.3.jar
+
+# Jars
+lib/junit-4.12.jar:
+ @[ -d lib ] || mkdir -p lib
+ @(cd lib; wget https://repo1.maven.org/maven2/junit/junit/4.12/junit-4.12.jar)
+
+lib/hamcrest-core-1.3.jar:
+ @[ -d lib ] || mkdir -p lib
+ @(cd lib; wget https://repo1.maven.org/maven2/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar)
+
+# Build emacs tags (part of a tasty emacs ide experience)
+tags: $(main_javas) $(test_javas)
+ @rm -f TAGS
+ @$(CTAGS) -e --recurse=yes --extra=+q --fields=+fksaiS $(SRC) $(TST)
diff --git a/chapter08/README.md b/chapter08/README.md
new file mode 100644
index 00000000..656ea0ef
--- /dev/null
+++ b/chapter08/README.md
@@ -0,0 +1,2 @@
+# Chapter 8
+
diff --git a/chapter08/docs/08-grammar.md b/chapter08/docs/08-grammar.md
new file mode 100644
index 00000000..cee33030
--- /dev/null
+++ b/chapter08/docs/08-grammar.md
@@ -0,0 +1,88 @@
+# Grammar for Chapter 8
+
+```antlrv4
+grammar SimpleLanguage;
+
+program
+ : statement+ EOF
+ ;
+
+statement
+ : returnStatement
+ | declStatement
+ | blockStatment
+ | expressionStatement
+ | ifStatement
+ | whileStatement
+ | metaStatement
+ ;
+
+whileStatement
+ : 'while' '(' expression ')' statement
+ ;
+
+ifStatement
+ : 'if' '(' expression ')' statement ('else' statement)?
+ ;
+
+metaStatement
+ : '#showGraph' ';'
+ ;
+
+expressionStatement
+ : IDENTIFIER '=' expression ';'
+ ;
+
+blockStatement
+ : '{' statement+ '}'
+ ;
+
+declStatement
+ : 'int' IDENTIFIER '=' expression ';'
+ ;
+
+returnStatement
+ : 'return' expression ';'
+ ;
+
+expression
+ : comparisonExpression
+ ;
+
+comparisonExpression
+ : additiveExpression (('==' | '!='| '>'| '<'| '>='| '<=') additiveExpression)*
+ ;
+
+additiveExpression
+ : multiplicativeExpression (('+' | '-') multiplicativeExpression)*
+ ;
+
+multiplicativeExpression
+ : unaryExpression (('*' | '/') unaryExpression)*
+ ;
+
+unaryExpression
+ : ('-') unaryExpression
+ | primaryExpression
+ ;
+
+primaryExpression
+ : IDENTIFIER
+ | INTEGER_LITERAL
+ | 'true'
+ | 'false'
+ | '(' expression ')'
+ ;
+
+INTEGER_LITERAL
+ : [1-9][0-9]*
+ | [0]
+ ;
+
+IDENTIFIER
+ : NON_DIGIT (NON_DIGIT | DIGIT)*
+ ;
+
+NON_DIGIT: [a-zA-Z_];
+DEC_DIGIT: [0-9];
+``` \ No newline at end of file
diff --git a/chapter08/docs/build-graphs.sh b/chapter08/docs/build-graphs.sh
new file mode 100644
index 00000000..73c39624
--- /dev/null
+++ b/chapter08/docs/build-graphs.sh
@@ -0,0 +1,3 @@
+dot -Tsvg 07-graph1.gv > 07-graph1.svg
+
+
diff --git a/chapter08/pom.xml b/chapter08/pom.xml
new file mode 100644
index 00000000..5942485f
--- /dev/null
+++ b/chapter08/pom.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>com.seaofnodes</groupId>
+ <artifactId>simple</artifactId>
+ <version>1.0</version>
+ </parent>
+ <artifactId>chapter08</artifactId>
+ <packaging>jar</packaging>
+ <name>Chapter 8</name>
+</project> \ No newline at end of file
diff --git a/chapter08/src/main/java/com/seaofnodes/simple/GraphVisualizer.java b/chapter08/src/main/java/com/seaofnodes/simple/GraphVisualizer.java
new file mode 100644
index 00000000..c823641d
--- /dev/null
+++ b/chapter08/src/main/java/com/seaofnodes/simple/GraphVisualizer.java
@@ -0,0 +1,258 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.node.*;
+
+import java.util.*;
+
+/**
+ * Simple visualizer that outputs GraphViz dot format.
+ * The dot output must be saved to a file and run manually via dot to generate the SVG output.
+ * Currently, this is done manually.
+ */
+public class GraphVisualizer {
+
+ /**
+ * If set to true we put the control nodes in a separate cluster from
+ * data nodes.
+ */
+ boolean _separateControlCluster = false;
+
+ public GraphVisualizer(boolean separateControlCluster) { this._separateControlCluster = separateControlCluster; }
+ public GraphVisualizer() { this(false); }
+
+ public String generateDotOutput(Parser parser) {
+
+ // Since the graph has cycles, we need to create a flat list of all the
+ // nodes in the graph.
+ Collection<Node> all = findAll(parser);
+ StringBuilder sb = new StringBuilder();
+ sb.append("digraph chapter08 {\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
+ // 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");
+
+ // Force nested scopes to order
+ sb.append("\tcompound=\"true\";\n");
+
+ // Just the Nodes first, in a cluster no edges
+ nodes(sb, all);
+
+ // Now the scopes, in a cluster no edges
+ scope(sb,parser._scope );
+ for( ScopeNode scope : parser._xScopes )
+ scope( sb, scope );
+
+ // Walk the Node edges
+ nodeEdges(sb, all);
+
+ // Walk the active Scope edges
+ scopeEdges( sb, parser._scope );
+ for( ScopeNode scope : parser._xScopes )
+ scopeEdges( sb, scope );
+
+ sb.append("}\n");
+ return sb.toString();
+ }
+
+ private void nodesByCluster(StringBuilder sb, boolean doCtrl, Collection<Node> all) {
+ if (!_separateControlCluster && doCtrl) // all nodes in 1 cluster
+ return;
+ // Just the Nodes first, in a cluster no edges
+ sb.append(doCtrl ? "\tsubgraph cluster_Controls {\n" : "\tsubgraph cluster_Nodes {\n"); // Magic "cluster_" in the subgraph name
+ for( Node n : all ) {
+ if( n instanceof ProjNode || n instanceof ScopeNode )
+ continue; // Do not emit, rolled into MultiNode or Scope cluster already
+ if (_separateControlCluster && doCtrl && !n.isCFG()) continue;
+ if (_separateControlCluster && !doCtrl && n.isCFG()) continue;
+ sb.append("\t\t").append(n.uniqueName()).append(" [ ");
+ 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");
+ sb.append("\t\t\t<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\" CELLPADDING=\"4\">\n");
+ sb.append("\t\t\t<TR><TD BGCOLOR=\"yellow\">").append(lab).append("</TD></TR>\n");
+ sb.append("\t\t\t<TR>");
+ boolean doProjTable = false;
+ for( Node use : n._outputs ) {
+ if( use instanceof ProjNode proj ) {
+ if (!doProjTable) {
+ doProjTable = true;
+ sb.append("<TD>").append("\n");
+ sb.append("\t\t\t\t<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">").append("\n");
+ sb.append("\t\t\t\t<TR>");
+ }
+ sb.append("<TD PORT=\"p").append(proj._idx).append("\"");
+ if( proj.isCFG() ) sb.append(" BGCOLOR=\"yellow\"");
+ sb.append(">").append(proj.glabel()).append("</TD>");
+ }
+ }
+ if (doProjTable) {
+ sb.append("</TR>").append("\n");
+ sb.append("\t\t\t\t</TABLE>").append("\n");
+ sb.append("\t\t\t</TD>");
+ }
+ sb.append("</TR>\n");
+ sb.append("\t\t\t</TABLE>>\n\t\t");
+
+ } else {
+ // control nodes have box shape
+ // other nodes are ellipses, i.e. default shape
+ if (n.isCFG()) sb.append("shape=box style=filled fillcolor=yellow ");
+ else if (n instanceof PhiNode) sb.append("style=filled fillcolor=lightyellow ");
+ sb.append("label=\"").append(lab).append("\" ");
+ }
+ sb.append("];\n");
+ }
+ if (!_separateControlCluster) {
+ // Force Region & Phis to line up
+ for( Node n : all ) {
+ if( n instanceof RegionNode region ) {
+ sb.append("\t\t{ rank=same; ");
+ sb.append(region).append(";") ;
+ for( Node phi : region._outputs )
+ if (phi instanceof PhiNode) sb.append(phi.uniqueName()).append(";");
+ sb.append("}\n");
+ }
+ }
+ }
+ sb.append("\t}\n"); // End Node cluster
+ }
+
+ private void nodes(StringBuilder sb, Collection<Node> all) {
+ nodesByCluster(sb, true, all);
+ nodesByCluster(sb, false, all);
+ }
+
+ // Build a nested scope display, walking the _prev edge
+ private void scope( StringBuilder sb, ScopeNode scope ) {
+ sb.append("\tnode [shape=plaintext];\n");
+ int level=1;
+ for( int idx = scope._scopes.size()-1; idx>=0; idx-- ) {
+ var syms = scope._scopes.get(idx);
+ String scopeName = makeScopeName(scope, 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<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">\n");
+ // Add the scope level
+ int scopeLevel = scope._scopes.size()-level;
+ sb.append("\t\t\t<TR><TD BGCOLOR=\"cyan\">").append(scopeLevel).append("</TD>");
+ for(String name: syms.keySet())
+ sb.append("<TD PORT=\"").append(makePortName(scopeName, name)).append("\">").append(name).append("</TD>");
+ sb.append("</TR>\n");
+ sb.append("\t\t\t</TABLE>>];\n");
+ level++;
+ }
+ // Scope clusters nest, so the graphics shows the nested scopes, so
+ // they are not closed as they are printed; so they just keep nesting.
+ // We close them all at once here.
+ sb.append( "\t}\n".repeat( level-1 ) ); // End all Scope clusters
+ }
+
+ 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<Node> all) {
+ // All them edge labels
+ sb.append("\tedge [ fontname=Helvetica, fontsize=8 ];\n");
+ for( Node n : all ) {
+ // 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;
+ for( int i=0; i<n.nIns(); i++ ) {
+ Node def = n.in(i);
+ if( n instanceof PhiNode && def instanceof RegionNode ) {
+ // Draw a dotted use->def edge from Phi to Region.
+ sb.append('\t').append(n.uniqueName());
+ sb.append(" -> ");
+ 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(" -> ");
+ if( def instanceof ProjNode proj ) {
+ String mname = proj.ctrl().uniqueName();
+ sb.append(mname).append(":p").append(proj._idx);
+ } else sb.append(def.uniqueName());
+ // 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");
+ // Backedges do not add a ranking constraint
+ if( i==2 && (n instanceof PhiNode || n instanceof LoopNode) )
+ sb.append(" constraint=false");
+ sb.append("];\n");
+ }
+ }
+ }
+ }
+
+ // Walk the scope edges
+ private void scopeEdges( StringBuilder sb, ScopeNode scope ) {
+ sb.append("\tedge [style=dashed color=cornflowerblue];\n");
+ int level=1;
+ for( int i = scope._scopes.size()-1; i>=0; i-- ) {
+ var syms = scope._scopes.get(i);
+ String scopeName = makeScopeName(scope, level);
+ for( String name : syms.keySet() ) {
+ int idx = syms.get(name);
+ Node def = scope.in(idx);
+ while( def instanceof ScopeNode lazy )
+ def = lazy.in(idx);
+ 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(" -> ");
+ if( def instanceof ProjNode proj ) {
+ String mname = proj.ctrl().uniqueName();
+ sb.append(mname).append(":p").append(proj._idx);
+ } else sb.append(def.uniqueName());
+ sb.append(";\n");
+ }
+ level++;
+ }
+ }
+
+ /**
+ * Finds all nodes in the graph.
+ */
+ private Collection<Node> findAll(Parser parser) {
+ final HashMap<Integer, Node> all = new HashMap<>();
+ for( Node n : Parser.START._outputs )
+ walk(all, n);
+ for( Node n : parser._scope._inputs )
+ walk(all, n);
+ return all.values();
+ }
+
+ /**
+ * Walk a subgraph and populate distinct nodes in the all list.
+ */
+ private void walk(HashMap<Integer,Node> all, Node n) {
+ 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)
+ walk(all, c);
+ for( Node c : n._outputs )
+ walk(all, c);
+ }
+}
diff --git a/chapter08/src/main/java/com/seaofnodes/simple/IRPrinter.java b/chapter08/src/main/java/com/seaofnodes/simple/IRPrinter.java
new file mode 100644
index 00000000..dc6d9f4f
--- /dev/null
+++ b/chapter08/src/main/java/com/seaofnodes/simple/IRPrinter.java
@@ -0,0 +1,124 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.node.LoopNode;
+import com.seaofnodes.simple.node.Node;
+
+import java.util.ArrayList;
+import java.util.BitSet;
+
+public class IRPrinter {
+
+ // Another bulk pretty-printer. Makes more effort at basic-block grouping.
+ public static String prettyPrint(Node node, int depth) {
+ // First, a Breadth First Search at a fixed depth.
+ BFS bfs = new BFS(node,depth);
+ // Convert just that set to a post-order
+ ArrayList<Node> rpos = new ArrayList<>();
+ BitSet visit = new BitSet();
+ for( int i=bfs._lim; i< bfs._bfs.size(); i++ )
+ postOrd( bfs._bfs.get(i), rpos, visit, bfs._bs);
+ // Reverse the post-order walk
+ StringBuilder sb = new StringBuilder();
+ boolean gap=false;
+ for( int i=rpos.size()-1; i>=0; i-- ) {
+ Node n = rpos.get(i);
+ if( n.isCFG() || n.isMultiHead() ) {
+ if( !gap ) sb.append("\n"); // Blank before multihead
+ n._print_line(sb); // Print head
+ while( --i >= 0 ) {
+ Node t = rpos.get(i);
+ if( !t.isMultiTail() ) { i++; break; }
+ t._print_line(sb);
+ }
+ sb.append("\n"); // Blank after multitail
+ gap = true;
+ } else {
+ n._print_line( sb );
+ gap = false;
+ }
+ }
+ return sb.toString();
+ }
+
+ private static void postOrd(Node n, ArrayList<Node> rpos, BitSet visit, BitSet bfs) {
+ if( !bfs.get(n._nid) )
+ return; // Not in the BFS visit
+ if( visit.get(n._nid) ) return; // Already post-order walked
+ visit.set(n._nid);
+ // First walk the CFG, then everything
+ if( n.isCFG() ) {
+ for( Node use : n._outputs )
+ if( use.isCFG() && use.nOuts()>=1 && !(use._outputs.get(0) instanceof LoopNode) )
+ postOrd(use, rpos,visit,bfs);
+ for( Node use : n._outputs )
+ if( use.isCFG() )
+ postOrd(use,rpos,visit,bfs);
+ }
+ for( Node use : n._outputs )
+ postOrd(use, rpos,visit,bfs);
+ // Post-order
+ rpos.add(n);
+ }
+
+ // Breadth-first search, broken out in a class to keep in more independent.
+ // Maintains a root-set of Nodes at the limit (or past by 1 if MultiHead).
+ public static class BFS {
+ // A breadth first search, plus MultiHeads for any MultiTails
+ public final ArrayList<Node> _bfs;
+ public final BitSet _bs; // Visited members by node id
+ public final int _depth; // Depth limit
+ public final int _lim; // From here to _bfs._len can be roots for a reverse search
+ public BFS( Node base, int d ) {
+ _depth = d;
+ _bfs = new ArrayList<>();
+ _bs = new BitSet();
+
+ add(base); // Prime the pump
+ int idx=0, lim=1; // Limit is where depth counter changes
+ while( idx < _bfs.size() ) { // Ran out of nodes below depth
+ Node n = _bfs.get(idx++);
+ for( Node def : n._inputs )
+ if( def!=null && !_bs.get(def._nid) )
+ add(def);
+ if( idx==lim ) { // Depth counter changes at limit
+ if( --d < 0 )
+ break; // Ran out of depth
+ lim = _bfs.size(); // New depth limit
+ }
+ }
+ // Toss things past the limit except multi-heads
+ while( idx < _bfs.size() ) {
+ Node n = _bfs.get(idx);
+ if( n.isMultiHead() ) idx++;
+ else del(idx);
+ }
+ // Root set is any node with no inputs in the visited set
+ lim = _bfs.size();
+ for( int i=_bfs.size()-1; i>=0; i-- )
+ if( !any_visited(_bfs.get(i)) )
+ swap( i,--lim);
+ _lim = lim;
+ }
+ void swap( int x, int y ) {
+ if( x==y ) return;
+ Node tx = _bfs.get(x);
+ Node ty = _bfs.get(y);
+ _bfs.set(x,ty);
+ _bfs.set(y,tx);
+ }
+ void add(Node n) {
+ _bfs.add(n);
+ _bs.set(n._nid);
+ }
+ void del(int idx) {
+ Node n = Utils.del(_bfs, idx);
+ _bs.clear(n._nid);
+ }
+ boolean any_visited( Node n ) {
+ for( Node def : n._inputs )
+ if( def!=null && _bs.get(def._nid) )
+ return true;
+ return false;
+ }
+ }
+}
diff --git a/chapter08/src/main/java/com/seaofnodes/simple/Parser.java b/chapter08/src/main/java/com/seaofnodes/simple/Parser.java
new file mode 100644
index 00000000..28b8f432
--- /dev/null
+++ b/chapter08/src/main/java/com/seaofnodes/simple/Parser.java
@@ -0,0 +1,574 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+
+import java.util.*;
+
+/**
+ * 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.
+ * <p>
+ * This is a simple recursive descent parser. All lexical analysis is done here as well.
+ */
+public class Parser {
+ public static boolean LAZY = false;
+
+ /**
+ * A Global Static, unique to each compilation. This is a public, so we
+ * can make constants everywhere without having to thread the StartNode
+ * through the entire parser and optimizer.
+ * <p>
+ * To make the compiler multithreaded, this field will have to move into a TLS.
+ */
+ public static StartNode START;
+
+ public StopNode STOP;
+
+ // The Lexer. Thin wrapper over a byte[] buffer with a cursor.
+ private final Lexer _lexer;
+
+ /**
+ * Current ScopeNode - ScopeNodes change as we parse code, but at any point of time
+ * there is one current ScopeNode. The reason the current ScopeNode can change is to do with how
+ * we handle branching. See {@link #parseIf()}.
+ * <p>
+ * Each ScopeNode contains a stack of lexical scopes, each scope is a symbol table that binds
+ * variable names to Nodes. The top of this stack represents current scope.
+ * <p>
+ * We keep a list of all ScopeNodes so that we can show them in graphs.
+ * @see #parseIf()
+ * @see #_xScopes
+ */
+ public ScopeNode _scope;
+
+ /**
+ * List of keywords disallowed as identifiers
+ */
+ private final HashSet<String> KEYWORDS = new HashSet<>(){{
+ add("else");
+ add("false");
+ add("if");
+ add("int");
+ add("return");
+ add("true");
+ add("while");
+ }};
+
+
+ /**
+ * We clone ScopeNodes when control flows branch; it is useful to have
+ * a list of all active ScopeNodes for purposes of visualization of the SoN graph
+ */
+ public final Stack<ScopeNode> _xScopes = new Stack<>();
+
+ public Parser(String source, TypeInteger arg) {
+ Node.reset();
+ _lexer = new Lexer(source);
+ _scope = new ScopeNode();
+ START = new StartNode(new Type[]{ Type.CONTROL, arg });
+ STOP = new StopNode();
+ }
+
+ public Parser(String source) {
+ this(source, TypeInteger.BOT);
+ }
+
+ @Override
+ public String toString() { return _lexer.toString(); }
+
+ String src() { return new String( _lexer._input ); }
+
+ // Debugging utility to find a Node by index
+ public static Node find(int nid) { return START.find(nid); }
+
+ private Node ctrl() { return _scope.ctrl(); }
+
+ private Node ctrl(Node n) { return _scope.ctrl(n); }
+
+ public StopNode parse() { return parse(false); }
+ public StopNode parse(boolean show) {
+ // Enter a new scope for the initial control and arguments
+ _scope.push();
+ _scope.define(ScopeNode.CTRL, new ProjNode(START, 0, ScopeNode.CTRL).peephole());
+ _scope.define(ScopeNode.ARG0, new ProjNode(START, 1, ScopeNode.ARG0).peephole());
+ parseBlock();
+ _scope.pop();
+ if (!_lexer.isEOF()) throw error("Syntax error, unexpected " + _lexer.getAnyNextToken());
+ STOP.peephole();
+ if( show ) showGraph();
+ return STOP;
+ }
+
+
+ /**
+ * Parses a block
+ *
+ * <pre>
+ * '{' statements '}'
+ * </pre>
+ * Does not parse the opening or closing '{}'
+ * @return a {@link Node} or {@code null}
+ */
+ private Node parseBlock() {
+ // Enter a new scope
+ _scope.push();
+ while (!peek('}') && !_lexer.isEOF())
+ parseStatement();
+ // Exit scope
+ _scope.pop();
+ return null;
+ }
+
+ /**
+ * Parses a statement
+ *
+ * <pre>
+ * returnStatement | declStatement | blockStatement | ifStatement | expressionStatement
+ * </pre>
+ * @return a {@link Node} or {@code null}
+ */
+ private Node parseStatement() {
+ if (matchx("return") ) return parseReturn();
+ else if (matchx("int")) return parseDecl();
+ else if (match ("{" )) return require(parseBlock(),"}");
+ else if (matchx("if" )) return parseIf();
+ else if (matchx("while")) return parseWhile();
+ else if (matchx("#showGraph")) return require(showGraph(),";");
+ else return parseExpressionStatement();
+ }
+
+ /**
+ * Parses a while statement
+ *
+ * <pre>
+ * while ( expression ) statement
+ * </pre>
+ * @return a {@link Node}, never {@code null}
+ */
+ private Node parseWhile() {
+ require("(");
+
+ // Loop region has two control inputs, the first one is the entry
+ // point, and second one is back edge that is set after loop is parsed
+ // (see end_loop() call below). Note that the absence of back edge is
+ // used as an indicator to switch off peepholes of the region and
+ // associated phis.
+
+ ctrl(new LoopNode(ctrl(),null).peephole());
+ ScopeNode head = _xScopes.push(_scope).keep(); // Save the current scope as the loop head
+ // Make a new Scope for the body, which has lazy-phi loop markers.
+ _scope = _scope.dup(true);
+
+ // Parse predicate
+ var pred = require(parseExpression(), ")");
+ // IfNode takes current control and predicate
+ IfNode ifNode = (IfNode)new IfNode(ctrl(), pred).<IfNode>keep().peephole();
+ // Setup projection nodes
+ Node ifT = new ProjNode(ifNode, 0, "True" ).peephole();
+ ifNode.unkeep();
+ Node ifF = new ProjNode(ifNode, 1, "False").peephole();
+ // The exit scope, accounting for any side effects in the predicate
+ var exit = _scope.dup();
+ _xScopes.push(exit);
+ exit.ctrl(ifF);
+
+ // Parse the true side, which corresponds to loop body
+ ctrl(ifT); // set ctrl token to ifTrue projection
+ parseStatement(); // Parse loop body
+
+ // The true branch loops back, so whatever is current control gets
+ // added to head loop as input
+ head.endLoop(_scope, exit);
+ head.unkeep().kill();
+ _xScopes.pop();
+ _xScopes.pop(); // Discard pushed from graph display
+
+ // At exit the false control is the current control, and
+ // the scope is the exit scope after the exit test.
+ return (_scope = exit);
+ }
+
+ /**
+ * Parses a statement
+ *
+ * <pre>
+ * if ( expression ) statement [else statement]
+ * </pre>
+ * @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).<IfNode>keep().peephole();
+ // Setup projection nodes
+ Node ifT = new ProjNode(ifNode, 0, "True" ).peephole();
+ ifNode.unkeep();
+ 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
+ _xScopes.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();
+ fScope = _scope;
+ }
+
+ if( tScope.nIns() != ndefs || fScope.nIns() != ndefs )
+ throw error("Cannot define a new name on one arm of an if");
+
+ // Merge results
+ _scope = tScope;
+ _xScopes.pop(); // Discard pushed from graph display
+
+ return ctrl(tScope.mergeScopes(fScope));
+ }
+
+
+ /**
+ * Parses a return statement; "return" already parsed.
+ * The $ctrl edge is killed.
+ *
+ * <pre>
+ * 'return' expr ;
+ * </pre>
+ * @return an expression {@link Node}, never {@code null}
+ */
+ private Node parseReturn() {
+ var expr = require(parseExpression(), ";");
+ Node ret = STOP.addReturn(new ReturnNode(ctrl(), expr).peephole());
+ ctrl(new ConstantNode(Type.XCONTROL).peephole()); // Kill control
+ return ret;
+ }
+
+ /**
+ * Dumps out the node graph
+ * @return {@code null}
+ */
+ private Node showGraph() {
+ System.out.println(new GraphVisualizer().generateDotOutput(this));
+ return null;
+ }
+
+ /**
+ * Parses an expression statement
+ *
+ * <pre>
+ * name '=' expression ';'
+ * </pre>
+ * @return an expression {@link Node}, never {@code null}
+ */
+ private Node parseExpressionStatement() {
+ var name = requireId();
+ require("=");
+ var expr = require(parseExpression(), ";");
+ if( _scope.update(name, expr)==null )
+ throw error("Undefined name '" + name + "'");
+ return expr;
+ }
+
+ /**
+ * Parses a declStatement
+ *
+ * <pre>
+ * 'int' name = expression ';'
+ * </pre>
+ * @return an expression {@link Node}, never {@code null}
+ */
+ private Node parseDecl() {
+ // Type is 'int' for now
+ var name = requireId();
+ require("=");
+ var expr = require(parseExpression(), ";");
+ if( _scope.define(name,expr) == null )
+ throw error("Redefining name '" + name + "'");
+ return expr;
+ }
+
+ /**
+ * Parse an expression of the form:
+ *
+ * <pre>
+ * expr : compareExpr
+ * </pre>
+ * @return an expression {@link Node}, never {@code null}
+ */
+ private Node parseExpression() { return parseComparison(); }
+
+ /**
+ * Parse an expression of the form:
+ *
+ * <pre>
+ * expr : additiveExpr op additiveExpr
+ * </pre>
+ * @return an comparator expression {@link Node}, never {@code null}
+ */
+ private Node parseComparison() {
+ var lhs = parseAddition();
+ if (match("==")) return new BoolNode.EQ(lhs, parseComparison()).peephole();
+ if (match("!=")) return new NotNode(new BoolNode.EQ(lhs, parseComparison()).peephole()).peephole();
+ if (match("<" )) return new BoolNode.LT(lhs, parseComparison()).peephole();
+ if (match("<=")) return new BoolNode.LE(lhs, parseComparison()).peephole();
+ if (match(">" )) return new BoolNode.LT(parseComparison(), lhs).peephole();
+ if (match(">=")) return new BoolNode.LE(parseComparison(), lhs).peephole();
+ return lhs;
+ }
+
+ /**
+ * Parse an additive expression
+ *
+ * <pre>
+ * additiveExpr : multiplicativeExpr (('+' | '-') multiplicativeExpr)*
+ * </pre>
+ * @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();
+ return lhs;
+ }
+
+ /**
+ * Parse an multiplicativeExpr expression
+ *
+ * <pre>
+ * multiplicativeExpr : unaryExpr (('*' | '/') unaryExpr)*
+ * </pre>
+ * @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();
+ return lhs;
+ }
+
+ /**
+ * Parse a unary minus expression.
+ *
+ * <pre>
+ * unaryExpr : ('-') unaryExpr | primaryExpr
+ * </pre>
+ * @return a unary expression {@link Node}, never {@code null}
+ */
+ private Node parseUnary() {
+ if (match("-")) return new MinusNode(parseUnary()).peephole();
+ return parsePrimary();
+ }
+
+ /**
+ * Parse a primary expression:
+ *
+ * <pre>
+ * primaryExpr : integerLiteral | Identifier | true | false | '(' expression ')'
+ * </pre>
+ * @return a primary {@link Node}, never {@code null}
+ */
+ private Node parsePrimary() {
+ if( _lexer.isNumber() ) return parseIntegerLiteral();
+ if( match("(") ) return require(parseExpression(), ")");
+ if( matchx("true" ) ) return new ConstantNode(TypeInteger.constant(1)).peephole();
+ if( matchx("false") ) return new ConstantNode(TypeInteger.constant(0)).peephole();
+ 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 + "'");
+ }
+
+ /**
+ * Parse integer literal
+ *
+ * <pre>
+ * integerLiteral: [1-9][0-9]* | [0]
+ * </pre>
+ */
+ private ConstantNode parseIntegerLiteral() {
+ return (ConstantNode) new ConstantNode(_lexer.parseNumber()).peephole();
+ }
+
+ //////////////////////////////////
+ // Utilities for lexical analysis
+
+ // Return true and skip if "syntax" is next in the stream.
+ 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 && !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 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);
+ }
+
+ ////////////////////////////////////
+ // Lexer components
+
+ private static class Lexer {
+
+ // Input buffer; an array of text bytes read from a file or a string
+ private final byte[] _input;
+ // Tracks current position in input buffer
+ private int _position = 0;
+
+ /**
+ * Record the source text for lexing
+ */
+ public Lexer(String source) {
+ this(source.getBytes());
+ }
+
+ /**
+ * Direct from disk file source
+ */
+ public Lexer(byte[] buf) {
+ _input = buf;
+ }
+
+ // Very handy in the debugger, shows the unparsed program
+ @Override
+ public String toString() {
+ return new String(_input, _position, _input.length - _position);
+ }
+
+ // True if at EOF
+ private boolean isEOF() {
+ return _position >= _input.length;
+ }
+
+ // Peek next character, or report EOF
+ private char peek() {
+ return isEOF() ? Character.MAX_VALUE // Special value that causes parsing to terminate
+ : (char) _input[_position];
+ }
+
+ private char nextChar() {
+ char ch = peek();
+ _position++;
+ return ch;
+ }
+
+ // True if a white space
+ private boolean isWhiteSpace() {
+ return peek() <= ' '; // Includes all the use space, tab, newline, CR
+ }
+
+ /**
+ * Return the next non-white-space character
+ */
+ private void skipWhiteSpace() {
+ while (isWhiteSpace()) _position++;
+ }
+
+
+ // 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) {
+ 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))
+ 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();
+ return isIdStart(peek()) ? parseId() : null;
+ }
+
+ // Used for errors
+ String getAnyNextToken() {
+ 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);}
+
+ private Type parseNumber() {
+ int start = _position;
+ while (isNumber(nextChar())) ;
+ 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 TypeInteger.constant(Long.parseLong(snum));
+ }
+
+ // First letter of an identifier
+ private boolean isIdStart(char ch) {
+ return Character.isAlphabetic(ch) || ch == '_';
+ }
+
+ // All characters of an identifier, e.g. "_x123"
+ private boolean isIdLetter(char ch) {
+ return Character.isLetterOrDigit(ch) || ch == '_';
+ }
+
+ private String parseId() {
+ int start = _position;
+ while (isIdLetter(nextChar())) ;
+ return new String(_input, start, --_position - start);
+ }
+
+ //
+ private boolean isPunctuation(char ch) {
+ return "=;[]<>()+-/*".indexOf(ch) != -1;
+ }
+
+ private String parsePunctuation() {
+ int start = _position;
+ return new String(_input, start, 1);
+ }
+ }
+}
diff --git a/chapter08/src/main/java/com/seaofnodes/simple/Utils.java b/chapter08/src/main/java/com/seaofnodes/simple/Utils.java
new file mode 100644
index 00000000..4d663c25
--- /dev/null
+++ b/chapter08/src/main/java/com/seaofnodes/simple/Utils.java
@@ -0,0 +1,39 @@
+package com.seaofnodes.simple;
+
+import java.util.ArrayList;
+
+public class Utils {
+ public static RuntimeException TODO() { return TODO("Not yet implemented"); }
+ public static RuntimeException TODO(String msg) { return new RuntimeException(msg); }
+
+ /**
+ * Fast, constant-time, element removal. Does not preserve order
+ *
+ * @param array ArrayList to modify
+ * @param i element to be removed
+ * @return element removed
+ */
+ public static <E> E del(ArrayList<E> array, int i) {
+ if ( i >= 0 && i < array.size() ) {
+ E tmp = array.get(i);
+ E last = array.removeLast();
+ if (i < array.size()) array.set(i, last);
+ return tmp;
+ }
+ return null;
+ }
+
+ /**
+ * Search a list for an element by reference
+ *
+ * @param ary List to search in
+ * @param x Object to be searched
+ * @return >= 0 on success, -1 on failure
+ */
+ public static <E> int find( ArrayList<E> ary, E x ) {
+ for( int i=0; i<ary.size(); i++ )
+ if( ary.get(i)==x )
+ return i;
+ return -1;
+ }
+} \ No newline at end of file
diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/AddNode.java
new file mode 100644
index 00000000..85d7b20e
--- /dev/null
+++ b/chapter08/src/main/java/com/seaofnodes/simple/node/AddNode.java
@@ -0,0 +1,158 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.*;
+
+import java.util.BitSet;
+
+public class AddNode extends Node {
+ public AddNode(Node lhs, Node rhs) {
+ super(null, lhs, rhs);
+ }
+
+ @Override public String label() { return "Add"; }
+
+ @Override public String glabel() { return "+"; }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ in(1)._print0(sb.append("("), visited);
+ in(2)._print0(sb.append("+"), visited);
+ return sb.append(")");
+ }
+
+
+ @Override
+ public Type compute() {
+ if( in(1)._type instanceof TypeInteger i0 &&
+ in(2)._type instanceof TypeInteger i1 ) {
+ if (i0.isConstant() && i1.isConstant())
+ return TypeInteger.constant(i0.value()+i1.value());
+ return i0.meet(i1);
+ }
+ return Type.BOTTOM;
+ }
+
+ @Override
+ public Node idealize () {
+ Node lhs = in(1);
+ Node rhs = in(2);
+ 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 instanceof TypeInteger i && i.value()==0 )
+ return lhs;
+
+ // 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).
+
+ // Move non-adds to RHS
+ if( !(lhs instanceof AddNode) && rhs instanceof AddNode )
+ return swap12();
+
+ // Now we might see (add add non) or (add non non) or (add add add) but never (add non add)
+
+ // 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) )
+ // Rotate; look for (add (phi cons) con/(phi cons))
+ return spline_cmp(lhs,rhs) ? swap12() : phiCon(this,true);
+
+ // 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());
+
+
+ // Do we have ((x + (phi cons)) + con) ?
+ // Do we have ((x + (phi cons)) + (phi cons)) ?
+ // Push constant up through the phi: x + (phi con0+con0 con1+con1...)
+ Node phicon = phiCon(this,true);
+ if( phicon!=null ) return phicon;
+
+ // 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;
+ }
+
+ // Rotation is only valid for associative ops, e.g. Add, Mul, And, Or.
+ // Do we have ((phi cons)|(x + (phi cons)) + con|(phi cons)) ?
+ // Push constant up through the phi: x + (phi con0+con0 con1+con1...)
+ static Node phiCon(Node op, boolean rotate) {
+ Node lhs = op.in(1);
+ Node rhs = op.in(2);
+ // LHS is either a Phi of constants, or another op with Phi of constants
+ PhiNode lphi = pcon(lhs);
+ if( rotate && lphi==null && lhs.nIns() > 2 ) {
+ // Only valid to rotate constants if both are same associative ops
+ if( lhs.getClass() != op.getClass() ) return null;
+ lphi = pcon(lhs.in(2)); // Will rotate with the Phi push
+ }
+ if( lphi==null ) return null;
+
+ // RHS is a constant or a Phi of constants
+ if( !(rhs instanceof ConstantNode con) && pcon(rhs)==null )
+ return null;
+
+ // If both are Phis, must be same Region
+ if( rhs instanceof PhiNode && lphi.in(0) != rhs.in(0) )
+ return null;
+
+ // Note that this is the exact reverse of Phi pulling a common op down
+ // to reduce total op-count. We don't get in an endless push-up
+ // push-down peephole cycle because the constants all fold first.
+ Node[] ns = new Node[lphi.nIns()];
+ ns[0] = lphi.in(0);
+ // Push constant up through the phi: x + (phi con0+con0 con1+con1...)
+ for( int i=1; i<ns.length; i++ )
+ ns[i] = op.copy(lphi.in(i), rhs instanceof PhiNode ? rhs.in(i) : rhs).peephole();
+ String label = lphi._label + (rhs instanceof PhiNode rphi ? rphi._label : "");
+ Node phi = new PhiNode(label,ns).peephole();
+ // Rotate needs another op, otherwise just the phi
+ return lhs==lphi ? phi : op.copy(lhs.in(1),phi);
+ }
+
+ static PhiNode pcon(Node op) {
+ return op instanceof PhiNode phi && phi.all_cons() ? phi : 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 Phi-of-constants, then muls, 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 ;
+
+ if( lo instanceof PhiNode && lo.all_cons() ) return false;
+ if( hi instanceof PhiNode && hi.all_cons() ) return true ;
+
+ if( lo instanceof PhiNode && !(hi instanceof PhiNode) ) return true;
+ if( hi instanceof PhiNode && !(lo instanceof PhiNode) ) return false;
+
+ // Same category of "others"
+ return lo._nid > hi._nid;
+ }
+
+ @Override Node copy(Node lhs, Node rhs) { return new AddNode(lhs,rhs); }
+}
diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/BoolNode.java
new file mode 100644
index 00000000..d65fbf14
--- /dev/null
+++ b/chapter08/src/main/java/com/seaofnodes/simple/node/BoolNode.java
@@ -0,0 +1,60 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+
+import java.util.BitSet;
+
+abstract public class BoolNode extends Node {
+
+ public BoolNode(Node lhs, Node rhs) {
+ super(null, lhs, rhs);
+ }
+
+ abstract String op(); // String opcode name
+
+ @Override
+ public String label() { return getClass().getSimpleName(); }
+
+ @Override
+ public String glabel() { return op(); }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ in(1)._print0(sb.append("("), visited);
+ in(2)._print0(sb.append(op()), visited);
+ return sb.append(")");
+ }
+
+ @Override
+ public Type compute() {
+ if( in(1)._type instanceof TypeInteger i0 &&
+ in(2)._type instanceof TypeInteger i1 ) {
+ if (i0.isConstant() && i1.isConstant())
+ return TypeInteger.constant(doOp(i0.value(), i1.value()) ? 1 : 0);
+ return i0.meet(i1);
+ }
+ return Type.BOTTOM;
+ }
+
+ abstract boolean doOp(long lhs, long rhs);
+
+ @Override
+ public Node idealize() {
+ // Compare of same
+ if( in(1)==in(2) )
+ return new ConstantNode(TypeInteger.constant(doOp(3,3)?1:0));
+
+ // Do we have ((x * (phi cons)) * con) ?
+ // Do we have ((x * (phi cons)) * (phi cons)) ?
+ // Push constant up through the phi: x * (phi con0*con0 con1*con1...)
+ Node phicon = AddNode.phiCon(this,false);
+ if( phicon!=null ) return phicon;
+
+ return null;
+ }
+
+ public static class EQ extends BoolNode { public EQ(Node lhs, Node rhs) { super(lhs,rhs); } String op() { return "=="; } boolean doOp(long lhs, long rhs) { return lhs == rhs; } Node copy(Node lhs, Node rhs) { return new EQ(lhs,rhs); } }
+ public static class LT extends BoolNode { public LT(Node lhs, Node rhs) { super(lhs,rhs); } String op() { return "<" ; } boolean doOp(long lhs, long rhs) { return lhs < rhs; } Node copy(Node lhs, Node rhs) { return new LT(lhs,rhs); } }
+ public static class LE extends BoolNode { public LE(Node lhs, Node rhs) { super(lhs,rhs); } String op() { return "<="; } boolean doOp(long lhs, long rhs) { return lhs <= rhs; } Node copy(Node lhs, Node rhs) { return new LE(lhs,rhs); } }
+}
diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/ConstantNode.java
new file mode 100644
index 00000000..0e13d2f2
--- /dev/null
+++ b/chapter08/src/main/java/com/seaofnodes/simple/node/ConstantNode.java
@@ -0,0 +1,45 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.Type;
+
+import java.util.BitSet;
+
+/**
+ * A Constant node represents a constant value. At present, the only constants
+ * that we allow are integer literals; therefore Constants contain an integer
+ * value. As we add other types of constants, we will refactor how we represent
+ * Constants.
+ * <p>
+ * Constants have no semantic inputs. However, we set Start as an input to
+ * Constants to enable a forward graph walk. This edge carries no semantic
+ * meaning, and it is present <em>solely</em> to allow visitation.
+ * <p>
+ * The Constant's value is the value stored in it.
+ */
+public class ConstantNode extends Node {
+ Type _con;
+ public ConstantNode( Type type ) {
+ super(Parser.START);
+ _con = type;
+ }
+
+ @Override
+ public String label() { return ""+_con; }
+
+ @Override
+ public String uniqueName() { return "Con_" + _nid; }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return _con._print(sb);
+ }
+
+ @Override public boolean isMultiTail() { return true; }
+
+ @Override
+ public Type compute() { return _con; }
+
+ @Override
+ public Node idealize() { return null; }
+}
diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/DivNode.java
new file mode 100644
index 00000000..b0e12547
--- /dev/null
+++ b/chapter08/src/main/java/com/seaofnodes/simple/node/DivNode.java
@@ -0,0 +1,39 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+
+import java.util.BitSet;
+
+public class DivNode extends Node {
+ public DivNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
+
+ @Override public String label() { return "Div"; }
+
+ @Override public String glabel() { return "//"; }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ in(1)._print0(sb.append("("), visited);
+ in(2)._print0(sb.append("/"), visited);
+ return sb.append(")");
+ }
+
+ @Override
+ public Type compute() {
+ if (in(1)._type instanceof TypeInteger i0 &&
+ in(2)._type instanceof TypeInteger i1) {
+ if (i0.isConstant() && i1.isConstant())
+ return i1.value() == 0
+ ? TypeInteger.ZERO
+ : TypeInteger.constant(i0.value()/i1.value());
+ return i0.meet(i1);
+ }
+ return Type.BOTTOM;
+ }
+
+ @Override
+ public Node idealize() { return null; }
+
+ @Override Node copy(Node lhs, Node rhs) { return new DivNode(lhs,rhs); }
+}
diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/IfNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/IfNode.java
new file mode 100644
index 00000000..e573010d
--- /dev/null
+++ b/chapter08/src/main/java/com/seaofnodes/simple/node/IfNode.java
@@ -0,0 +1,58 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.type.TypeTuple;
+
+import java.util.BitSet;
+
+public class IfNode extends MultiNode {
+
+ public IfNode(Node ctrl, Node pred) {
+ super(ctrl, pred);
+ }
+
+ @Override
+ public String label() { return "If"; }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ sb.append("if( ");
+ return in(1)._print0(sb, visited).append(" )");
+ }
+
+ @Override public boolean isCFG() { return true; }
+ @Override public boolean isMultiHead() { return true; }
+
+ public Node ctrl() { return in(0); }
+ public Node pred() { return in(1); }
+
+ @Override
+ public Type compute() {
+ // If the If node is not reachable then neither is any following Proj
+ if (ctrl()._type != Type.CONTROL && ctrl()._type != Type.BOTTOM )
+ return TypeTuple.IF_NEITHER;
+ // If constant is 0 then false branch is reachable
+ // Else true branch is reachable
+ if (pred()._type instanceof TypeInteger ti && ti.isConstant()) {
+ if (ti.value() == 0) return TypeTuple.IF_FALSE;
+ else return TypeTuple.IF_TRUE;
+ }
+
+ // Hunt up the immediate dominator tree. If we find an identical if
+ // test on either the true or false branch, then this test matches.
+ for( Node dom = idom(), prior=this; dom!=null; prior=dom, dom = dom.idom() )
+ if( dom instanceof IfNode iff && iff.pred()==pred() )
+ return prior instanceof ProjNode proj
+ // Repeated test, dominated on one side. Test result is the same.
+ ? (proj._idx==0 ? TypeTuple.IF_TRUE : TypeTuple.IF_FALSE)
+ : null; // Repeated test not dominated on one side
+
+ return TypeTuple.IF_BOTH;
+ }
+
+ @Override
+ public Node idealize() {
+ return null;
+ }
+}
diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/LoopNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/LoopNode.java
new file mode 100644
index 00000000..eaf19199
--- /dev/null
+++ b/chapter08/src/main/java/com/seaofnodes/simple/node/LoopNode.java
@@ -0,0 +1,35 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+
+import java.util.BitSet;
+
+public class LoopNode extends RegionNode {
+
+ public LoopNode( Node entry, Node back) { super(null,entry,back); }
+
+ Node entry() { return in(1); }
+ Node back () { return in(2); }
+
+ @Override
+ public String label() { return "Loop"; }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return sb.append("Loop").append(_nid);
+ }
+
+ @Override
+ public Type compute() {
+ return inProgress() ? Type.CONTROL : super.compute();
+ }
+
+ @Override
+ public Node idealize() {
+ return inProgress() ? null : super.idealize();
+ }
+
+ @Override Node idom() { return entry(); }
+
+ @Override public boolean inProgress() { return back()==null; }
+}
diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/MinusNode.java
new file mode 100644
index 00000000..8a96693d
--- /dev/null
+++ b/chapter08/src/main/java/com/seaofnodes/simple/node/MinusNode.java
@@ -0,0 +1,30 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+
+import java.util.BitSet;
+
+public class MinusNode extends Node {
+ public MinusNode(Node in) { super(null, in); }
+
+ @Override public String label() { return "Minus"; }
+
+ @Override public String glabel() { return "-"; }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ in(1)._print0(sb.append("(-"), visited);
+ return sb.append(")");
+ }
+
+ @Override
+ public Type compute() {
+ if (in(1)._type instanceof TypeInteger i0)
+ return i0.isConstant() ? TypeInteger.constant(-i0.value()) : i0;
+ return Type.BOTTOM;
+ }
+
+ @Override
+ public Node idealize() { return null; }
+}
diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/MulNode.java
new file mode 100644
index 00000000..70e6c0db
--- /dev/null
+++ b/chapter08/src/main/java/com/seaofnodes/simple/node/MulNode.java
@@ -0,0 +1,58 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+
+import java.util.BitSet;
+
+public class MulNode extends Node {
+ public MulNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
+
+ @Override public String label() { return "Mul"; }
+
+ @Override public String glabel() { return "*"; }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ in(1)._print0(sb.append("("), visited);
+ in(2)._print0(sb.append("*"), visited);
+ return sb.append(")");
+ }
+
+ @Override
+ public Type compute() {
+ if (in(1)._type instanceof TypeInteger i0 &&
+ in(2)._type instanceof TypeInteger i1) {
+ if (i0.isConstant() && i1.isConstant())
+ return TypeInteger.constant(i0.value()*i1.value());
+ return i0.meet(i1);
+ }
+ return Type.BOTTOM;
+ }
+
+ @Override
+ public Node idealize() {
+ Node lhs = in(1);
+ Node rhs = in(2);
+ Type t1 = lhs._type;
+ Type t2 = rhs._type;
+
+ // Mul of 1. We do not check for (1*x) because this will already
+ // canonicalize to (x*1)
+ if ( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==1 )
+ return lhs;
+
+ // Move constants to RHS: con*arg becomes arg*con
+ if ( t1.isConstant() && !t2.isConstant() )
+ return swap12();
+
+ // Do we have ((x * (phi cons)) * con) ?
+ // Do we have ((x * (phi cons)) * (phi cons)) ?
+ // Push constant up through the phi: x * (phi con0*con0 con1*con1...)
+ Node phicon = AddNode.phiCon(this,true);
+ if( phicon!=null ) return phicon;
+
+ return null;
+ }
+ @Override Node copy(Node lhs, Node rhs) { return new MulNode(lhs,rhs); }
+}
diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/MultiNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/MultiNode.java
new file mode 100644
index 00000000..81c90f68
--- /dev/null
+++ b/chapter08/src/main/java/com/seaofnodes/simple/node/MultiNode.java
@@ -0,0 +1,7 @@
+package com.seaofnodes.simple.node;
+
+public abstract class MultiNode extends Node {
+
+ public MultiNode(Node... inputs) { super(inputs); }
+
+}
diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter08/src/main/java/com/seaofnodes/simple/node/Node.java
new file mode 100644
index 00000000..fcc8df85
--- /dev/null
+++ b/chapter08/src/main/java/com/seaofnodes/simple/node/Node.java
@@ -0,0 +1,467 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.type.Type;
+
+import java.util.*;
+
+/**
+ * All Nodes in the Sea of Nodes IR inherit from the Node class.
+ * The Node class provides common functionality used by all subtypes.
+ * Subtypes of Node specialize by overriding methods.
+ */
+public abstract class Node {
+
+ /**
+ * Each node has a unique dense Node ID within a compilation context
+ * The ID is useful for debugging, for using as an offset in a bitvector,
+ * as well as for computing equality of nodes (to be implemented later).
+ */
+ public final int _nid;
+
+ /**
+ * Inputs to the node. These are use-def references to Nodes.
+ * <p>
+ * 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 {@link #isCFG} node.
+ */
+ public final ArrayList<Node> _inputs;
+
+ /**
+ * Outputs reference Nodes that are not null and have this Node as an
+ * input. These nodes are users of this node, thus these are def-use
+ * references to Nodes.
+ * <p>
+ * Outputs directly match inputs, making a directed graph that can be
+ * walked in either direction. These outputs are typically used for
+ * efficient optimizations but otherwise have no semantics meaning.
+ */
+ public final ArrayList<Node> _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;
+
+
+ /**
+ * Immediate dominator tree depth, used to approximate a real IDOM during
+ * parsing where we do not have the whole program, and also peepholes
+ * change the CFG incrementally.
+ * <p>
+ * See {@link <a href="https://en.wikipedia.org/wiki/Dominator_(graph_theory)">...</a>}
+ */
+ int _idepth;
+
+ /**
+ * A private Global Static mutable counter, for unique node id generation.
+ * To make the compiler multithreaded, this field will have to move into a TLS.
+ * Starting with value 1, to avoid bugs confusing node ID 0 with uninitialized values.
+ * */
+ private static int UNIQUE_ID = 1;
+
+ 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.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(); }
+
+
+ // ------------------------------------------------------------------------
+
+ // Debugger Printing.
+
+ // {@code toString} is what you get in the debugger. It has to print 1
+ // line (because this is what a debugger typically displays by default) and
+ // has to be robust with broken graph/nodes.
+ @Override
+ public final String toString() { 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. 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 _print0(new StringBuilder(), new BitSet()).toString();
+ }
+ // This is the common print: check for repeats, check for DEAD and print
+ // "DEAD" else call the per-Node print1.
+ final StringBuilder _print0(StringBuilder sb, BitSet visited) {
+ if (visited.get(_nid))
+ return sb.append(label());
+ visited.set(_nid);
+ return isDead()
+ ? sb.append(uniqueName()).append(":DEAD")
+ : _print1(sb, visited);
+ }
+ // Every Node implements this; a partial-line recursive print
+ abstract StringBuilder _print1(StringBuilder sb, BitSet visited);
+
+
+ // Print a node on 1 line, columnar aligned, as:
+ // NNID NNAME DDEF DDEF [[ UUSE UUSE ]] TYPE
+ // 1234 sssss 1234 1234 1234 1234 1234 1234 tttttt
+ public void _print_line( StringBuilder sb ) {
+ sb.append("%4d %-7.7s ".formatted(_nid,label()));
+ if( _inputs==null ) {
+ sb.append("DEAD\n");
+ return;
+ }
+ for( Node def : _inputs )
+ sb.append(def==null ? "____ " : "%4d ".formatted(def._nid));
+ for( int i = _inputs.size(); i<3; i++ )
+ sb.append(" ");
+ sb.append(" [[ ");
+ for( Node use : _outputs )
+ sb.append("%4d ".formatted(use._nid));
+ int lim = 5 - Math.max(_inputs.size(),3);
+ for( int i = _outputs.size(); i<lim; i++ )
+ sb.append(" ");
+ sb.append(" ]] ");
+ if( _type!= null ) _type._print(sb);
+ sb.append("\n");
+ }
+
+ public boolean isMultiHead() { return false; }
+ public boolean isMultiTail() { return false; }
+
+ // ------------------------------------------------------------------------
+ // Graph Node & Edge manipulation
+
+ /**
+ * Gets the ith input node
+ * @param i Offset of the input node
+ * @return Input node or null
+ */
+ public Node in(int i) { return _inputs.get(i); }
+
+ public int nIns() { return _inputs.size(); }
+
+ 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; }
+
+ /**
+ * Change a <em>def</em> into a Node. Keeps the edges correct, by removing
+ * the corresponding <em>use->def</em> edge. This may make the original
+ * <em>def</em> go dead. This function is co-recursive with {@link #kill}.
+ * <p>
+
+ * This method is the normal path for altering a Node, because it does the
+ * proper default edge maintenance. It also <em>immediately</em> kills
+ * Nodes that lose their last use; at times care must be taken to avoid
+ * killing Nodes that are being used without having an output Node. This
+ * definitely happens in the middle of recursive {@link #peephole} calls.
+ *
+ * @param idx which def to set
+ * @param new_def the new definition
+ * @return new_def for flow coding
+ */
+ Node set_def(int idx, Node new_def ) {
+ Node old_def = in(idx);
+ 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.
+ if( new_def != null )
+ 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
+ // Set the new_def over the old (killed) edge
+ _inputs.set(idx,new_def);
+ // Return self for easy flow-coding
+ return new_def;
+ }
+
+ // Remove the numbered input, compressing the inputs in-place. This
+ // shuffles the order deterministically - which is suitable for Region and
+ // Phi, but not for every Node.
+ void delDef(int idx) {
+ Node old_def = in(idx);
+ 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
+ Utils.del(_inputs, idx);
+ }
+
+ /**
+ * Add a new def to an existing Node. Keep the edges correct by
+ * adding the corresponding <em>def->use</em> edge.
+ *
+ * @param new_def the new definition, appended to the end of existing definitions
+ * @return new_def for flow coding
+ */
+ public Node add_def(Node new_def) {
+ // Add use->def edge
+ _inputs.add(new_def);
+ // If new def is not null, add the corresponding def->use edge
+ if( new_def != null )
+ new_def.addUse(this);
+ return new_def;
+ }
+
+ // Breaks the edge invariants, used temporarily
+ protected <N extends Node> N addUse(Node n) { _outputs.add(n); return (N)this; }
+
+ // Remove node 'use' from 'def's (i.e. our) output list, by compressing the list in-place.
+ // Return true if the output list is empty afterward.
+ // Error is 'use' does not exist; ok for 'use' to be null.
+ protected boolean delUse( Node use ) {
+ Utils.del(_outputs, Utils.find(_outputs, use));
+ return _outputs.size() == 0;
+ }
+
+ // Shortcut for "popping" n nodes. A "pop" is basically a
+ // set_def(last,null) followed by lowering the nIns() count.
+ void pop_n(int n) {
+ for( int i=0; i<n; i++ ) {
+ Node old_def = _inputs.removeLast();
+ if( old_def != null && // If it exists and
+ old_def.delUse(this) ) // If we removed the last use, the old def is now dead
+ old_def.kill(); // Kill old def
+ }
+ }
+
+ /**
+ * Kill a Node with no <em>uses</em>, by setting all of its <em>defs</em>
+ * to null. This may recursively kill more Nodes and is basically dead
+ * code elimination. This function is co-recursive with {@link #pop_n}.
+ */
+ 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; }
+
+ // Shortcuts to stop DCE mid-parse
+ // Add bogus null use to keep node alive
+ public <N extends Node> N keep() { return addUse(null); }
+ // Remove bogus null.
+ public <N extends Node> N unkeep() { delUse(null); return (N)this; }
+
+
+ // Replace self with nnn in the graph, making 'this' go dead
+ void subsume( Node nnn ) {
+ assert nnn!=this;
+ while( nOuts() > 0 ) {
+ Node n = _outputs.removeLast();
+ int idx = Utils.find(n._inputs, this);
+ n._inputs.set(idx,nnn);
+ nnn.addUse(n);
+ }
+ kill();
+ }
+
+ // ------------------------------------------------------------------------
+ // Graph-based optimizations
+
+ /**
+ * 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:
+ * <ul>
+ * <li>if the Type {@link Type#isConstant}, we replace with a {@link ConstantNode}</li>
+ * <li>in a future chapter we will look for a
+ * <a href="https://en.wikipedia.org/wiki/Common_subexpression_elimination">Common Subexpression</a>
+ * to eliminate.</li>
+ * <li>we ask the Node for a better replacement. The "better replacement"
+ * is things like {@code (1+2)} becomes {@code 3} and {@code (1+(x+2))} becomes
+ * {@code (x+(1+2))}. By canonicalizing expressions we fold common addressing
+ * math constants, remove algebraic identities and generally simplify the
+ * code. </li>
+ * </ul>
+ */
+ 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
+ if (!(this instanceof ConstantNode) && type.isConstant())
+ return deadCodeElim(new ConstantNode(type).peephole());
+
+ // Future chapter: Global Value Numbering goes here
+
+ // Ask each node for a better replacement
+ Node n = idealize();
+ if( n != null ) // Something changed
+ // Recursively optimize
+ return deadCodeElim(n.peephole());
+
+ return this; // No progress
+ }
+
+ // m is the new Node, self is the old.
+ // Return 'm', which may have zero uses but is alive nonetheless.
+ // If self has zero uses (and is not 'm'), {@link #kill} self.
+ private Node deadCodeElim(Node m) {
+ // If self is going dead and not being returned here (Nodes returned
+ // from peephole commonly have no uses (yet)), then kill self.
+ if( m != this && isUnused() ) {
+ // Killing self - and since self recursively kills self's inputs we
+ // might end up killing 'm', which we are returning as a live Node.
+ // So we add a bogus extra null output edge to stop kill().
+ m.keep(); // Keep m alive
+ kill(); // Kill self because replacing with 'm'
+ m.unkeep(); // Okay to peephole m
+ }
+ return m;
+ }
+
+ /**
+ * This function needs to be
+ * <a href="https://en.wikipedia.org/wiki/Monotonic_function">Monotonic</a>
+ * as it is part of a Monotone Analysis Framework.
+ * <a href="https://www.cse.psu.edu/~gxt29/teaching/cse597s21/slides/08monotoneFramework.pdf">See for example this set of slides</a>.
+ * <p>
+ * For Chapter 2, all our Types are really integer constants, and so all
+ * the needed properties are trivially true, and we can ignore the high
+ * theory. Much later on, this will become important and allow us to do
+ * many fancy complex optimizations trivially... because theory.
+ * <p>
+ * compute() needs to be stand-alone, and cannot recursively call compute
+ * on its inputs programs are cyclic (have loops!) and this will just
+ * infinitely recurse until stack overflow. Instead, compute typically
+ * computes a new type from the {@link #_type} field of its inputs.
+ */
+ public abstract Type compute();
+
+ /**
+ * This function rewrites the current Node into a more "idealized" form.
+ * This is the bulk of our peephole rewrite rules, and we use this to
+ * e.g. turn arbitrary collections of adds and multiplies with mixed
+ * constants into a normal form that's easy for hardware to implement.
+ * Example: An array addressing expression:
+ * <pre> ary[idx+1]</pre>
+ * might turn into Sea-of-Nodes IR:
+ * <pre> (ary+12)+((idx+1) * 4)</pre>
+ * This expression can then be idealized into:
+ * <pre> ary + ((idx*4) + (12 + (1*4)))</pre>
+ * And more folding:
+ * <pre> ary + ((idx<<2) + 16)</pre>
+ * And during code-gen:
+ * <pre> MOV4 Rary,Ridx,16 // or some such hardware-specific notation </pre>
+ * <p>
+ * {@link #idealize} has a very specific calling convention:
+ * <ul>
+ * <li>If NO change is made, return {@code null}
+ * <li>If ANY change is made, return not-null; this can be {@code this}
+ * <li>The returned Node does NOT call {@link #peephole} on itself; the {@link #peephole} call will recursively peephole it.
+ * <li>Any NEW nodes that are not directly returned DO call {@link #peephole}.
+ * </ul>
+ * <p>
+ * Examples:
+ * <table border="3">
+ * <tr><th> before </th><th> after </th><th>return </th><th>comment </th></tr>
+ * <tr><td>{@code (x+5) }</td><td>{@code (x+5) }</td><td>{@code null }</td><td>No change</td></tr>
+ * <tr><td>{@code (5+x) }</td><td>{@code (x+5) }</td><td>{@code this }</td><td>Swapped arguments</td></tr>
+ * <tr><td>{@code ((x+1)+2)}</td><td>{@code (x+(1+2))}</td><td>{@code (x+_) }</td><td>Returns 2 new Nodes</td></tr>
+ * </table>
+ *
+ * The last entry deserves more discussion. The new Node {@code (1+2)}
+ * created in {@link #idealize} calls {@link #peephole} (which then folds
+ * into a constant). The other new Node {@code (x+3)} does not call
+ * peephole, because it is returned and peephole itself will recursively
+ * call peephole.
+ * <p>
+ * Since idealize calls peephole and peephole calls idealize, you must be
+ * careful that all idealizations are <em>monotonic</em>: all transforms remove
+ * some feature, so that the set of available transforms always shrinks.
+ * If you don't, you risk an infinite peephole loop!
+ *
+ * @return Either a new or changed node, or null for no changes.
+ */
+ 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;
+ }
+
+ // does this node contain all constants?
+ // Ignores in(0), as is usually control.
+ boolean all_cons() {
+ for( int i=1; i<nIns(); i++ )
+ if( !(in(i)._type.isConstant()) )
+ return false;
+ return true;
+ }
+
+ // Return the immediate dominator of this Node and compute dom tree depth.
+ Node idom() {
+ Node idom = in(0);
+ if( idom._idepth==0 ) idom.idom(); // Recursively set _idepth
+ if( _idepth==0 ) _idepth = idom._idepth+1;
+ return idom;
+ }
+
+ // Make a shallow copy (same class) of this Node, with given inputs and
+ // empty outputs and a new Node ID. The original inputs are ignored.
+ // Does not need to be implemented in isCFG() nodes.
+ Node copy(Node lhs, Node rhs) { throw Utils.TODO("Binary ops need to implement copy"); }
+
+ /**
+ * 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; _disablePeephole=false; }
+
+ /**
+ * Debugging utility to find a Node by index
+ */
+ public Node find(int nid) { return _find(new BitSet(),nid); }
+ private Node _find(BitSet visit, int nid) {
+ if( _nid==nid ) return this;
+ if( visit.get(_nid) ) return null;
+ visit.set(_nid);
+ for( Node def : _inputs )
+ if( def!=null ) {
+ Node rez = def._find(visit,nid);
+ if( rez != null ) return rez;
+ }
+ for( Node use : _outputs ) {
+ Node rez = use._find(visit,nid);
+ if( rez != null ) return rez;
+ }
+ return null;
+ }
+}
diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/NotNode.java
new file mode 100644
index 00000000..04e6c75b
--- /dev/null
+++ b/chapter08/src/main/java/com/seaofnodes/simple/node/NotNode.java
@@ -0,0 +1,30 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+
+import java.util.BitSet;
+
+public class NotNode extends Node {
+ public NotNode(Node in) { super(null, in); }
+
+ @Override public String label() { return "Not"; }
+
+ @Override public String glabel() { return "!"; }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ in(1)._print0(sb.append("(!"), visited);
+ return sb.append(")");
+ }
+
+ @Override
+ public Type compute() {
+ if( in(1)._type instanceof TypeInteger i0 )
+ return i0.isConstant() ? TypeInteger.constant(i0.value()==0 ? 1 : 0) : i0;
+ return Type.BOTTOM;
+ }
+
+ @Override
+ public Node idealize() { return null; }
+}
diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/PhiNode.java
new file mode 100644
index 00000000..202dbe37
--- /dev/null
+++ b/chapter08/src/main/java/com/seaofnodes/simple/node/PhiNode.java
@@ -0,0 +1,101 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+
+import java.util.BitSet;
+
+public class PhiNode extends Node {
+
+ final String _label;
+
+ public PhiNode(String label, Node... inputs) { super(inputs); _label = label; }
+
+ @Override public String label() { return "Phi_"+_label; }
+
+ @Override public String glabel() { return "&phi;_"+_label; }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ if( region().inProgress() )
+ sb.append("Z");
+ sb.append("Phi(");
+ for( Node in : _inputs ) {
+ if (in == null) sb.append("____");
+ else in._print0(sb, visited);
+ sb.append(",");
+ }
+ sb.setLength(sb.length()-1);
+ sb.append(")");
+ return sb;
+ }
+
+ RegionNode region() { return (RegionNode)in(0); }
+ @Override public boolean isMultiTail() { return true; }
+
+ @Override
+ public Type compute() {
+ if( !(region() instanceof RegionNode r) || r.inProgress() )
+ return Type.BOTTOM;
+ Type t = Type.TOP;
+ for (int i = 1; i < nIns(); i++)
+ t = t.meet(in(i)._type);
+ return t;
+ }
+
+ @Override
+ public Node idealize() {
+ if( !(region() instanceof RegionNode r ) || r.inProgress() )
+ return null;
+
+ // If we have only a single unique input, become it.
+ Node live = singleUniqueInput();
+ if (live != null)
+ return live;
+
+ // Pull "down" a common data op. One less op in the world. One more
+ // Phi, but Phis do not make code.
+ // Phi(op(A,B),op(Q,R),op(X,Y)) becomes
+ // op(Phi(A,Q,X), Phi(B,R,Y)).
+ Node op = in(1);
+ if( op.nIns()==3 && op.in(0)==null && !op.isCFG() && same_op() ) {
+ Node[] lhss = new Node[nIns()];
+ Node[] rhss = new Node[nIns()];
+ lhss[0] = rhss[0] = in(0); // Set Region
+ for( int i=1; i<nIns(); i++ ) {
+ lhss[i] = in(i).in(1);
+ rhss[i] = in(i).in(2);
+ }
+ Node phi_lhs = new PhiNode(_label,lhss).peephole();
+ Node phi_rhs = new PhiNode(_label,rhss).peephole();
+ return op.copy(phi_lhs,phi_rhs);
+ }
+
+ return null;
+ }
+
+ private boolean same_op() {
+ for( int i=2; i<nIns(); i++ )
+ if( in(1).getClass() != in(i).getClass() )
+ return false;
+ return true;
+ }
+
+ /**
+ * If only single unique input, return it
+ */
+ private Node singleUniqueInput() {
+ Node live = null;
+ for( int i=1; i<nIns(); i++ )
+ if( region().in(i)._type != Type.XCONTROL && in(i) != this )
+ if( live == null || live == in(i) ) live = in(i);
+ else return null;
+ return live;
+ }
+
+ @Override
+ boolean all_cons() {
+ if( !(region() instanceof RegionNode r) || r.inProgress() )
+ return false;
+ return super.all_cons();
+ }
+}
diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/ProjNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/ProjNode.java
new file mode 100644
index 00000000..79ba9d13
--- /dev/null
+++ b/chapter08/src/main/java/com/seaofnodes/simple/node/ProjNode.java
@@ -0,0 +1,49 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeTuple;
+
+import java.util.BitSet;
+
+public class ProjNode extends Node {
+
+ // Which slice of the incoming multi-part value
+ public final int _idx;
+
+ // Debugging label
+ public final String _label;
+
+ public ProjNode(MultiNode ctrl, int idx, String label) {
+ super(ctrl);
+ _idx = idx;
+ _label = label;
+ }
+
+ @Override
+ public String label() { return _label; }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(_label); }
+
+ @Override public boolean isCFG() { return _idx==0 || ctrl() instanceof IfNode; }
+ @Override public boolean isMultiTail() { return in(0).isMultiHead(); }
+
+ public MultiNode ctrl() { return (MultiNode)in(0); }
+
+ @Override
+ public Type compute() {
+ Type t = ctrl()._type;
+ return t instanceof TypeTuple tt ? tt._types[_idx] : Type.BOTTOM;
+ }
+
+ @Override
+ public Node idealize() {
+ if( ctrl()._type instanceof TypeTuple tt ) {
+ if( tt._types[_idx]==Type.XCONTROL )
+ return new ConstantNode(Type.XCONTROL).peephole(); // We are dead
+ if( tt._types[1-_idx]==Type.XCONTROL ) // Only true for IfNodes
+ return ctrl().in(0); // We become our input control
+ }
+ return null;
+ }
+}
diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/RegionNode.java
new file mode 100644
index 00000000..3b793758
--- /dev/null
+++ b/chapter08/src/main/java/com/seaofnodes/simple/node/RegionNode.java
@@ -0,0 +1,81 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.type.Type;
+
+import java.util.Collection;
+import java.util.BitSet;
+
+public class RegionNode extends Node {
+ public RegionNode(Node... inputs) { super(inputs); }
+
+ @Override
+ public String label() { return "Region"; }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return sb.append(label()).append(_nid);
+ }
+
+ @Override public boolean isCFG() { return true; }
+ @Override public boolean isMultiHead() { return true; }
+
+ @Override
+ public Type compute() {
+ Type t = Type.XCONTROL;
+ for (int i = 1; i < nIns(); i++)
+ t = t.meet(in(i)._type);
+ return t;
+ }
+
+ @Override
+ public Node idealize() {
+ int path = findDeadInput();
+ if( path != 0 ) {
+ for( Node phi : _outputs )
+ if( phi instanceof PhiNode )
+ phi.delDef(path);
+ delDef(path);
+
+ // If down to a single input, become that input - but also make all
+ // Phis an identity on *their* single input.
+ if( nIns()==2 ) {
+ for( Node phi : _outputs )
+ if( phi instanceof PhiNode )
+ // Currently does not happen, because no loops
+ throw Utils.TODO();
+ return in(1);
+ }
+ return this;
+ }
+ return null;
+ }
+
+ private int findDeadInput() {
+ for( int i=1; i<nIns(); i++ )
+ if( in(i)._type==Type.XCONTROL )
+ return i;
+ return 0; // All inputs alive
+ }
+
+ // Immediate dominator of Region is a little more complicated.
+ private Node _idom; // Immediate dominator cache
+ @Override Node idom() {
+ if( _idom != null ) return _idom; // Return cached copy
+ if( nIns()!=3 ) return null; // Fails for anything other than 2-inputs
+ // Walk the LHS & RHS idom trees in parallel until they match, or either fails
+ Node lhs = in(1).idom();
+ Node rhs = in(2).idom();
+ while( lhs != rhs ) {
+ if( lhs==null || rhs==null ) return null;
+ var comp = lhs._idepth - rhs._idepth;
+ if( comp >= 0 ) lhs = lhs.idom();
+ if( comp <= 0 ) rhs = rhs.idom();
+ }
+ if( lhs==null ) return null;
+ _idepth = lhs._idepth+1;
+ return (_idom=lhs);
+ }
+
+ public boolean inProgress() { return false; }
+}
diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
new file mode 100644
index 00000000..4dea773a
--- /dev/null
+++ b/chapter08/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
@@ -0,0 +1,46 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.*;
+
+import java.util.BitSet;
+
+/**
+ * The Return node has two inputs. The first input is a control node and the
+ * second is the data node that supplies the return value.
+ * <p>
+ * In this presentation, Return functions as a Stop node, since multiple <code>return</code> statements are not possible.
+ * The Stop node will be introduced in Chapter 6 when we implement <code>if</code> statements.
+ * <p>
+ * The Return's output is the value from the data node.
+ */
+public class ReturnNode extends Node {
+
+ public ReturnNode(Node ctrl, Node data) {
+ super(ctrl, data);
+ }
+
+ public Node ctrl() { return in(0); }
+ public Node expr() { return in(1); }
+
+ @Override
+ public String label() { return "Return"; }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return expr()._print0(sb.append("return "), visited).append(";");
+ }
+
+ @Override public boolean isCFG() { return true; }
+
+ @Override
+ public Type compute() {
+ return new TypeTuple(ctrl()._type,expr()._type);
+ }
+
+ @Override
+ public Node idealize() {
+ if( ctrl()._type==Type.XCONTROL )
+ return ctrl();
+ return null;
+ }
+}
diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
new file mode 100644
index 00000000..e486a390
--- /dev/null
+++ b/chapter08/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
@@ -0,0 +1,193 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.Type;
+
+import java.util.*;
+
+/**
+ * The Scope node is purely a parser helper - it tracks names to nodes with a
+ * stack of scopes.
+ */
+public class ScopeNode extends Node {
+
+ /**
+ * The control is a name that binds to the currently active control
+ * node in the graph
+ */
+ public static final String CTRL = "$ctrl";
+ public static final String ARG0 = "arg";
+
+ /**
+ * Names for every input edge
+ */
+ public final Stack<HashMap<String, Integer>> _scopes;
+
+
+ // A new ScopeNode
+ public ScopeNode() {
+ _scopes = new Stack<>();
+ _type = Type.BOTTOM;
+ }
+
+
+ @Override public String label() { return "Scope"; }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ sb.append("Scope[ ");
+ String[] names = reverse_names();
+ for( int j=0; j<nIns(); j++ ) {
+ sb.append(names[j]).append(":");
+ Node n = in(j);
+ while( n instanceof ScopeNode loop ) {
+ sb.append("Lazy_");
+ n = loop.in(j);
+ }
+ n._print0(sb, visited).append(" ");
+ }
+ sb.setLength(sb.length()-1);
+ return sb.append("]");
+ }
+
+ // Expensive...
+ public String[] reverse_names() {
+ String[] names = new String[nIns()];
+ for( HashMap<String,Integer> syms : _scopes )
+ for( String name : syms.keySet() )
+ names[syms.get(name)] = name;
+ return names;
+ }
+
+ @Override public Type compute() { return Type.BOTTOM; }
+
+ @Override public Node idealize() { return null; }
+
+ public void push() { _scopes.push(new HashMap<>()); }
+ public void pop() { pop_n(_scopes.pop().size()); }
+
+ // Create a new name in the current scope
+ public Node define( String name, Node n ) {
+ HashMap<String,Integer> syms = _scopes.lastElement();
+ if( syms.put(name,nIns()) != null )
+ return null; // Double define
+ return add_def(n);
+ }
+
+ // Lookup a name. It is recursive to support lazy Phis on loops.
+ public Node lookup(String name) { return update(name,null,_scopes.size()-1); }
+ // If the name is present in any scope, then redefine else null
+ public Node update(String name, Node n) { return update(name,n,_scopes.size()-1); }
+ // Both recursive lookup and update. If a Lazy Phi is found at any level,
+ // a concrete Phi is inserted at the loop head and also in the current
+ // scope. The Lazy Phi does a recursive lookup for its input which may
+ // itself be Lazy.
+ private Node update( String name, Node n, int i ) {
+ if( i<0 ) return null; // Missed in all scopes, not found
+ var syms = _scopes.get(i);
+ var idx = syms.get(name);
+ if( idx == null ) return update(name,n,i-1); // Missed in this scope, recursively look
+ Node old = in(idx);
+ if( old instanceof ScopeNode loop ) {
+ // Lazy Phi!
+ old = loop.in(idx) instanceof PhiNode phi && loop.ctrl()==phi.region()
+ // Loop already has a real Phi, use it
+ ? loop.in(idx)
+ // Set real Phi in the loop head
+ // The phi takes its one input (no backedge yet) from a recursive
+ // lookup, which might have insert a Phi in every loop nest.
+ : loop.set_def(idx,new PhiNode(name,loop.ctrl(),loop.update(name,null,i),null).peephole());
+ set_def(idx,old);
+ }
+ return n==null ? old : set_def(idx,n); // Not lazy, so this is the answer
+ }
+
+ public Node ctrl() { return in(0); }
+
+ /**
+ * The ctrl of a ScopeNode is always bound to the currently active
+ * control node in the graph, via a special name '$ctrl' that is not
+ * a valid identifier in the language grammar and hence cannot be
+ * referenced in Simple code.
+ *
+ * @param n The node to be bound to '$ctrl'
+ *
+ * @return Node that was bound
+ */
+ public Node ctrl(Node n) { return set_def(0,n); }
+
+ /**
+ * 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).
+ * <p>
+ * If the {@code loop} flag is set, the edges are filled in as the original
+ * Scope, as a indication of Lazy Phis at loop heads. The goal here is to
+ * not make Phis at loop heads for variables which are never touched in the
+ * loop body.
+ * <p>
+ * The new Scope is a full-fledged Node with proper use<->def edges.
+ */
+ public ScopeNode dup() { return dup(false); }
+ public ScopeNode dup(boolean loop) {
+ ScopeNode dup = new ScopeNode();
+ // 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
+ for( HashMap<String,Integer> syms : _scopes )
+ dup._scopes.push(new HashMap<>(syms));
+ String[] reverse = Parser.LAZY ? null : reverse_names();
+ dup.add_def(ctrl()); // Control input is just copied
+ for( int i=1; i<nIns(); i++ ) {
+ dup.add_def(loop ? (Parser.LAZY ? this : new PhiNode(reverse[i],ctrl(),in(i),null).peephole()) : in(i));
+ if( loop && !Parser.LAZY ) set_def(i,dup.in(i));
+ }
+ 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 node representing the merge point
+ */
+ public Node mergeScopes(ScopeNode that) {
+ RegionNode r = (RegionNode) ctrl(new RegionNode(null,ctrl(), that.ctrl()).keep());
+ String[] ns = reverse_names();
+ // Note that we skip i==0, which is bound to '$ctrl'
+ for (int i = 1; i < nIns(); i++) {
+ if( in(i) != that.in(i) ) { // No need for redundant Phis
+ Node n1 = Parser.LAZY ? this.lookup(ns[i]) : in(i); // Must do a lookup to set lazy Phis
+ Node n2 = Parser.LAZY ? that.lookup(ns[i]) : that.in(i); // Must do a lookup to set lazy Phis
+ set_def(i, new PhiNode(ns[i], r, n1, n2).peephole());
+ }
+ }
+ that.kill(); // Kill merged scope
+ return r.unkeep().peephole();
+ }
+
+ // Merge the backedge scope into this loop head scope
+ public void endLoop(ScopeNode back, ScopeNode exit ) {
+ Node ctrl = ctrl();
+ assert ctrl instanceof LoopNode loop && loop.inProgress();
+ ctrl.set_def(2,back.ctrl());
+ for( int i=1; i<nIns(); i++ ) {
+ if( back.in(i) != this ) {
+ PhiNode phi = (PhiNode)in(i);
+ assert phi.region()==ctrl && phi.in(2)==null;
+ phi.set_def(2,back.in(i));
+ // Do an eager useless-phi removal
+ Node in = phi.peephole();
+ if( in != phi )
+ phi.subsume(in);
+ }
+ if( exit.in(i) == this ) // Replace a lazy-phi on the exit path also
+ exit.set_def(i,in(i));
+ }
+ back.kill(); // Loop backedge is dead
+ }
+}
diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/StartNode.java
new file mode 100644
index 00000000..19d03e0f
--- /dev/null
+++ b/chapter08/src/main/java/com/seaofnodes/simple/node/StartNode.java
@@ -0,0 +1,46 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeTuple;
+
+import java.util.BitSet;
+
+/**
+ * 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 MultiNode {
+
+ final TypeTuple _args;
+
+ public StartNode(Type[] args) {
+ super();
+ _args = new TypeTuple(args);
+ _type = _args;
+ }
+
+ @Override
+ public String label() { return "Start"; }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return sb.append(label());
+ }
+
+ @Override public boolean isCFG() { return true; }
+ @Override public boolean isMultiHead() { return true; }
+
+ @Override
+ public TypeTuple compute() { return _args; }
+
+ @Override
+ public Node idealize() { return null; }
+
+ // No immediate dominator, and idepth==0
+ @Override
+ Node idom() { return null; }
+
+}
diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/StopNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/StopNode.java
new file mode 100644
index 00000000..c4375771
--- /dev/null
+++ b/chapter08/src/main/java/com/seaofnodes/simple/node/StopNode.java
@@ -0,0 +1,53 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+
+import java.util.BitSet;
+
+public class StopNode extends Node {
+ public StopNode(Node... inputs) {
+ super(inputs);
+ }
+
+ @Override
+ public String label() {
+ return "Stop";
+ }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ if( ret()!=null ) return ret()._print0(sb, visited);
+ sb.append("Stop[ ");
+ for( Node ret : _inputs )
+ ret._print0(sb, visited).append(" ");
+ return sb.append("]");
+ }
+
+ @Override public boolean isCFG() { return true; }
+
+ // If a single Return, return it.
+ // Otherwise, null because ambiguous.
+ public ReturnNode ret() {
+ return nIns()==1 ? (ReturnNode)in(0) : null;
+ }
+
+ @Override
+ public Type compute() {
+ return Type.BOTTOM;
+ }
+
+ @Override
+ public Node idealize() {
+ int len = nIns();
+ for( int i=0; i<nIns(); i++ )
+ if( in(i)._type==Type.XCONTROL )
+ delDef(i--);
+ if( len != nIns() ) return this;
+ return null;
+ }
+
+ public Node addReturn(Node node) {
+ return add_def(node);
+ }
+
+}
diff --git a/chapter08/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter08/src/main/java/com/seaofnodes/simple/node/SubNode.java
new file mode 100644
index 00000000..1484ab8a
--- /dev/null
+++ b/chapter08/src/main/java/com/seaofnodes/simple/node/SubNode.java
@@ -0,0 +1,43 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+
+import java.util.BitSet;
+
+public class SubNode extends Node {
+ public SubNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
+
+ @Override public String label() { return "Sub"; }
+
+ @Override public String glabel() { return "-"; }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ in(1)._print0(sb.append("("), visited);
+ in(2)._print0(sb.append("-"), visited);
+ return sb.append(")");
+ }
+
+ @Override
+ public Type compute() {
+ if (in(1)._type instanceof TypeInteger i0 &&
+ in(2)._type instanceof TypeInteger i1) {
+ if (i0.isConstant() && i1.isConstant())
+ return TypeInteger.constant(i0.value()-i1.value());
+ return i0.meet(i1);
+ }
+ return Type.BOTTOM;
+ }
+
+ @Override
+ public Node idealize() {
+ // Sub of same is 0
+ if( in(1)==in(2) )
+ return new ConstantNode(TypeInteger.constant(0));
+
+ return null;
+ }
+
+ @Override Node copy(Node lhs, Node rhs) { return new SubNode(lhs,rhs); }
+}
diff --git a/chapter08/src/main/java/com/seaofnodes/simple/type/Type.java b/chapter08/src/main/java/com/seaofnodes/simple/type/Type.java
new file mode 100644
index 00000000..f88403a5
--- /dev/null
+++ b/chapter08/src/main/java/com/seaofnodes/simple/type/Type.java
@@ -0,0 +1,77 @@
+package com.seaofnodes.simple.type;
+
+
+/**
+ * These types are part of a Monotone Analysis Framework,
+ * @see <a href="https://www.cse.psu.edu/~gxt29/teaching/cse597s21/slides/08monotoneFramework.pdf">see for example this set of slides</a>.
+ * <p>
+ * The types form a lattice; @see <a href="https://en.wikipedia.org/wiki/Lattice_(order)">a symmetric complete bounded (ranked) lattice.</a>
+ * <p>
+ * This wild lattice theory will be needed later to allow us to easily beef up
+ * the analysis and optimization of the Simple compiler... but we don't need it
+ * now, just know that it is coming along in a later Chapter.
+ * <p>g
+ * One of the fun things here is that while the theory is deep and subtle, the
+ * actual implementation is darn near trivial and is generally really obvious
+ * what we're doing with it. Right now, it's just simple integer math to do
+ * simple constant folding e.g. 1+2 == 3 stuff.
+ */
+
+public class Type {
+
+ // ----------------------------------------------------------
+ // Simple types are implemented fully here. "Simple" means: the code and
+ // type hierarchy are simple, not that the Type is conceptually simple.
+ static final byte TBOT = 0; // Bottom (ALL)
+ static final byte TTOP = 1; // Top (ANY)
+ static final byte TCTRL = 2; // Ctrl flow bottom
+ static final byte TXCTRL = 3; // Ctrl flow top (mini-lattice: any-xctrl-ctrl-all)
+ static final byte TSIMPLE = 4; // End of the Simple Types
+ static final byte TINT = 5; // All Integers; see TypeInteger
+ static final byte TTUPLE = 6; // Tuples; finite collections of unrelated Types, kept in parallel
+
+ public final byte _type;
+
+ public boolean is_simple() { return _type < TSIMPLE; }
+ private static final String[] STRS = new String[]{"Bot","Top","Ctrl","~Ctrl"};
+ protected Type(byte type) { _type = type; }
+
+ public static final Type BOTTOM = new Type( TBOT ); // ALL
+ public static final Type TOP = new Type( TTOP ); // ANY
+ public static final Type CONTROL = new Type( TCTRL ); // Ctrl
+ public static final Type XCONTROL = new Type( TXCTRL ); // ~Ctrl
+
+ public boolean isConstant() { return _type == TTOP || _type == TXCTRL; }
+
+ public StringBuilder _print(StringBuilder sb) {return is_simple() ? sb.append(STRS[_type]) : sb;}
+
+ public final Type meet(Type t) {
+ // Shortcut for the self case
+ if( t == this ) return this;
+ // Same-type is always safe in the subclasses
+ if( _type==t._type ) return xmeet(t);
+ // Reverse; xmeet 2nd arg is never "is_simple" and never equal to "this".
+ if( is_simple() ) return this.xmeet(t );
+ if( t.is_simple() ) return t .xmeet(this);
+ return BOTTOM; // Mixing 2 unrelated types
+ }
+
+ // Compute meet right now. Overridden in subclasses.
+ // Handle cases where 'this.is_simple()' and unequal to 't'.
+ // Subclassed xmeet calls can assert that '!t.is_simple()'.
+ protected Type xmeet(Type t) {
+ assert is_simple(); // Should be overridden in subclass
+ // ANY meet anything is thing; thing meet ALL is ALL
+ if( _type==TBOT || t._type==TTOP ) return this;
+ if( _type==TTOP || t._type==TBOT ) return t;
+ // 'this' is {TCTRL,TXCTRL}
+ if( !t.is_simple() ) return BOTTOM;
+ // 't' is {TCTRL,TXCTRL}
+ return _type==TCTRL || t._type==TCTRL ? CONTROL : XCONTROL;
+ }
+
+ @Override
+ public final String toString() {
+ return _print(new StringBuilder()).toString();
+ }
+}
diff --git a/chapter08/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/chapter08/src/main/java/com/seaofnodes/simple/type/TypeInteger.java
new file mode 100644
index 00000000..93f5eae3
--- /dev/null
+++ b/chapter08/src/main/java/com/seaofnodes/simple/type/TypeInteger.java
@@ -0,0 +1,63 @@
+package com.seaofnodes.simple.type;
+
+/**
+ * Integer Type
+ */
+public class TypeInteger extends Type {
+
+ public final static TypeInteger TOP = new TypeInteger(false, 0);
+ public final static TypeInteger BOT = new TypeInteger(false, 1);
+ public final static TypeInteger ZERO= new TypeInteger(true, 0);
+
+ private final boolean _is_con;
+
+ /**
+ * The constant value or
+ * if not constant then 0=bottom, 1=top.
+ */
+ private final long _con;
+
+ public TypeInteger(boolean is_con, long con) {
+ super(TINT);
+ _is_con = is_con;
+ _con = con;
+ }
+
+ public static TypeInteger constant(long con) { return new TypeInteger(true, con); }
+
+ public boolean isTop() { return !_is_con && _con==0; }
+ public boolean isBot() { return !_is_con && _con==1; }
+
+ @Override
+ public StringBuilder _print(StringBuilder sb) {
+ if (isTop()) return sb.append("IntTop");
+ if (isBot()) return sb.append("IntBot");
+ return sb.append(_con);
+ }
+
+ @Override
+ public boolean isConstant() { return _is_con; }
+
+ public long value() { return _con; }
+
+ @Override
+ public Type xmeet(Type other) {
+ if( this==other ) return this;
+ if (!(other instanceof TypeInteger i)) return super.meet(other);
+ // BOT wins
+ if ( isBot() ) return this;
+ if ( i.isBot() ) return i ;
+ // TOP loses
+ if ( i.isTop() ) return this;
+ if ( isTop() ) return i ;
+ assert isConstant() && i.isConstant();
+ return _con==i._con ? this : TypeInteger.BOT;
+ }
+
+ @Override
+ public boolean equals( Object o ) {
+ if( o==this ) return true;
+ if( !(o instanceof TypeInteger i) ) return false;
+ return _con==i._con && _is_con==i._is_con;
+ }
+}
diff --git a/chapter08/src/main/java/com/seaofnodes/simple/type/TypeTuple.java b/chapter08/src/main/java/com/seaofnodes/simple/type/TypeTuple.java
new file mode 100644
index 00000000..f8c3b3c8
--- /dev/null
+++ b/chapter08/src/main/java/com/seaofnodes/simple/type/TypeTuple.java
@@ -0,0 +1,32 @@
+package com.seaofnodes.simple.type;
+
+public class TypeTuple extends Type {
+
+ public final Type[] _types;
+
+ public TypeTuple(Type... _types) {
+ super(TTUPLE);
+ this._types = _types;
+ }
+
+ @Override
+ public Type xmeet(Type other) {
+ throw new UnsupportedOperationException("Meet on Tuple Type not yet implemented");
+ }
+
+ @Override
+ public StringBuilder _print(StringBuilder sb) {
+ sb.append("[");
+ for( Type t : _types )
+ t._print(sb).append(",");
+ sb.setLength(sb.length()-1);
+ sb.append("]");
+ return sb;
+ }
+
+ public static final TypeTuple IF_BOTH = new TypeTuple(new Type[]{Type.CONTROL, Type.CONTROL});
+ public static final TypeTuple IF_NEITHER = new TypeTuple(new Type[]{Type.XCONTROL,Type.XCONTROL});
+ public static final TypeTuple IF_TRUE = new TypeTuple(new Type[]{Type.CONTROL, Type.XCONTROL});
+ public static final TypeTuple IF_FALSE = new TypeTuple(new Type[]{Type.XCONTROL,Type.CONTROL});
+
+}
diff --git a/chapter08/src/test/java/com/seaofnodes/simple/Chapter08Test.java b/chapter08/src/test/java/com/seaofnodes/simple/Chapter08Test.java
new file mode 100644
index 00000000..00178a64
--- /dev/null
+++ b/chapter08/src/test/java/com/seaofnodes/simple/Chapter08Test.java
@@ -0,0 +1,1028 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import org.junit.Test;
+
+import java.util.ArrayList;
+
+import static org.junit.Assert.*;
+
+public class Chapter08Test {
+
+ @Test
+ public void testChapter7Example() {
+ Parser parser = new Parser(
+ """
+ while(arg < 10) {
+ arg = arg + 1;
+ #showGraph;
+ }
+ return arg;
+ """);
+ Node._disablePeephole = true;
+ StopNode stop = parser.parse(true);
+ assertEquals("return Phi(Loop6,arg,(Phi_arg+1));", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof ProjNode);
+ Node._disablePeephole = false;
+ }
+
+ @Test
+ public void testChapter7Regression() {
+ Parser parser = new Parser(
+
+"""
+int a = 1;
+if(arg){}else{
+ while(a < 10) {
+ a = a + 1;
+ }
+}
+return a;
+""");
+ StopNode stop = parser.parse(true);
+ assertEquals("return Phi(Region23,1,Phi(Loop11,1,(Phi_a+1)));", stop.toString());
+ }
+
+ @Test
+ public void testChapter7WhileNested() {
+ Parser parser = new Parser(
+
+"""
+int sum = 0;
+int i = 0;
+while(i < arg) {
+ i = i + 1;
+ int j = 0;
+ while( j < arg ) {
+ sum = sum + j;
+ j = j + 1;
+ }
+}
+return sum;
+""");
+ StopNode stop = parser.parse(true);
+ assertEquals("return Phi(Loop8,0,Phi(Loop21,Phi_sum,(Phi(Loop,0,(Phi_j+1))+Phi_sum)));", stop.toString());
+ System.out.println(IRPrinter.prettyPrint(stop,99));
+ }
+
+ @Test
+ public void testChapter7WhileScope() {
+ Parser parser = new Parser(
+"""
+int a = 1;
+int b = 2;
+while(a < 10) {
+ if (a == 2) a = 3;
+ else b = 4;
+}
+return b;
+""");
+ Node._disablePeephole = true;
+ StopNode stop = parser.parse(true);
+ assertEquals("return Phi(Loop8,2,Phi(Region27,Phi_b,4));", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof ProjNode);
+ Node._disablePeephole = false;
+ System.out.println(IRPrinter.prettyPrint(stop,99));
+ }
+
+ @Test
+ public void testChapter7WhileNestedIfAndInc() {
+ Parser parser = new Parser(
+"""
+int a = 1;
+int b = 2;
+while(a < 10) {
+ if (a == 2) a = 3;
+ else b = 4;
+ b = b + 1;
+ a = a + 1;
+}
+return b;
+""");
+ StopNode stop = parser.parse(true);
+ assertEquals("return Phi(Loop8,2,(Phi(Region27,Phi_b,4)+1));", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof ProjNode);
+ System.out.println(IRPrinter.prettyPrint(stop,99));
+ }
+
+
+ @Test
+ public void testChapter7While() {
+ Parser parser = new Parser(
+"""
+int a = 1;
+while(a < 10) {
+ a = a + 1;
+ a = a + 2;
+}
+return a;
+""");
+ Node._disablePeephole = true;
+ StopNode stop = parser.parse(true);
+ assertEquals("return Phi(Loop7,1,((Phi_a+1)+2));", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof ProjNode);
+ Node._disablePeephole = false;
+ }
+
+ @Test
+ public void testChapter7WhilePeep() {
+ Parser parser = new Parser(
+"""
+int a = 1;
+while(a < 10) {
+ a = a + 1;
+ a = a + 2;
+}
+return a;
+""");
+ StopNode stop = parser.parse(true);
+ assertEquals("return Phi(Loop7,1,(Phi_a+3));", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof ProjNode);
+ }
+
+ @Test
+ public void testChapter7While2() {
+ Parser parser = new Parser(
+"""
+int a = 1;
+while(arg) a = 2;
+return a;
+""");
+ Node._disablePeephole = true;
+ StopNode stop = parser.parse(true);
+ assertEquals("return Phi(Loop7,1,2);", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof ProjNode);
+ Node._disablePeephole = false;
+ System.out.println(IRPrinter.prettyPrint(stop,99));
+ }
+
+ @Test
+ public void testChapter7While2Peep() {
+ Parser parser = new Parser(
+"""
+int a = 1;
+while(arg) a = 2;
+return a;
+""");
+ StopNode stop = parser.parse(false);
+ assertEquals("return Phi(Loop7,1,2);", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof ProjNode);
+ System.out.println(IRPrinter.prettyPrint(stop, 99));
+ }
+
+ @Test
+ public void testChapter7While3() {
+ Parser parser = new Parser(
+"""
+int a = 1;
+while(a < 10) {
+ int b = a + 1;
+ a = b + 2;
+}
+return a;
+""");
+ Node._disablePeephole = true;
+ StopNode stop = parser.parse(true);
+ assertEquals("return Phi(Loop7,1,((Phi_a+1)+2));", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof ProjNode);
+ Node._disablePeephole = false;
+ }
+
+ @Test
+ public void testChapter7While3Peep() {
+ Parser parser = new Parser(
+"""
+int a = 1;
+while(a < 10) {
+ int b = a + 1;
+ a = b + 2;
+}
+return a;
+""");
+ StopNode stop = parser.parse(true);
+ assertEquals("return Phi(Loop7,1,(Phi_a+3));", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof ProjNode);
+ }
+
+ @Test
+ public void testChapter7While4() {
+ Parser parser = new Parser(
+"""
+int a = 1;
+int b = 2;
+while(a < 10) {
+ int b = a + 1;
+ a = b + 2;
+}
+return a;
+""");
+ Node._disablePeephole = true;
+ StopNode stop = parser.parse(true);
+ assertEquals("return Phi(Loop8,1,((Phi_a+1)+2));", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof ProjNode);
+ Node._disablePeephole = false;
+ }
+
+ @Test
+ public void testChapter7While4Peep() {
+ Parser parser = new Parser(
+"""
+int a = 1;
+int b = 2;
+while(a < 10) {
+ int b = a + 1;
+ a = b + 2;
+}
+return a;
+""");
+ StopNode stop = parser.parse(true);
+ assertEquals("return Phi(Loop8,1,(Phi_a+3));", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof ProjNode);
+ }
+
+ @Test
+ public void testChapter6PeepholeReturn() {
+ Parser parser = new Parser(
+"""
+if( true ) return 2;
+return 1;
+""");
+ StopNode stop = parser.parse(true);
+ assertEquals("return 2;", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof ProjNode);
+ }
+
+ @Test
+ public void testChapter6PeepholeRotate() {
+ Parser parser = new Parser(
+"""
+int a = 1;
+if (arg)
+ a = 2;
+return (arg < a) < 3;
+""");
+ StopNode stop = parser.parse(false);
+ assertEquals("return ((arg<Phi(Region12,2,1))<3);", stop.toString());
+ }
+
+ @Test
+ public void testChapter6PeepholeCFG() {
+ Parser parser = new Parser(
+"""
+int a=1;
+if( true )
+ a=2;
+else
+ a=3;
+return a;
+""");
+ StopNode stop = parser.parse(true);
+ assertEquals("return 2;", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof ProjNode);
+ }
+
+ @Test
+ public void testChapter6IfIf() {
+ Parser parser = new Parser(
+"""
+int a=1;
+if( arg!=1 )
+ a=2;
+else
+ a=3;
+int b=4;
+if( a==2 )
+ b=42;
+else
+ b=5;
+return b;""", TypeInteger.BOT);
+ StopNode stop = parser.parse(true);
+ assertEquals("return Phi(Region32,42,5);", stop.toString());
+ }
+
+ @Test
+ public void testChapter6IfArgIf() {
+ Parser parser = new Parser(
+"""
+int a=1;
+if( 1==1 )
+ a=2;
+else
+ a=3;
+int b=4;
+if( arg==2 )
+ b=a;
+else
+ b=5;
+return b;""", TypeInteger.BOT);
+ StopNode stop = parser.parse(true);
+ assertEquals("return Phi(Region28,2,5);", stop.toString());
+ }
+
+ @Test
+ public void testChapter6Merge3With2() {
+ Parser parser = new Parser(
+"""
+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;""", TypeInteger.constant(2));
+ StopNode stop = parser.parse();
+ assertEquals("return 5;", stop.toString());
+ }
+
+ @Test
+ public void testChapter6Merge3With1() {
+ Parser parser = new Parser(
+"""
+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;
+""", TypeInteger.constant(1));
+ StopNode stop = parser.parse(true);
+ assertEquals("return 3;", stop.toString());
+ }
+
+ @Test
+ public void testChapter6Merge3Peephole() {
+ Parser parser = new Parser(
+"""
+int a=1;
+if( arg==1 )
+ if( 1==2 )
+ a=2;
+ else
+ a=3;
+else if( arg==3 )
+ a=4;
+else
+ a=5;
+return a;
+""", TypeInteger.BOT);
+ StopNode stop = parser.parse(true);
+ assertEquals("return Phi(Region36,3,Phi(Region34,4,5));", stop.toString());
+ }
+
+ @Test
+ public void testChapter6Merge3Peephole1() {
+ Parser parser = new Parser(
+"""
+int a=1;
+if( arg==1 )
+ if( 1==2 )
+ a=2;
+ else
+ a=3;
+else if( arg==3 )
+ a=4;
+else
+ a=5;
+return a;
+""", TypeInteger.constant(1));
+ StopNode stop = parser.parse(true);
+ assertEquals("return 3;", stop.toString());
+ }
+
+ @Test
+ public void testChapter6Merge3Peephole3() {
+ Parser parser = new Parser(
+"""
+int a=1;
+if( arg==1 )
+ if( 1==2 )
+ a=2;
+ else
+ a=3;
+else if( arg==3 )
+ a=4;
+else
+ a=5;
+return a;
+""", TypeInteger.constant(3));
+ StopNode stop = parser.parse(true);
+ assertEquals("return 4;", stop.toString());
+ }
+
+ @Test
+ public void testChapter6Demo1NonConst() {
+ Parser parser = new Parser("""
+int a = 0;
+int b = 1;
+if( arg ) {
+ a = 2;
+ if( arg ) { b = 2; }
+ else b = 3;
+}
+return a+b;
+""");
+ StopNode ret = parser.parse(true);
+ assertEquals("return Phi(Region22,4,1);", ret.toString());
+ }
+
+
+ @Test
+ public void testChapter6Demo1True() {
+ Parser parser = new Parser("""
+int a = 0;
+int b = 1;
+if( arg ) {
+ a = 2;
+ if( arg ) { b = 2; }
+ else b = 3;
+}
+return a+b;
+""", TypeInteger.constant(1));
+ StopNode ret = parser.parse(true);
+ assertEquals("return 4;", ret.toString());
+ }
+
+ @Test
+ public void testChapter6Demo1False() {
+ Parser parser = new Parser("""
+int a = 0;
+int b = 1;
+if( arg ) {
+ a = 2;
+ if( arg ) { b = 2; }
+ else b = 3;
+}
+return a+b;
+""", TypeInteger.constant(0));
+ StopNode ret = parser.parse(true);
+ assertEquals("return 1;", ret.toString());
+ }
+
+ @Test
+ public void testChapter6Demo2NonConst() {
+ Parser parser = new Parser("""
+int a = 0;
+int b = 1;
+int c = 0;
+if( arg ) {
+ a = 1;
+ if( arg==2 ) { c=2; } else { c=3; }
+ if( arg ) { b = 2; }
+ else b = 3;
+}
+return a+b+c;
+""");
+ StopNode ret = parser.parse(true);
+ assertEquals("return (Phi(Region33,Phi(Region22,2,3),0)+Phi(Region,3,1));", ret.toString());
+ }
+
+
+ @Test
+ public void testChapter6Demo2True() {
+ Parser parser = new Parser("""
+int a = 0;
+int b = 1;
+int c = 0;
+if( arg ) {
+ a = 1;
+ if( arg==2 ) { c=2; } else { c=3; }
+ if( arg ) { b = 2; }
+ else b = 3;
+}
+return a+b+c;
+""", TypeInteger.constant(1));
+ StopNode ret = parser.parse(true);
+ assertEquals("return 6;", ret.toString());
+ }
+
+ @Test
+ public void testChapter6Demo2arg2() {
+ Parser parser = new Parser("""
+int a = 0;
+int b = 1;
+int c = 0;
+if( arg ) {
+ a = 1;
+ if( arg==2 ) { c=2; } else { c=3; }
+ if( arg ) { b = 2; }
+ else b = 3;
+}
+return a+b+c;
+""", TypeInteger.constant(2));
+ StopNode ret = parser.parse(true);
+ assertEquals("return 5;", ret.toString());
+ }
+
+
+
+ @Test
+ public void testChapter5IfStmt() {
+ Parser parser = new Parser(
+"""
+int a = 1;
+if (arg == 1)
+ a = arg+2;
+else {
+ a = arg-3;
+ #showGraph;
+}
+#showGraph;
+return a;""");
+ StopNode ret = parser.parse(true);
+ assertEquals("return Phi(Region17,(arg+2),(arg-3));", ret.toString());
+ }
+
+ @Test
+ public void testChapter5Test() {
+ Parser parser = new Parser(
+"""
+int c = 3;
+int b = 2;
+if (arg == 1) {
+ b = 3;
+ c = 4;
+}
+return c;""", TypeInteger.BOT);
+ StopNode ret = parser.parse(true);
+ assertEquals("return Phi(Region16,4,3);", ret.toString());
+ }
+
+ @Test
+ public void testChapter5Return2() {
+ Parser parser = new Parser(
+"""
+if( arg==1 )
+ return 3;
+else
+ return 4;
+#showGraph;""", TypeInteger.BOT);
+ StopNode stop = parser.parse();
+ assertEquals("Stop[ return 3; return 4; ]", stop.toString());
+ }
+
+ @Test
+ public void testChapter5IfMergeB() {
+ Parser parser = new Parser(
+"""
+int a=arg+1;
+int b=0;
+if( arg==1 )
+ b=a;
+else
+ b=a+1;
+return a+b;""");
+ StopNode ret = parser.parse(true);
+ assertEquals("return ((arg*2)+Phi(Region20,2,3));", ret.toString());
+ }
+
+ @Test
+ public void testChapter5IfMerge2() {
+ Parser parser = new Parser(
+"""
+int a=arg+1;
+int b=arg+2;
+if( arg==1 )
+ b=b+a;
+else
+ a=b+1;
+return a+b;""");
+ StopNode ret = parser.parse(true);
+ assertEquals("return ((Phi(Region31,(arg*2),arg)+arg)+Phi(Region,4,5));", ret.toString());
+ }
+
+ @Test
+ public void testChapter5Merge3() {
+ Parser parser = new Parser(
+"""
+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;""", TypeInteger.BOT);
+ StopNode stop = parser.parse();
+ assertEquals("return Phi(Region33,Phi(Region21,2,3),Phi(Region31,4,5));", stop.toString());
+ }
+
+ @Test
+ public void testChapter5Merge4() {
+ Parser parser = new Parser(
+"""
+int a=0;
+int b=0;
+if( arg )
+ a=1;
+if( arg==0 )
+ b=2;
+return arg+a+b;
+#showGraph;""", TypeInteger.BOT);
+ StopNode stop = parser.parse();
+ assertEquals("return ((arg+Phi(Region13,1,0))+Phi(Region22,2,0));", stop.toString());
+ }
+
+ @Test
+ public void testChapter5Merge5() {
+ Parser parser = new Parser(
+"""
+int a=arg==2;
+if( arg==1 )
+{
+ a=arg==3;
+}
+return a;""");
+ StopNode ret = parser.parse(true);
+ assertEquals("return (arg==Phi(Region16,3,2));", ret.toString());
+ }
+
+ @Test
+ public void testChapter5True() {
+ StopNode stop = new Parser("return true;").parse();
+ assertEquals("return 1;",stop.toString());
+ }
+
+ @Test
+ public void testChapter5HalfDef() {
+ try {
+ new Parser("if( arg==1 ) int b=2; return b;").parse();
+ fail();
+ } catch( RuntimeException e ) {
+ assertEquals("Cannot define a new name on one arm of an if",e.getMessage());
+ }
+ }
+
+ @Test
+ public void testChapter5HalfDef2() {
+ try {
+ new Parser("if( arg==1 ) { int b=2; } else { int b=3; } return b;").parse();
+ fail();
+ } catch( RuntimeException e ) {
+ assertEquals("Undefined name 'b'",e.getMessage());
+ }
+ }
+
+ @Test
+ public void testChapter5BadNum() {
+ try {
+ new Parser("return 1-;").parse();
+ fail();
+ } catch( RuntimeException e ) {
+ assertEquals("Syntax error, expected an identifier or expression: ;",e.getMessage());
+ }
+ }
+
+ @Test
+ public void testChapter5Keyword1() {
+ try {
+ new Parser("int true=0; return true;").parse();
+ fail();
+ } catch( RuntimeException e ) {
+ assertEquals("Expected an identifier, found 'true'",e.getMessage());
+ }
+ }
+
+ @Test
+ public void testChapter5Keyword2() {
+ try {
+ new Parser("int else=arg; if(else) else=2; else else=1; return else;").parse();
+ fail();
+ } catch( RuntimeException e ) {
+ assertEquals("Expected an identifier, found 'else'",e.getMessage());
+ }
+ }
+
+ @Test
+ public void testChapter5Keyword3() {
+ try {
+ new Parser("int a=1; ififif(arg)inta=2;return a;").parse();
+ fail();
+ } catch( RuntimeException e ) {
+ assertEquals("Syntax error, expected =: (",e.getMessage());
+ }
+ }
+
+ @Test
+ public void testChapter4Peephole() {
+ Parser parser = new Parser("return 1+arg+2; #showGraph;");
+ StopNode ret = parser.parse();
+ assertEquals("return (arg+3);", ret.print());
+ }
+
+ @Test
+ public void testChapter4Peephole2() {
+ Parser parser = new Parser("return (1+arg)+2;");
+ StopNode ret = parser.parse();
+ assertEquals("return (arg+3);", ret.print());
+ }
+
+ @Test
+ public void testChapter4Add0() {
+ Parser parser = new Parser("return 0+arg;");
+ StopNode ret = parser.parse();
+ assertEquals("return arg;", ret.print());
+ }
+
+ @Test
+ public void testChapter4AddAddMul() {
+ Parser parser = new Parser("return arg+0+arg;");
+ StopNode ret = parser.parse();
+ assertEquals("return (arg*2);", ret.print());
+ }
+
+ @Test
+ public void testChapter4Peephole3() {
+ Parser parser = new Parser("return 1+arg+2+arg+3; #showGraph;");
+ StopNode ret = parser.parse();
+ assertEquals("return ((arg*2)+6);", ret.print());
+ }
+
+ @Test
+ public void testChapter4Mul1() {
+ Parser parser = new Parser("return 1*arg;");
+ StopNode ret = parser.parse();
+ assertEquals("return arg;", ret.print());
+ }
+
+ @Test
+ public void testChapter4VarArg() {
+ Parser parser = new Parser("return arg; #showGraph;");
+ StopNode stop = parser.parse();
+ ReturnNode ret = stop.ret();
+ assertTrue(ret.in(0) instanceof ProjNode);
+ assertTrue(ret.in(1) instanceof ProjNode);
+ }
+
+ @Test
+ public void testChapter4ConstantArg() {
+ Parser parser = new Parser("return arg; #showGraph;", TypeInteger.constant(2));
+ StopNode ret = parser.parse();
+ assertEquals("return 2;", ret.print());
+ }
+
+ @Test
+ public void testChapter4CompEq() {
+ Parser parser = new Parser("return 3==3; #showGraph;");
+ StopNode ret = parser.parse();
+ assertEquals("return 1;", ret.print());
+ }
+
+ @Test
+ public void testChapter4CompEq2() {
+ Parser parser = new Parser("return 3==4; #showGraph;");
+ StopNode ret = parser.parse();
+ assertEquals("return 0;", ret.print());
+ }
+
+ @Test
+ public void testChapter4CompNEq() {
+ Parser parser = new Parser("return 3!=3; #showGraph;");
+ StopNode ret = parser.parse();
+ assertEquals("return 0;", ret.print());
+ }
+
+ @Test
+ public void testChapter4CompNEq2() {
+ Parser parser = new Parser("return 3!=4; #showGraph;");
+ StopNode 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;");
+ StopNode ret = parser.parse();
+ assertEquals("return (arg+3);", ret.print());
+ }
+
+ @Test
+ public void testChapter4Bug2() {
+ Parser parser = new Parser("int a=arg+1; a=a; return a; #showGraph;");
+ StopNode ret = parser.parse();
+ assertEquals("return (arg+1);", ret.print());
+ }
+
+ @Test
+ public void testChapter4Bug3() {
+ 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;");
+ StopNode ret = parser.parse();
+ assertEquals("return (-arg);", ret.print());
+ }
+
+ @Test
+ public void testVarDecl() {
+ Parser parser = new Parser("int a=1; return a;");
+ StopNode ret = parser.parse();
+ assertEquals("return 1;", ret.print());
+ }
+
+ @Test
+ public void testVarAdd() {
+ Parser parser = new Parser("int a=1; int b=2; return a+b;");
+ StopNode ret = parser.parse();
+ assertEquals("return 3;", ret.print());
+ }
+
+ @Test
+ public void testVarScope() {
+ Parser parser = new Parser("int a=1; int b=2; int c=0; { int b=3; c=a+b; } return c;");
+ StopNode ret = parser.parse();
+ assertEquals("return 4;", ret.print());
+ }
+
+ @Test
+ public void testVarScopeNoPeephole() {
+ Parser parser = new Parser("int a=1; int b=2; int c=0; { int b=3; c=a+b; #showGraph; } return c; #showGraph;");
+ Node._disablePeephole = true;
+ StopNode ret = parser.parse();
+ Node._disablePeephole = false;
+ assertEquals("return (1+3);", ret.print());
+ }
+
+ @Test
+ public void testVarDist() {
+ Parser parser = new Parser("int x0=1; int y0=2; int x1=3; int y1=4; return (x0-x1)*(x0-x1) + (y0-y1)*(y0-y1); #showGraph;");
+ StopNode ret = parser.parse();
+ 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() {
+ Parser parser = new Parser("return 1+2*3+-5;");
+ Node._disablePeephole = true; // disable peephole so we can observe full graph
+ StopNode ret = parser.parse();
+ assertEquals("return (1+((2*3)+(-5)));", ret.print());
+ GraphVisualizer gv = new GraphVisualizer();
+ System.out.println(gv.generateDotOutput(parser));
+ Node._disablePeephole = false;
+ }
+
+ @Test
+ public void testChapter2AddPeephole() {
+ Parser parser = new Parser("return 1+2;");
+ StopNode ret = parser.parse();
+ assertEquals("return 3;", ret.print());
+ }
+
+ @Test
+ public void testChapter2SubPeephole() {
+ Parser parser = new Parser("return 1-2;");
+ StopNode ret = parser.parse();
+ assertEquals("return -1;", ret.print());
+ }
+
+ @Test
+ public void testChapter2MulPeephole() {
+ Parser parser = new Parser("return 2*3;");
+ StopNode ret = parser.parse();
+ assertEquals("return 6;", ret.print());
+ }
+
+ @Test
+ public void testChapter2DivPeephole() {
+ Parser parser = new Parser("return 6/3;");
+ StopNode ret = parser.parse();
+ assertEquals("return 2;", ret.print());
+ }
+
+ @Test
+ public void testChapter2MinusPeephole() {
+ Parser parser = new Parser("return 6/-3;");
+ StopNode ret = parser.parse();
+ assertEquals("return -2;", ret.print());
+ }
+
+ @Test
+ public void testChapter2Example() {
+ Parser parser = new Parser("return 1+2*3+-5;");
+ StopNode ret = parser.parse();
+ assertEquals("return 2;", ret.print());
+ GraphVisualizer gv = new GraphVisualizer();
+ System.out.println(gv.generateDotOutput(parser));
+ }
+
+ @Test
+ public void testSimpleProgram() {
+ Parser parser = new Parser("return 1;");
+ StopNode stop = parser.parse();
+ StartNode start = Parser.START;
+ ReturnNode ret = (ReturnNode)stop.in(0);
+
+ assertTrue(ret.ctrl() instanceof ProjNode);
+ Node expr = ret.expr();
+ if( expr instanceof ConstantNode con ) {
+ assertEquals(start,con.in(0));
+ assertEquals(TypeInteger.constant(1), con._type);
+ } else {
+ fail();
+ }
+ }
+
+ @Test
+ public void testZero() {
+ Parser parser = new Parser("return 0;");
+ parser.parse();
+ StartNode start = Parser.START;
+ for( Node use : start._outputs )
+ if( use instanceof ConstantNode con )
+ assertEquals(TypeInteger.constant(0),con._type);
+ }
+
+ @Test
+ public void testBad1() {
+ try {
+ new Parser("ret").parse();
+ fail();
+ } catch( RuntimeException e ) {
+ assertEquals("Syntax error, expected =: ",e.getMessage());
+ }
+ }
+
+ @Test
+ public void testBad2() {
+ try {
+ new Parser("return 0123;").parse();
+ fail();
+ } catch( RuntimeException e ) {
+ assertEquals("Syntax error: integer values cannot start with '0'",e.getMessage());
+ }
+ }
+
+ @Test
+ public void testNotBad3() {
+ // this test used to fail in chapter 1
+ assertEquals("return 12;", new Parser("return --12;").parse().print());
+ }
+
+ @Test
+ public void testBad4() {
+ try {
+ new Parser("return 100").parse();
+ fail();
+ } catch( RuntimeException e ) {
+ assertEquals("Syntax error, expected ;: ",e.getMessage());
+ }
+ }
+
+ @Test
+ public void testNotBad5() {
+ // this test used to fail in chapter 1
+ assertEquals("return -100;", new Parser("return -100;").parse().print());
+ }
+
+ @Test
+ public void testBad6() {
+ 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/pom.xml b/pom.xml
index 30022404..4557af32 100644
--- a/pom.xml
+++ b/pom.xml
@@ -25,6 +25,8 @@
<module>chapter04</module>
<module>chapter05</module>
<module>chapter06</module>
+ <module>chapter07</module>
+ <module>chapter08</module>
</modules>
<properties>