summaryrefslogtreecommitdiff
path: root/chapter10
diff options
context:
space:
mode:
authorDibyendu Majumdar <dibyendumajumdar@users.noreply.github.com>2024-04-23 17:23:16 +0100
committerGitHub <noreply@github.com>2024-04-23 09:23:16 -0700
commit17f9d743c06170f20773863bd1eca4f25ef41490 (patch)
treea6e3ab1fec4e23d7259305701dc571242637c69f /chapter10
parentce45fc4e749ceb130068d83801a6d4c080fd70d3 (diff)
downloadSimple-17f9d743c06170f20773863bd1eca4f25ef41490.tar.gz
Simple-17f9d743c06170f20773863bd1eca4f25ef41490.zip
Ch10 (#74)
* Chapter 10 start * #67 language grammar for chapter 10 * #67 Fix chapter title * #67 Fix typo * #67 assignment to fields * #67 Struct var decl must be initialized * #67 Start to describe memory and aliasing * #67 revision * #67 Start to describe how we construct the graph * #67 Start to describe how we construct the graph * #67 Start to describe how we construct the graph * #67 Parse struct declaration * #67 WIP parse expressions and statements involving struct vars * #67 WIP parse store expressions * #67 WIP parse load expressions * #67 WIP first go at adding aliases and memslices * #67 WIP A store should get bound to the $mem alias replacing previous node * #67 WIP Add MergeMem node * Update script generator to new features (#68) * #67 WIP Drop MergeMem node * #67 WIP Drop MergeMem node * #67 WIP Improve parsing of postfix expressions * #67 WIP Improve parsing of postfix expressions * #67 WIP Update xmeet - these types can only meet another of the same type * #67 WIP Phi's compute should return input type if input is a MemPtr even if region is in progress because parser needs the correct type * #67 WIP If a null meets a non-null ptr then non-null wins; but ideally we want to retain type of null so we do not have this problem * #67 WIP If a null meets a non-null ptr then non-null then the result is type|null * #67 WIP Fix TypeMemPtr.eq() and also intern struct types * #67 WIP Add missing hash and eq implementations Also refactor common code to get field from ptr * #67 Add START as input to New (as proxy for Mem). Other minor adjustments * #67 Phis widen type inside a loop during parse * #67 Fix redeclaration bug * #67 Small refactor * #67 Add an LLVM inspired IR printer based on post by Yasser A * #67 Add instructions to initialize fields of a struct after a new * #67 Make New's ctrl input the current ctrl rather than Start * #67 Refactor to make it cleaner * #67 Fix bug - interned types should be cleared * #67 Fix bug - new node must be kept so that its not prematurely killed * #67 Fix bug - we must do a depp comparison of structs because types get interned and we can't easily clear the interned types across runs, this means we can end up with structs retaining definitions from older runs. * #67 Refactor * #67 When we compute type of a Phi we must stay within the domain of the Phi, Example Int stays Int, Ptr stays Ptr, Control stays Control, Mem stays Mem. To ensure this we need to default to the GLB of the first input * #67 When we compute type of a Phi we must stay within the domain of the Phi, Example Int stays Int, Ptr stays Ptr, Control stays Control, Mem stays Mem. To ensure this we need to default to the GLB of the first input * #67 Tidy up printing and add expected result on tests * #67 Refactor TypeStruct * #67 Refactor TypeStruct When printing IR in new format - show the projection index * #67 Revise the lattice for ptr types to make it more complete * #67 Revise the lattice for mem and struct types. Add initial test * #67 Refactor how we compute phi type * #67 Refactor how we compute phi type * #67 Use the generated graph but an edge is missing in the graph - to be investigated * #67 One more tweek * Partial work on ch10, some tests busted Revamp Types Mem, MemPtr, Struct, FIeld. Add tests to enforce theoretical rules properties., Add parsing "?" to allow nullable types. Enhance GraphViz to show off mem better. Strip trailing WS in many files. * Strip trailing WS * Post-peephole errors Some nicer error reporting for types. Mem Projs following StartNode are indexed by alias, but may have "holes" in the set of projs. * WS cleanup * Simplify TMP dual Add more test type Rename test type so makefile picks it up Failing 1 test * Scopes and Phis track declared type ...not the initial type. * A fuzzer found bug * Some changes on the fuzzer and maybe faster reducer (#69) * WS and printing changes * Kotlin style null checks (partial) Allow `ptr=rand?; if( ptr ) ptr.field` where the if-test upcasts the ptr to not-null. Field assignments are expressions not statements: `if( (ptr.fld=rez) !=null ) .... ` Many WS changes. * missed cleanup * Fuzzer update (#71) * Update script generator * Fix script generator * Reset type cache to prevent alias blowup (#70) * Reset type cache to prevent alias blowup * Remove the default intern Just reset the UNIQUE_ALIAS number. Intern table doesn't care, that's the whole point of the intern table. Passes existing tests, but fuzzer finds some new stuff * Fix a few of the fuzzer bugs More to come * Fix one more fuzzer bug --------- Co-authored-by: Cliff Click <cliffc@acm.org> * Fix script generated generating invalid code * Greatly simplify Cast usage Casts peephole normally. Added needed Phi optimizations to remove excess casts. Fuzzer bug fixes: Need a plain kill unused on worklist. MemOp error handles aTOP/BOT input. * #67 Add description of lattice to the doc Update grammar - add the '?' nullable syntax Rename VOID to PTR * #67 Some renames * #67 some more text re lattice * #67 Fix the alias ID start to 2 * #67 Revise lattice diagram and doc * #67 Start describing the parser changes and Null Pointer analysis * #67 Draw lattice diagram using graphviz * #67 Revisions to text * #67 Improve description of alias classes * #67 Some renames to make code clearer * #67 Describe null pointer analysis * Fix script generated generating invalid code (#72) * updates to readme shorter example names to make a nicer graph * #67 Revise top level README * #67 Revise top level README --------- Co-authored-by: XmiliaH <45106915+XmiliaH@users.noreply.github.com> Co-authored-by: Cliff Click <cliffc@acm.org> Co-authored-by: XmiliaH <xmilia.hermit@gmail.com>
Diffstat (limited to 'chapter10')
-rw-r--r--chapter10/Makefile98
-rw-r--r--chapter10/README.md284
-rw-r--r--chapter10/docs/10-grammar.md128
-rw-r--r--chapter10/docs/build-graphs.sh3
-rw-r--r--chapter10/docs/example1.drawio234
-rw-r--r--chapter10/docs/example1.gv84
-rw-r--r--chapter10/docs/example1.svg321
-rw-r--r--chapter10/docs/lattice.drawio379
-rw-r--r--chapter10/docs/lattice.gv122
-rw-r--r--chapter10/docs/lattice.svg460
-rw-r--r--chapter10/pom.xml14
-rw-r--r--chapter10/src/main/java/com/seaofnodes/simple/GraphEvaluator.java181
-rw-r--r--chapter10/src/main/java/com/seaofnodes/simple/GraphVisualizer.java269
-rw-r--r--chapter10/src/main/java/com/seaofnodes/simple/IRPrinter.java188
-rw-r--r--chapter10/src/main/java/com/seaofnodes/simple/IterPeeps.java204
-rw-r--r--chapter10/src/main/java/com/seaofnodes/simple/Parser.java825
-rw-r--r--chapter10/src/main/java/com/seaofnodes/simple/Utils.java58
-rw-r--r--chapter10/src/main/java/com/seaofnodes/simple/node/AddNode.java174
-rw-r--r--chapter10/src/main/java/com/seaofnodes/simple/node/BoolNode.java75
-rw-r--r--chapter10/src/main/java/com/seaofnodes/simple/node/CastNode.java38
-rw-r--r--chapter10/src/main/java/com/seaofnodes/simple/node/ConstantNode.java58
-rw-r--r--chapter10/src/main/java/com/seaofnodes/simple/node/DivNode.java38
-rw-r--r--chapter10/src/main/java/com/seaofnodes/simple/node/IfNode.java62
-rw-r--r--chapter10/src/main/java/com/seaofnodes/simple/node/LoadNode.java43
-rw-r--r--chapter10/src/main/java/com/seaofnodes/simple/node/LoopNode.java29
-rw-r--r--chapter10/src/main/java/com/seaofnodes/simple/node/MemOpNode.java41
-rw-r--r--chapter10/src/main/java/com/seaofnodes/simple/node/MinusNode.java30
-rw-r--r--chapter10/src/main/java/com/seaofnodes/simple/node/MulNode.java57
-rw-r--r--chapter10/src/main/java/com/seaofnodes/simple/node/MultiNode.java14
-rw-r--r--chapter10/src/main/java/com/seaofnodes/simple/node/NewNode.java40
-rw-r--r--chapter10/src/main/java/com/seaofnodes/simple/node/Node.java623
-rw-r--r--chapter10/src/main/java/com/seaofnodes/simple/node/NotNode.java44
-rw-r--r--chapter10/src/main/java/com/seaofnodes/simple/node/PhiNode.java156
-rw-r--r--chapter10/src/main/java/com/seaofnodes/simple/node/ProjNode.java62
-rw-r--r--chapter10/src/main/java/com/seaofnodes/simple/node/RegionNode.java117
-rw-r--r--chapter10/src/main/java/com/seaofnodes/simple/node/ReturnNode.java55
-rw-r--r--chapter10/src/main/java/com/seaofnodes/simple/node/ScopeNode.java296
-rw-r--r--chapter10/src/main/java/com/seaofnodes/simple/node/StartNode.java70
-rw-r--r--chapter10/src/main/java/com/seaofnodes/simple/node/StopNode.java65
-rw-r--r--chapter10/src/main/java/com/seaofnodes/simple/node/StoreNode.java46
-rw-r--r--chapter10/src/main/java/com/seaofnodes/simple/node/SubNode.java42
-rw-r--r--chapter10/src/main/java/com/seaofnodes/simple/type/Field.java96
-rw-r--r--chapter10/src/main/java/com/seaofnodes/simple/type/Type.java191
-rw-r--r--chapter10/src/main/java/com/seaofnodes/simple/type/TypeInteger.java101
-rw-r--r--chapter10/src/main/java/com/seaofnodes/simple/type/TypeMem.java64
-rw-r--r--chapter10/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java78
-rw-r--r--chapter10/src/main/java/com/seaofnodes/simple/type/TypeStruct.java123
-rw-r--r--chapter10/src/main/java/com/seaofnodes/simple/type/TypeTuple.java88
-rw-r--r--chapter10/src/test/java/com/seaofnodes/simple/Chapter01Test.java50
-rw-r--r--chapter10/src/test/java/com/seaofnodes/simple/Chapter02Test.java99
-rw-r--r--chapter10/src/test/java/com/seaofnodes/simple/Chapter03Test.java70
-rw-r--r--chapter10/src/test/java/com/seaofnodes/simple/Chapter04Test.java130
-rw-r--r--chapter10/src/test/java/com/seaofnodes/simple/Chapter05Test.java225
-rw-r--r--chapter10/src/test/java/com/seaofnodes/simple/Chapter06Test.java296
-rw-r--r--chapter10/src/test/java/com/seaofnodes/simple/Chapter07Test.java243
-rw-r--r--chapter10/src/test/java/com/seaofnodes/simple/Chapter08Test.java211
-rw-r--r--chapter10/src/test/java/com/seaofnodes/simple/Chapter09Test.java300
-rw-r--r--chapter10/src/test/java/com/seaofnodes/simple/Chapter10Test.java363
-rw-r--r--chapter10/src/test/java/com/seaofnodes/simple/FuzzerWrap.java35
-rw-r--r--chapter10/src/test/java/com/seaofnodes/simple/TypeTest.java145
-rw-r--r--chapter10/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java173
-rw-r--r--chapter10/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java140
-rw-r--r--chapter10/src/test/java/com/seaofnodes/simple/fuzzer/Reducer.java176
-rw-r--r--chapter10/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java792
64 files changed, 10316 insertions, 0 deletions
diff --git a/chapter10/Makefile b/chapter10/Makefile
new file mode 100644
index 00000000..c7724bc7
--- /dev/null
+++ b/chapter10/Makefile
@@ -0,0 +1,98 @@
+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.%,$(wildcard $(TST)/$(SIMPLE)/*Test.java))
+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)
+ @echo "testing " $(test_cp)
+ @$(JVM) org.junit.runner.JUnitCore $(test_cp)
+
+fuzzer: $(default_targets)
+ @echo "fuzzing " $(test_cp)
+ @$(JVM) org.junit.runner.JUnitCore com.seaofnodes.simple.FuzzerWrap
+
+
+.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/chapter10/README.md b/chapter10/README.md
new file mode 100644
index 00000000..c4045513
--- /dev/null
+++ b/chapter10/README.md
@@ -0,0 +1,284 @@
+# Chapter 10
+
+In this chapter:
+
+* We add user defined struct types in the Simple language.
+* We introduce the concept of Memory, Memory Pointers and Aliasing.
+* We add new nodes to represent operations on memory - such as creating new instance of a struct, storing and loading struct fields.
+
+Here is the [complete language grammar](docs/10-grammar.md) for this chapter.
+
+## Memory
+
+In previous chapters, we dealt with scalar values that fit into a single
+machine register. We now introduce aggregate values of `struct` type; these
+have reference semantics and thus do not behave like register values, even if
+the struct would fit into a register. To support values of such a type, we
+need the concept of `Memory`.
+
+The core ideas regarding `Memory` are:
+
+* The program starts and stops with a single `Memory` value that encapsulates
+ the whole of available memory; `Start` produces memory and `Return` consumes
+ memory.
+
+* Each `struct` and field carves up this memory into "alias classes", a unique
+ class for each combination of struct and field.
+
+* Memory in different alias classes *never* aliases; memory in the same alias
+ class *always* aliases. This is sometimes called "equivalence class"
+ aliasing.
+
+* Equivalence class aliasing can be directly represented in the Sea of Nodes,
+ as such we will not use any other side-structure for aliasing. This also
+ means with normal peepholes we can do alias-based optimizations such as
+ load/store bypassing or load forwarding without needing to consult any side
+ structure, or make alias tracking modifications when we alter the graph.
+
+
+### Alias Classes
+
+Aliasing describes a situation in which a data location in memory can be
+accessed through multiple symbolic names (variables) in the program. Modifying
+the data through one name implicitly modifies the values associated with all
+the aliased names.
+
+Data accesses to the same alias class (struct+field) are serialized as defined
+by the source program; data in other classes is allowed to be accessed in
+parallel.
+
+Simple, like Java, is a strongly typed language. Therefore, two variables that
+have incompatible types cannot possibly be names to the same location in
+memory. We can use type information to determine which symbols alias,
+i.e., potentially reference the same location in memory. We
+divide/slice all memory into disjoint alias classes.
+
+We implement the "alias class" as a unique integer ID assigned to each field in
+a struct, where distinct struct types have their own set of ids. Thus, every
+field in every struct type is a new alias class. In later chapters as we
+implement arrays and subtyping, our strategy for assigning alias classes will
+be enhanced.
+
+For background information and overview of type based alias analysis, please
+refer to the paper `Type Based Alias Analysis`.[^1] Our approach is a form of
+TBAA similar to the "FieldTypeDecl" algorithm described in the paper. We do
+not track aliasing by instance of a Struct type.
+
+
+### Implementation of Alias Classes
+
+For example, suppose we have declared two struct types like so:
+
+```
+struct Vec2D { int x; int y; }
+struct Vec3D { int x; int y; int z; }
+```
+
+Then we can imagine assigning following alias class IDs
+
+| Struct type | Field | Alias |
+| ----------- | ----- | ----- |
+| Vec2D | x | 1 |
+| Vec2D | y | 2 |
+| Vec3D | x | 3 |
+| Vec3D | y | 4 |
+| Vec3D | z | 5 |
+
+So at this point we have sliced memory into 5 distinct alias classes.
+Note that the `x` and `y` in `Vec2D` do not alias `x` and `y` in `Vec3D`.
+
+In this chapter we do not have inheritance or sub-typing. But if we had subtyping and `Vec3D`
+was a subtype of `Vec2D` then `x` and `y` would alias and would be given the same alias class.
+
+## Extensions to Intermediate Representation
+
+We add following new Node types to support memory operations:
+
+| Node Name | Type | Description | Inputs | Value |
+|-----------|------|----------------------------------|-----------------------------------------------------------|----------------------------------------|
+| New | Mem | Create ptr to new object | Control, Struct type | Ptr value |
+| Store | Mem | Stores a value in a struct field | Memory slice (aliased by struct+field), Ptr, Field, Value | Memory slice (aliased by struct+field) |
+| Load | Mem | Loads a value from a field | Memory slice (aliased by struct+field), Ptr, Field | Value loaded |
+| Cast | Data | Upcasts a value | Control, Data, type | Upcast value |
+
+
+* New takes the current control as an input so that it is pinned correctly in
+ the control flow. Conceptually the control input is also a proxy for all
+ memory that originates from the Start node.
+
+* Above, "Ptr" refers to the base address of the allocated object. Ptrs can represent different values in loops.
+* A "Memory Slice" represents a slice of memory where all stores and loads alias. Different slices do not alias.
+
+Additionally, the following Node types will be enhanced:
+
+| Node Name | Type | Changes |
+|-----------|---------|------------------------------------------------------------------|
+| Start | Control | Start will produce the initial Memory projections, one per slice |
+| Return | Control | Will merge all memory slices |
+
+## Enhanced Type Lattice
+
+The Type Lattice for Simple has a major revision in this chapter.
+
+![Graph1](./docs/lattice.svg)
+
+Within the Type Lattice, we now have the following type domains:
+
+* Control type - this represents control flow
+* Integer type - Integer values
+* Struct type (new) - Represents user defined struct types, a struct type is allowed to have members of Integer type only in this chapter
+ * `$TOP` represents local Top for struct type; all we know about the type is that it is a struct but, we do not know if it is a specific struct, or all possible structs, etc.
+ * `$BOT` represents local Bottom for struct type; we definitely know the value can take all possible struct types.
+* Pointer type (new) - Represents a pointer to a struct type
+ * `null` is a pointer to non-existent memory object
+ * We use the prefix `*` to mean pointer-to. Thus `*S1` means pointer to `S1`, `*$TOP` means pointer to `$TOP`.
+ * `*void` is a synonym for `*$BOT` - i.e. it represents a Non-Null pointer to all possible struct types, akin to `void *` in C except not null.
+ * `?` suffix represents the union of a pointer to some type and `null`.
+ * `null` pointer evaluates to False and non-null pointers evaluate to True, as in C.
+* Memory (new) - Represents memory and memory slices
+ * `MEM#TOP` - Nothing known about a slice
+ * `#n` - refers to a memory slice
+ * `MEM#BOT` - all of memory
+
+We make use of following operations on the lattice.
+
+* The `meet` operation takes two types and computes the greatest lower bound
+ type. While not true for all lattices, in the our lattice this can be
+ thought of as set union (ORing) of its input types (as sets).
+ * Example, meet of `*S1` and `*S2` results in `*void` - there is no lattice
+ element for exactly both `{*S1,*S2}`, so we drop down to the nearest
+ element containing both (and maybe more), which is `*void*`.
+ * Meet of `*S1` and struct `Null` results in `*S1?`
+ * Meet of `*void` and `Null` results in `*$BOT?`; bear in mind that `*void` is a synonym for `*$BOT`.
+ * Meet of `1` and `2` results in `Int Bot`.
+ * Meet of `#1` and `#2` results in `MEM#BOT`.
+* The `dual` operation is symmetric across the lattice centerline. Types directly on the centerline are their own dual.
+ * Find the corresponding node of the lattice after inverting the lattice.
+ * Thus, dual of `Top` is `Bot`.
+ * The dual of `*$TOP` is `*$BOT?`.
+ * For structs, the dual is obtained by computing the dual of each struct member. Thus, dual of `*S1` is not `*S1?`.
+
+* The `join` operation takes two types and computes the least upper bound type.
+ Similar to `meet` this can thought of as set intersection (ANDing) of its input types.
+ Because our lattice structure is simple, we compute `join` with a well known identity:
+ `JOIN(x,y) === MEET(x.dual,y.dual).dual`
+ * Example, join of `*S1` and `*S2` results in `*$TOP`.
+ * Join of `1` and `2` results in `Int Top`.
+ * Join of `*void` and `Null` results in `*$TOP`.
+
+As we construct the Sea of Nodes graph, we ensure that values stay inside the
+domain they are created in - that is ptr fields will never contain ints
+(obviously)... nor even the generic `TOP` and `BOT`. There are a couple of
+nuances worth highlighting.
+
+* When Phis are created, the initial type of the Phi is based on the declared
+ type of its first input. This is a pessimistic type assignment because we do
+ not yet know what other types will be met.
+
+* When all the inputs of the Phi are known, we start with the local Top of the
+ declared type, and then compute a meet of all the input types. This
+ computation results in a more refined type for the Phi.
+
+## Parser Enhancements
+
+In previous chapters the only available type was Integer. Now, variables can
+be of Integer type or pointer to Struct types. To support this, the Parser now
+tracks the declared type of a variable (used to only be Integer!). The
+declared type of a variable defines the set of values that can be legitimately
+assigned to the variable. The Sea of Nodes graph also tracks the actual type
+of values assigned to variables, these type transitions are defined by the Type
+lattice described in the previous section.
+
+## Null Pointer Analysis
+
+The Simple language syntax allows a pointer variable to be specified as
+Null-able - and it prevents ever throwing a Null Pointer Exception. The
+compiler tries to decide in a null check is needed, and if needed and missing
+the compiler rejects the program (asking the user to insert a null check).
+
+When the Parser encounters conditions that test the truthiness of a pointer variable, it uses this knowledge to refine the type information in the branches that follow.
+
+Here are two motivating examples:
+
+```java
+struct Bar { int a; }
+Bar? bar = new Bar;
+if (arg) bar = null;
+if( bar ) bar.a = 1;
+```
+
+If `bar` is not `null` above, then we would like to allow the assignment to
+`bar.a`. If the final line lacked the null check, e.g. a plain `bar.a = 1;`,
+then `Java` would compile this program but throw a NPE, and Simple rejects the
+program pointing out a null check is required.
+
+
+```java
+struct Bar { int a; }
+Bar? bar = new Bar;
+if (arg) bar = null;
+int rez = 3;
+if( !bar ) rez=4;
+else bar.a = 1;
+```
+
+If `bar` is `null` then we would like `!bar` to evaluate to `true`. In the else
+branch we know `bar` is not null, hence the assignment to `bar.a` should be
+allowed.
+
+To enable this behaviour, we make following enhancements.
+
+* We track the null-ness of a pointer in the Type system, and hence in every
+ Node that ptr type flows through.
+
+* We "wrap" the predicate of an `If` node with an up-cast when we see that the
+ predicate is a ptr value. The up-cast removes the null possibility on the
+ true branch making it legal to use the pointer.
+
+* The up-cast is done using a `Cast` op with the type `*void`. This does a
+ lattice JOIN of the original type and `*void`. This JOIN operation preserves
+ everything we already know about ptr, and also adds the new knowledge that
+ the ptr is not-null.
+
+* The up-cast is represented by the `CastNode` op, and if applicable, we
+ replace all occurrences of the original predicate with the up-cast in the
+ current scope. The change is local to each branch of the `If`; occuring
+ separately for the true and the false branches of the `If` (and the false
+ branch inverts the predicate).
+
+* The up-cast peepholes normally; if its input's type is a super type of the
+ up-cast, then the `CastNode` can be removed. See `ScopeNode.upcast()` method,
+ and `CastNode`.
+
+* When computing a type for Not, we produce an Integer type when its input is a
+ ptr. When the input is a `null` we convert to `1` and when is a not `null`
+ ptr value, we convert to `0`. See `NotNode.compute()`.
+
+
+## Examples
+
+We now look at some examples.
+
+### Example 1
+
+This is a simple example illustrating how loads and stores are ordered based on aliasing.
+
+```java
+struct Vec2D { int x; int y; }
+
+Vec2D v = new Vec2D;
+v.x = 1;
+if (arg)
+ v.y = 2;
+else
+ v.y = 3;
+return v;
+```
+
+The graph from above will have the shape:
+
+![Graph1](./docs/example1.svg)
+
+
+[^1]: Diwan A., McKinley K.S., Moss J.E.B. (1998).
+ Type Based Alias Analysis. \ No newline at end of file
diff --git a/chapter10/docs/10-grammar.md b/chapter10/docs/10-grammar.md
new file mode 100644
index 00000000..cb68df3d
--- /dev/null
+++ b/chapter10/docs/10-grammar.md
@@ -0,0 +1,128 @@
+# Grammar for Chapter 10
+
+```antlrv4
+grammar SimpleLanguage;
+
+program
+ : statement+ EOF
+ ;
+
+statement
+ : returnStatement
+ | structDeclaration
+ | declStatement
+ | blockStatment
+ | expressionStatement
+ | ifStatement
+ | whileStatement
+ | breakStatement
+ | continueStatment
+ | metaStatement
+ ;
+
+field
+ : 'int' IDENTIFIER ';'
+ ;
+
+fields
+ : field+
+ ;
+
+structDeclaration
+ : 'struct' IDENTIFIER '{' fields '}'
+ ;
+
+whileStatement
+ : 'while' '(' expression ')' statement
+ ;
+
+breakStatement
+ : 'break' ';'
+ ;
+
+continueStatement
+ : 'continue' ';'
+ ;
+
+ifStatement
+ : 'if' '(' expression ')' statement ('else' statement)?
+ ;
+
+metaStatement
+ : '#showGraph' ';'
+ ;
+
+expressionStatement
+ : IDENTIFIER '=' expression ';'
+ | fieldExpression '=' expression ';'
+ ;
+
+blockStatement
+ : '{' statement+ '}'
+ ;
+
+structName
+ : IDENTIFIER
+ ;
+
+declStatement
+ : 'int' IDENTIFIER '=' expression ';'
+ | structName IDENTIFIER ('?')? '=' expression ';'
+ ;
+
+returnStatement
+ : 'return' expression ';'
+ ;
+
+expression
+ : comparisonExpression
+ ;
+
+comparisonExpression
+ : additiveExpression (('==' | '!='| '>'| '<'| '>='| '<=') additiveExpression)*
+ ;
+
+additiveExpression
+ : multiplicativeExpression (('+' | '-') multiplicativeExpression)*
+ ;
+
+multiplicativeExpression
+ : unaryExpression (('*' | '/') unaryExpression)*
+ ;
+
+unaryExpression
+ : ('-') unaryExpression
+ | primaryExpression
+ ;
+
+newExpression
+ : 'new' IDENTIFIER
+ ;
+
+fieldExpression
+ : primaryExpresson '.' IDENTIFIER
+ ;
+
+primaryExpression
+ : IDENTIFIER
+ | INTEGER_LITERAL
+ | 'true'
+ | 'false'
+ | 'null'
+ | newExpression
+ | '(' expression ')'
+ | fieldExpression
+ ;
+
+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/chapter10/docs/build-graphs.sh b/chapter10/docs/build-graphs.sh
new file mode 100644
index 00000000..853f50f7
--- /dev/null
+++ b/chapter10/docs/build-graphs.sh
@@ -0,0 +1,3 @@
+dot -Tsvg example1.gv > example1.svg
+dot -Tsvg lattice.gv > lattice.svg
+
diff --git a/chapter10/docs/example1.drawio b/chapter10/docs/example1.drawio
new file mode 100644
index 00000000..b3b408c5
--- /dev/null
+++ b/chapter10/docs/example1.drawio
@@ -0,0 +1,234 @@
+<mxfile host="65bd71144e">
+ <diagram id="pVfa21MziEbxiRWqA5Sa" name="Page-1">
+ <mxGraphModel dx="1780" dy="907" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
+ <root>
+ <mxCell id="0"/>
+ <mxCell id="1" parent="0"/>
+ <mxCell id="2" value="" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
+ <mxGeometry x="280" y="20" width="160" height="90" as="geometry"/>
+ </mxCell>
+ <mxCell id="3" value="Start" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#e3c800;fontColor=#000000;strokeColor=#B09500;" vertex="1" parent="1">
+ <mxGeometry x="280" y="20" width="160" height="40" as="geometry"/>
+ </mxCell>
+ <mxCell id="4" value="$ctrl" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#e3c800;fontColor=#000000;strokeColor=#B09500;" vertex="1" parent="1">
+ <mxGeometry x="290" y="70" width="40" height="30" as="geometry"/>
+ </mxCell>
+ <mxCell id="5" value="arg" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
+ <mxGeometry x="340" y="70" width="40" height="30" as="geometry"/>
+ </mxCell>
+ <mxCell id="6" value="Mem" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
+ <mxGeometry x="390" y="70" width="40" height="30" as="geometry"/>
+ </mxCell>
+ <mxCell id="8" style="edgeStyle=none;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" source="7" target="6">
+ <mxGeometry relative="1" as="geometry"/>
+ </mxCell>
+ <mxCell id="7" value="new Vec2D" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
+ <mxGeometry x="360" y="210" width="100" height="40" as="geometry"/>
+ </mxCell>
+ <mxCell id="9" value="#1" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="1">
+ <mxGeometry x="720" y="200" width="80" height="80" as="geometry"/>
+ </mxCell>
+ <mxCell id="12" style="edgeStyle=none;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" source="10" target="51">
+ <mxGeometry relative="1" as="geometry">
+ <mxPoint x="385" y="270" as="targetPoint"/>
+ <Array as="points">
+ <mxPoint x="550" y="360"/>
+ </Array>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="13" style="edgeStyle=none;html=1;exitX=0.75;exitY=0;exitDx=0;exitDy=0;" edge="1" parent="1" source="10" target="9">
+ <mxGeometry relative="1" as="geometry">
+ <mxPoint x="550" y="230" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="10" value="Store .x" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
+ <mxGeometry x="650" y="350" width="120" height="60" as="geometry"/>
+ </mxCell>
+ <mxCell id="11" value="alias 1" style="endArrow=classic;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;" edge="1" parent="1" source="10" target="52">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="420" y="290" as="sourcePoint"/>
+ <mxPoint x="470" y="240" as="targetPoint"/>
+ <Array as="points">
+ <mxPoint x="610" y="290"/>
+ </Array>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="14" value="" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
+ <mxGeometry x="275" y="410" width="90" height="80" as="geometry"/>
+ </mxCell>
+ <mxCell id="15" value="" style="curved=1;endArrow=classic;html=1;" edge="1" parent="1" source="39" target="5">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="170" y="200" as="sourcePoint"/>
+ <mxPoint x="450" y="370" as="targetPoint"/>
+ <Array as="points"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="16" value="If" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#e3c800;fontColor=#000000;strokeColor=#B09500;" vertex="1" parent="1">
+ <mxGeometry x="275" y="410" width="90" height="30" as="geometry"/>
+ </mxCell>
+ <mxCell id="17" value="T" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#e3c800;fontColor=#000000;strokeColor=#B09500;" vertex="1" parent="1">
+ <mxGeometry x="285" y="450" width="30" height="30" as="geometry"/>
+ </mxCell>
+ <mxCell id="18" value="F" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#e3c800;fontColor=#000000;strokeColor=#B09500;" vertex="1" parent="1">
+ <mxGeometry x="325" y="450" width="30" height="30" as="geometry"/>
+ </mxCell>
+ <mxCell id="19" value="Store .y" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
+ <mxGeometry x="110" y="430" width="120" height="60" as="geometry"/>
+ </mxCell>
+ <mxCell id="20" value="" style="endArrow=classic;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" source="19" target="52">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="170" y="480" as="sourcePoint"/>
+ <mxPoint x="220" y="430" as="targetPoint"/>
+ <Array as="points">
+ <mxPoint x="290" y="360"/>
+ </Array>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="21" value="alias 2" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="20">
+ <mxGeometry x="-0.2147" y="-1" relative="1" as="geometry">
+ <mxPoint x="-15" y="30" as="offset"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="22" value="#2" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="1">
+ <mxGeometry x="60" y="295" width="80" height="80" as="geometry"/>
+ </mxCell>
+ <mxCell id="23" value="" style="endArrow=classic;html=1;" edge="1" parent="1" source="19" target="22">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="140" y="430" as="sourcePoint"/>
+ <mxPoint x="190" y="380" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="24" value="" style="endArrow=classic;html=1;" edge="1" parent="1" source="19" target="51">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="200" y="430" as="sourcePoint"/>
+ <mxPoint x="380" y="280" as="targetPoint"/>
+ <Array as="points">
+ <mxPoint x="270" y="290"/>
+ </Array>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="29" style="edgeStyle=none;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" edge="1" parent="1" source="25" target="27">
+ <mxGeometry relative="1" as="geometry"/>
+ </mxCell>
+ <mxCell id="25" value="Store .y" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
+ <mxGeometry x="440" y="490" width="90" height="60" as="geometry"/>
+ </mxCell>
+ <mxCell id="26" value="" style="endArrow=classic;html=1;" edge="1" parent="1" source="25" target="51">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="400" y="420" as="sourcePoint"/>
+ <mxPoint x="430" y="300" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="27" value="#3" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="1">
+ <mxGeometry x="540" y="420" width="80" height="80" as="geometry"/>
+ </mxCell>
+ <mxCell id="28" value="alias 2" style="endArrow=classic;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" source="25" target="52">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="320" y="520" as="sourcePoint"/>
+ <mxPoint x="350" y="450" as="targetPoint"/>
+ <Array as="points">
+ <mxPoint x="480" y="350"/>
+ </Array>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="30" value="Region" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#e3c800;fontColor=#000000;strokeColor=#B09500;" vertex="1" parent="1">
+ <mxGeometry x="250" y="600" width="120" height="60" as="geometry"/>
+ </mxCell>
+ <mxCell id="31" value="" style="endArrow=classic;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;exitX=0.392;exitY=-0.064;exitDx=0;exitDy=0;exitPerimeter=0;fillColor=#a20025;strokeColor=#6F0000;" edge="1" parent="1" source="30" target="17">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="265" y="570" as="sourcePoint"/>
+ <mxPoint x="315" y="520" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="32" value="" style="endArrow=classic;html=1;exitX=0.75;exitY=0;exitDx=0;exitDy=0;fillColor=#a20025;strokeColor=#6F0000;" edge="1" parent="1" source="30" target="18">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="310" y="570" as="sourcePoint"/>
+ <mxPoint x="360" y="520" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="35" style="edgeStyle=none;html=1;exitX=1;exitY=1;exitDx=0;exitDy=0;" edge="1" parent="1" source="33" target="30">
+ <mxGeometry relative="1" as="geometry"/>
+ </mxCell>
+ <mxCell id="33" value="phi" style="ellipse;whiteSpace=wrap;html=1;fillColor=#fad7ac;strokeColor=#b46504;fontColor=#0e0101;" vertex="1" parent="1">
+ <mxGeometry x="90" y="540" width="120" height="80" as="geometry"/>
+ </mxCell>
+ <mxCell id="36" value="" style="endArrow=classic;html=1;" edge="1" parent="1" source="33" target="19">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="170" y="560" as="sourcePoint"/>
+ <mxPoint x="220" y="510" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="37" value="" style="endArrow=classic;html=1;" edge="1" parent="1" source="33" target="25">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="170" y="560" as="sourcePoint"/>
+ <mxPoint x="220" y="510" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="38" value="" style="curved=1;endArrow=classic;html=1;startArrow=none;fillColor=#a20025;strokeColor=#6F0000;" edge="1" parent="1" source="16" target="4">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="295" y="330" as="sourcePoint"/>
+ <mxPoint x="307.14285714285734" y="80" as="targetPoint"/>
+ <Array as="points"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="39" value="==" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="1">
+ <mxGeometry x="130" y="180" width="80" height="80" as="geometry"/>
+ </mxCell>
+ <mxCell id="41" value="#1" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="1">
+ <mxGeometry x="50" y="60" width="80" height="80" as="geometry"/>
+ </mxCell>
+ <mxCell id="42" value="" style="endArrow=classic;html=1;" edge="1" parent="1" source="39" target="41">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="140" y="150" as="sourcePoint"/>
+ <mxPoint x="190" y="100" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="49" style="edgeStyle=none;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;fontColor=default;fillColor=#e51400;strokeColor=#B20000;" edge="1" parent="1" source="43" target="30">
+ <mxGeometry relative="1" as="geometry"/>
+ </mxCell>
+ <mxCell id="43" value="Return" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#e3c800;fontColor=#000000;strokeColor=#B09500;" vertex="1" parent="1">
+ <mxGeometry x="250" y="850" width="120" height="60" as="geometry"/>
+ </mxCell>
+ <mxCell id="44" value="Merge Mem" style="rounded=0;whiteSpace=wrap;html=1;fontColor=default;" vertex="1" parent="1">
+ <mxGeometry x="365" y="730" width="120" height="60" as="geometry"/>
+ </mxCell>
+ <mxCell id="45" value="" style="endArrow=classic;html=1;fontColor=default;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" target="51">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="410" y="730" as="sourcePoint"/>
+ <mxPoint x="400" y="300" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="46" value="" style="endArrow=classic;html=1;fontColor=default;" edge="1" parent="1" source="44" target="10">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="600" y="680" as="sourcePoint"/>
+ <mxPoint x="650" y="630" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="47" value="" style="endArrow=classic;html=1;fontColor=default;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" source="44" target="33">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="160" y="800" as="sourcePoint"/>
+ <mxPoint x="210" y="750" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="50" value="" style="endArrow=classic;html=1;fontColor=default;" edge="1" parent="1" source="43" target="44">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="305" y="860" as="sourcePoint"/>
+ <mxPoint x="355" y="810" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="51" value="ptr" style="rounded=0;whiteSpace=wrap;html=1;fontColor=default;" vertex="1" parent="1">
+ <mxGeometry x="365" y="250" width="40" height="30" as="geometry"/>
+ </mxCell>
+ <mxCell id="52" value="mem" style="rounded=0;whiteSpace=wrap;html=1;fontColor=default;" vertex="1" parent="1">
+ <mxGeometry x="405" y="250" width="40" height="30" as="geometry"/>
+ </mxCell>
+ <mxCell id="55" value="" style="endArrow=classic;html=1;fontColor=default;" edge="1" parent="1" source="16" target="39">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="400" y="490" as="sourcePoint"/>
+ <mxPoint x="450" y="440" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ </root>
+ </mxGraphModel>
+ </diagram>
+</mxfile> \ No newline at end of file
diff --git a/chapter10/docs/example1.gv b/chapter10/docs/example1.gv
new file mode 100644
index 00000000..fcc24154
--- /dev/null
+++ b/chapter10/docs/example1.gv
@@ -0,0 +1,84 @@
+digraph chapter10 {
+/*
+struct Vec2D { int x; int y; }
+
+Vec2D v = new Vec2D;
+v.x = 1;
+if (arg)
+ v.y = 2;
+else
+ v.y = 3;
+return v;
+
+*/
+ 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><TD PORT="p2">$Vec2D_x</TD><TD PORT="p3">$Vec2D_y</TD></TR>
+ </TABLE>
+ </TD></TR>
+ </TABLE>>
+ ];
+ Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ];
+ New8 [ label="new Vec2D" ];
+ Con_9 [ label="#0" ];
+ Store10 [ label="Store" ];
+ Store11 [ label="Store" ];
+ Con_12 [ label="#1" ];
+ Store13 [ label="Store" ];
+ 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="#2" ];
+ Store19 [ label="Store" ];
+ Con_20 [ label="#3" ];
+ Store21 [ label="Store" ];
+ Region22 [ shape=box style=filled fillcolor=yellow label="Region" ];
+ Phi_Vec2D_y23 [ style=filled fillcolor=lightyellow label="&phi;_$Vec2D_y" ];
+ Return24 [ shape=box style=filled fillcolor=yellow label="Return" ];
+ { rank=same; Region22;Phi_Vec2D_y23;}
+ }
+ edge [ fontname=Helvetica, fontsize=8 ];
+ Stop3 -> Return24[taillabel=0 color=red];
+ New8 -> Start2:p0[taillabel=0 color=green];
+ Store10 -> Start2:p2[taillabel=1];
+ Store10 -> New8[taillabel=2];
+ Store10 -> Con_9[taillabel=3];
+ Store11 -> Start2:p3[taillabel=1];
+ Store11 -> New8[taillabel=2];
+ Store11 -> Con_9[taillabel=3];
+ Store13 -> Store10[taillabel=1];
+ Store13 -> New8[taillabel=2];
+ Store13 -> Con_12[taillabel=3];
+ If14 -> Start2:p0[taillabel=0 color=red];
+ If14 -> Start2:p1[taillabel=1];
+ Store19 -> Store11[taillabel=1];
+ Store19 -> New8[taillabel=2];
+ Store19 -> Con_18[taillabel=3];
+ Store21 -> Store11[taillabel=1];
+ Store21 -> New8[taillabel=2];
+ Store21 -> Con_20[taillabel=3];
+ Region22 -> If14:p0[taillabel=1 color=red];
+ Region22 -> If14:p1[taillabel=2 color=red];
+ Phi_Vec2D_y23 -> Region22 [style=dotted taillabel=0];
+ Phi_Vec2D_y23 -> Store19[taillabel=1];
+ Phi_Vec2D_y23 -> Store21[taillabel=2 constraint=false];
+ Return24 -> Region22[taillabel=0 color=red];
+ Return24 -> New8[taillabel=1];
+ Return24 -> Store13[taillabel=2];
+ Return24 -> Phi_Vec2D_y23[taillabel=3];
+}
diff --git a/chapter10/docs/example1.svg b/chapter10/docs/example1.svg
new file mode 100644
index 00000000..0c2dfa4d
--- /dev/null
+++ b/chapter10/docs/example1.svg
@@ -0,0 +1,321 @@
+<?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: chapter10 Pages: 1 -->
+<svg width="518pt" height="564pt"
+ viewBox="0.00 0.00 518.00 564.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 560)">
+<title>chapter10</title>
+<polygon fill="white" stroke="transparent" points="-4,4 -4,-560 514,-560 514,4 -4,4"/>
+<g id="clust1" class="cluster">
+<title>cluster_Nodes</title>
+<polygon fill="none" stroke="black" points="8,-8 8,-548 502,-548 502,-8 8,-8"/>
+</g>
+<!-- Start2 -->
+<g id="node1" class="node">
+<title>Start2</title>
+<polygon fill="yellow" stroke="transparent" points="212,-511 212,-536 487,-536 487,-511 212,-511"/>
+<polygon fill="none" stroke="black" points="212,-511 212,-536 487,-536 487,-511 212,-511"/>
+<text text-anchor="start" x="331" y="-519.8" font-family="Times,serif" font-size="14.00">Start</text>
+<polygon fill="none" stroke="black" points="212,-480 212,-511 487,-511 487,-480 212,-480"/>
+<polygon fill="yellow" stroke="transparent" points="217,-485 217,-506 257,-506 257,-485 217,-485"/>
+<polygon fill="none" stroke="black" points="217,-485 217,-506 257,-506 257,-485 217,-485"/>
+<text text-anchor="start" x="220" y="-491.8" font-family="Times,serif" font-size="14.00">$ctrl</text>
+<polygon fill="none" stroke="black" points="257,-485 257,-506 288,-506 288,-485 257,-485"/>
+<text text-anchor="start" x="260" y="-491.8" font-family="Times,serif" font-size="14.00">arg</text>
+<polygon fill="none" stroke="black" points="288,-485 288,-506 385,-506 385,-485 288,-485"/>
+<text text-anchor="start" x="291" y="-491.8" font-family="Times,serif" font-size="14.00">$Vector2D_x</text>
+<polygon fill="none" stroke="black" points="385,-485 385,-506 482,-506 482,-485 385,-485"/>
+<text text-anchor="start" x="388" y="-491.8" font-family="Times,serif" font-size="14.00">$Vector2D_y</text>
+</g>
+<!-- Stop3 -->
+<g id="node2" class="node">
+<title>Stop3</title>
+<polygon fill="yellow" stroke="black" points="386,-52 332,-52 332,-16 386,-16 386,-52"/>
+<text text-anchor="middle" x="359" y="-30.3" font-family="Times,serif" font-size="14.00">Stop</text>
+</g>
+<!-- Return24 -->
+<g id="node16" class="node">
+<title>Return24</title>
+<polygon fill="yellow" stroke="black" points="392,-124 326,-124 326,-88 392,-88 392,-124"/>
+<text text-anchor="middle" x="359" y="-102.3" font-family="Times,serif" font-size="14.00">Return</text>
+</g>
+<!-- Stop3&#45;&gt;Return24 -->
+<g id="edge1" class="edge">
+<title>Stop3&#45;&gt;Return24</title>
+<path fill="none" stroke="red" d="M359,-52.3C359,-60.02 359,-69.29 359,-77.89"/>
+<polygon fill="red" stroke="red" points="355.5,-77.9 359,-87.9 362.5,-77.9 355.5,-77.9"/>
+<text text-anchor="middle" x="356" y="-54.9" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
+</g>
+<!-- New8 -->
+<g id="node3" class="node">
+<title>New8</title>
+<ellipse fill="none" stroke="black" cx="247" cy="-408" rx="98.58" ry="18"/>
+<text text-anchor="middle" x="247" y="-404.3" font-family="Times,serif" font-size="14.00">New Ptr(Vector2D)</text>
+</g>
+<!-- New8&#45;&gt;Start2 -->
+<g id="edge2" class="edge">
+<title>New8&#45;&gt;Start2:p0</title>
+<path fill="none" stroke="green" d="M243.39,-426.19C241,-438.85 238.14,-456.79 237.27,-474"/>
+<polygon fill="green" stroke="green" points="233.77,-473.91 237,-484 240.76,-474.1 233.77,-473.91"/>
+<text text-anchor="middle" x="240.39" y="-428.79" 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="103" cy="-408" rx="27" ry="18"/>
+<text text-anchor="middle" x="103" y="-404.3" font-family="Times,serif" font-size="14.00">#0</text>
+</g>
+<!-- Store10 -->
+<g id="node5" class="node">
+<title>Store10</title>
+<ellipse fill="none" stroke="black" cx="124" cy="-322" rx="36.29" ry="18"/>
+<text text-anchor="middle" x="124" y="-318.3" font-family="Times,serif" font-size="14.00">Store</text>
+</g>
+<!-- Store10&#45;&gt;Start2 -->
+<g id="edge3" class="edge">
+<title>Store10&#45;&gt;Start2:p2</title>
+<path fill="none" stroke="black" d="M102.14,-336.54C89.28,-345.86 74.18,-359.52 67,-376 55.64,-402.08 48.58,-418.32 67,-440 103.77,-483.27 308.02,-430.84 333.41,-474.03"/>
+<polygon fill="black" stroke="black" points="330.1,-475.2 336,-484 336.87,-473.44 330.1,-475.2"/>
+<text text-anchor="middle" x="99.14" y="-339.14" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- Store10&#45;&gt;New8 -->
+<g id="edge4" class="edge">
+<title>Store10&#45;&gt;New8</title>
+<path fill="none" stroke="black" d="M144.59,-337.06C163.55,-350.01 191.99,-369.44 214.1,-384.53"/>
+<polygon fill="black" stroke="black" points="212.3,-387.54 222.53,-390.29 216.25,-381.76 212.3,-387.54"/>
+<text text-anchor="middle" x="141.59" y="-339.66" font-family="Helvetica,sans-Serif" font-size="8.00">2</text>
+</g>
+<!-- Store10&#45;&gt;Con_9 -->
+<g id="edge5" class="edge">
+<title>Store10&#45;&gt;Con_9</title>
+<path fill="none" stroke="black" d="M119.75,-340C116.88,-351.5 113.02,-366.93 109.73,-380.08"/>
+<polygon fill="black" stroke="black" points="106.27,-379.48 107.24,-390.03 113.06,-381.17 106.27,-379.48"/>
+<text text-anchor="middle" x="116.75" y="-342.6" font-family="Helvetica,sans-Serif" font-size="8.00">3</text>
+</g>
+<!-- Store11 -->
+<g id="node6" class="node">
+<title>Store11</title>
+<ellipse fill="none" stroke="black" cx="326" cy="-322" rx="36.29" ry="18"/>
+<text text-anchor="middle" x="326" y="-318.3" font-family="Times,serif" font-size="14.00">Store</text>
+</g>
+<!-- Store11&#45;&gt;Start2 -->
+<g id="edge6" class="edge">
+<title>Store11&#45;&gt;Start2:p3</title>
+<path fill="none" stroke="black" d="M336.29,-339.76C342.27,-349.98 349.67,-363.45 355,-376 366.65,-403.43 357.77,-415.68 375,-440 391.62,-463.46 424.6,-454.4 432.35,-474.07"/>
+<polygon fill="black" stroke="black" points="428.9,-474.71 434,-484 435.81,-473.56 428.9,-474.71"/>
+<text text-anchor="middle" x="333.29" y="-342.36" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- Store11&#45;&gt;New8 -->
+<g id="edge7" class="edge">
+<title>Store11&#45;&gt;New8</title>
+<path fill="none" stroke="black" d="M311.14,-338.8C299.53,-351.15 283.18,-368.53 269.89,-382.66"/>
+<polygon fill="black" stroke="black" points="267.18,-380.44 262.88,-390.12 272.28,-385.23 267.18,-380.44"/>
+<text text-anchor="middle" x="308.14" y="-341.4" font-family="Helvetica,sans-Serif" font-size="8.00">2</text>
+</g>
+<!-- Store11&#45;&gt;Con_9 -->
+<g id="edge8" class="edge">
+<title>Store11&#45;&gt;Con_9</title>
+<path fill="none" stroke="black" d="M294.91,-331.44C243.56,-345.42 145.98,-372.24 139,-376 134.01,-378.69 129.1,-382.17 124.59,-385.82"/>
+<polygon fill="black" stroke="black" points="122.22,-383.24 116.98,-392.45 126.82,-388.52 122.22,-383.24"/>
+<text text-anchor="middle" x="291.91" y="-334.04" font-family="Helvetica,sans-Serif" font-size="8.00">3</text>
+</g>
+<!-- Con_12 -->
+<g id="node7" class="node">
+<title>Con_12</title>
+<ellipse fill="none" stroke="black" cx="43" cy="-322" rx="27" ry="18"/>
+<text text-anchor="middle" x="43" y="-318.3" font-family="Times,serif" font-size="14.00">#1</text>
+</g>
+<!-- Store13 -->
+<g id="node8" class="node">
+<title>Store13</title>
+<ellipse fill="none" stroke="black" cx="146" cy="-250" rx="36.29" ry="18"/>
+<text text-anchor="middle" x="146" y="-246.3" font-family="Times,serif" font-size="14.00">Store</text>
+</g>
+<!-- Store13&#45;&gt;New8 -->
+<g id="edge10" class="edge">
+<title>Store13&#45;&gt;New8</title>
+<path fill="none" stroke="black" d="M153.54,-267.95C163.49,-290.05 180.7,-327.38 189,-340 198.92,-355.08 211.92,-370.41 223.11,-382.59"/>
+<polygon fill="black" stroke="black" points="220.75,-385.2 230.14,-390.11 225.86,-380.41 220.75,-385.2"/>
+<text text-anchor="middle" x="150.54" y="-270.55" font-family="Helvetica,sans-Serif" font-size="8.00">2</text>
+</g>
+<!-- Store13&#45;&gt;Store10 -->
+<g id="edge9" class="edge">
+<title>Store13&#45;&gt;Store10</title>
+<path fill="none" stroke="black" d="M140.67,-267.95C138.22,-275.74 135.26,-285.18 132.51,-293.92"/>
+<polygon fill="black" stroke="black" points="129.09,-293.14 129.43,-303.72 135.77,-295.23 129.09,-293.14"/>
+<text text-anchor="middle" x="137.67" y="-270.55" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- Store13&#45;&gt;Con_12 -->
+<g id="edge11" class="edge">
+<title>Store13&#45;&gt;Con_12</title>
+<path fill="none" stroke="black" d="M125.16,-265.17C109.24,-275.98 87.18,-290.98 69.92,-302.7"/>
+<polygon fill="black" stroke="black" points="67.7,-299.98 61.4,-308.5 71.63,-305.77 67.7,-299.98"/>
+<text text-anchor="middle" x="122.16" y="-267.77" font-family="Helvetica,sans-Serif" font-size="8.00">3</text>
+</g>
+<!-- If14 -->
+<g id="node9" class="node">
+<title>If14</title>
+<polygon fill="yellow" stroke="transparent" points="392,-411 392,-436 486,-436 486,-411 392,-411"/>
+<polygon fill="none" stroke="black" points="392,-411 392,-436 486,-436 486,-411 392,-411"/>
+<text text-anchor="start" x="433.5" y="-419.8" font-family="Times,serif" font-size="14.00">If</text>
+<polygon fill="none" stroke="black" points="392,-380 392,-411 486,-411 486,-380 392,-380"/>
+<polygon fill="yellow" stroke="transparent" points="397,-385 397,-406 437,-406 437,-385 397,-385"/>
+<polygon fill="none" stroke="black" points="397,-385 397,-406 437,-406 437,-385 397,-385"/>
+<text text-anchor="start" x="400" y="-391.8" font-family="Times,serif" font-size="14.00">True</text>
+<polygon fill="yellow" stroke="transparent" points="437,-385 437,-406 481,-406 481,-385 437,-385"/>
+<polygon fill="none" stroke="black" points="437,-385 437,-406 481,-406 481,-385 437,-385"/>
+<text text-anchor="start" x="440" y="-391.8" font-family="Times,serif" font-size="14.00">False</text>
+</g>
+<!-- If14&#45;&gt;Start2 -->
+<g id="edge12" class="edge">
+<title>If14&#45;&gt;Start2:p0</title>
+<path fill="none" stroke="red" d="M383.85,-434.38C379.19,-436.34 374.53,-438.24 370,-440 315.48,-461.2 246.02,-426.45 237.8,-473.79"/>
+<polygon fill="red" stroke="red" points="234.3,-473.76 237,-484 241.27,-474.31 234.3,-473.76"/>
+<text text-anchor="middle" x="380.85" y="-427.98" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
+</g>
+<!-- If14&#45;&gt;Start2 -->
+<g id="edge13" class="edge">
+<title>If14&#45;&gt;Start2:p1</title>
+<path fill="none" stroke="black" d="M383.86,-433.77C346.61,-450.49 300.83,-471.05 281.42,-479.77"/>
+<polygon fill="black" stroke="black" points="279.69,-476.71 272,-484 282.56,-483.1 279.69,-476.71"/>
+<text text-anchor="middle" x="380.86" y="-436.37" 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="225" cy="-322" rx="27" ry="18"/>
+<text text-anchor="middle" x="225" y="-318.3" font-family="Times,serif" font-size="14.00">#2</text>
+</g>
+<!-- Store19 -->
+<g id="node11" class="node">
+<title>Store19</title>
+<ellipse fill="none" stroke="black" cx="281" cy="-250" rx="36.29" ry="18"/>
+<text text-anchor="middle" x="281" y="-246.3" font-family="Times,serif" font-size="14.00">Store</text>
+</g>
+<!-- Store19&#45;&gt;New8 -->
+<g id="edge15" class="edge">
+<title>Store19&#45;&gt;New8</title>
+<path fill="none" stroke="black" d="M277.3,-267.99C271.45,-294.81 260.03,-347.21 253,-379.46"/>
+<polygon fill="black" stroke="black" points="249.51,-379.04 250.8,-389.55 256.35,-380.53 249.51,-379.04"/>
+<text text-anchor="middle" x="274.3" y="-270.59" font-family="Helvetica,sans-Serif" font-size="8.00">2</text>
+</g>
+<!-- Store19&#45;&gt;Store11 -->
+<g id="edge14" class="edge">
+<title>Store19&#45;&gt;Store11</title>
+<path fill="none" stroke="black" d="M291.66,-267.59C297.1,-276.05 303.83,-286.51 309.88,-295.92"/>
+<polygon fill="black" stroke="black" points="307.06,-298.01 315.41,-304.53 312.95,-294.23 307.06,-298.01"/>
+<text text-anchor="middle" x="288.66" y="-270.19" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- Store19&#45;&gt;Con_18 -->
+<g id="edge16" class="edge">
+<title>Store19&#45;&gt;Con_18</title>
+<path fill="none" stroke="black" d="M268.29,-266.88C261.03,-275.96 251.79,-287.51 243.71,-297.61"/>
+<polygon fill="black" stroke="black" points="240.79,-295.66 237.27,-305.66 246.25,-300.04 240.79,-295.66"/>
+<text text-anchor="middle" x="265.29" y="-269.48" font-family="Helvetica,sans-Serif" font-size="8.00">3</text>
+</g>
+<!-- Con_20 -->
+<g id="node12" class="node">
+<title>Con_20</title>
+<ellipse fill="none" stroke="black" cx="427" cy="-322" rx="27" ry="18"/>
+<text text-anchor="middle" x="427" y="-318.3" font-family="Times,serif" font-size="14.00">#3</text>
+</g>
+<!-- Store21 -->
+<g id="node13" class="node">
+<title>Store21</title>
+<ellipse fill="none" stroke="black" cx="381" cy="-250" rx="36.29" ry="18"/>
+<text text-anchor="middle" x="381" y="-246.3" font-family="Times,serif" font-size="14.00">Store</text>
+</g>
+<!-- Store21&#45;&gt;New8 -->
+<g id="edge18" class="edge">
+<title>Store21&#45;&gt;New8</title>
+<path fill="none" stroke="black" d="M383.48,-267.99C385.36,-287.16 385.53,-318.48 371,-340 355.77,-362.56 330.37,-378.17 306.51,-388.6"/>
+<polygon fill="black" stroke="black" points="305.01,-385.43 297.11,-392.49 307.69,-391.9 305.01,-385.43"/>
+<text text-anchor="middle" x="380.48" y="-270.59" font-family="Helvetica,sans-Serif" font-size="8.00">2</text>
+</g>
+<!-- Store21&#45;&gt;Store11 -->
+<g id="edge17" class="edge">
+<title>Store21&#45;&gt;Store11</title>
+<path fill="none" stroke="black" d="M368.24,-267.24C361.3,-276.07 352.58,-287.17 344.87,-296.99"/>
+<polygon fill="black" stroke="black" points="342.11,-294.83 338.69,-304.85 347.62,-299.15 342.11,-294.83"/>
+<text text-anchor="middle" x="365.24" y="-269.84" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- Store21&#45;&gt;Con_20 -->
+<g id="edge19" class="edge">
+<title>Store21&#45;&gt;Con_20</title>
+<path fill="none" stroke="black" d="M391.67,-267.24C397.44,-276.02 404.68,-287.04 411.1,-296.81"/>
+<polygon fill="black" stroke="black" points="408.29,-298.9 416.71,-305.33 414.14,-295.06 408.29,-298.9"/>
+<text text-anchor="middle" x="388.67" y="-269.84" font-family="Helvetica,sans-Serif" font-size="8.00">3</text>
+</g>
+<!-- Region22 -->
+<g id="node14" class="node">
+<title>Region22</title>
+<polygon fill="yellow" stroke="black" points="494,-196 428,-196 428,-160 494,-160 494,-196"/>
+<text text-anchor="middle" x="461" y="-174.3" font-family="Times,serif" font-size="14.00">Region</text>
+</g>
+<!-- Region22&#45;&gt;If14 -->
+<g id="edge20" class="edge">
+<title>Region22&#45;&gt;If14:p0</title>
+<path fill="none" stroke="red" d="M468.66,-196.11C481.18,-226.8 502.33,-292.46 478,-340 464.62,-366.15 427.64,-354.5 418.88,-373.99"/>
+<polygon fill="red" stroke="red" points="415.4,-373.53 417,-384 422.28,-374.82 415.4,-373.53"/>
+<text text-anchor="middle" x="465.66" y="-198.71" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- Phi_Vector2D_y23 -->
+<g id="node15" class="node">
+<title>Phi_Vector2D_y23</title>
+<ellipse fill="lightyellow" stroke="black" cx="310" cy="-178" rx="79.89" ry="18"/>
+<text text-anchor="middle" x="310" y="-174.3" font-family="Times,serif" font-size="14.00">φ_$Vector2D_y</text>
+</g>
+<!-- Phi_Vector2D_y23&#45;&gt;Store19 -->
+<g id="edge23" class="edge">
+<title>Phi_Vector2D_y23&#45;&gt;Store19</title>
+<path fill="none" stroke="black" d="M302.98,-195.95C299.63,-204.03 295.55,-213.88 291.82,-222.89"/>
+<polygon fill="black" stroke="black" points="288.55,-221.63 287.96,-232.21 295.02,-224.31 288.55,-221.63"/>
+<text text-anchor="middle" x="299.98" y="-198.55" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- Phi_Vector2D_y23&#45;&gt;Store21 -->
+<g id="edge24" class="edge">
+<title>Phi_Vector2D_y23&#45;&gt;Store21</title>
+<path fill="none" stroke="black" d="M327.19,-195.95C336.48,-205.1 348.07,-216.53 358.11,-226.43"/>
+<polygon fill="black" stroke="black" points="355.85,-229.12 365.42,-233.64 360.76,-224.13 355.85,-229.12"/>
+<text text-anchor="middle" x="324.19" y="-198.55" font-family="Helvetica,sans-Serif" font-size="8.00">2</text>
+</g>
+<!-- Phi_Vector2D_y23&#45;&gt;Region22 -->
+<g id="edge22" class="edge">
+<title>Phi_Vector2D_y23&#45;&gt;Region22</title>
+<path fill="none" stroke="black" stroke-dasharray="1,5" d="M390.22,-178C399.33,-178 408.43,-178 417.54,-178"/>
+<polygon fill="black" stroke="black" points="417.82,-181.5 427.82,-178 417.82,-174.5 417.82,-181.5"/>
+<text text-anchor="middle" x="393.22" y="-171.6" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
+</g>
+<!-- Return24&#45;&gt;New8 -->
+<g id="edge26" class="edge">
+<title>Return24&#45;&gt;New8</title>
+<path fill="none" stroke="black" d="M373.77,-124.44C381.82,-134.45 391.63,-147.49 399,-160 416.77,-190.17 455.73,-269.74 463,-304 466.32,-319.65 472.49,-327.12 463,-340 453.78,-352.52 373.24,-375.2 313.19,-390.68"/>
+<polygon fill="black" stroke="black" points="312.19,-387.32 303.37,-393.19 313.93,-394.1 312.19,-387.32"/>
+<text text-anchor="middle" x="370.77" y="-127.04" font-family="Helvetica,sans-Serif" font-size="8.00">1</text>
+</g>
+<!-- Return24&#45;&gt;Store13 -->
+<g id="edge27" class="edge">
+<title>Return24&#45;&gt;Store13</title>
+<path fill="none" stroke="black" d="M325.78,-114.7C296.32,-122.65 253.09,-137.15 221,-160 196.73,-177.27 175.54,-204.17 161.93,-223.87"/>
+<polygon fill="black" stroke="black" points="158.9,-222.11 156.23,-232.36 164.71,-226.01 158.9,-222.11"/>
+<text text-anchor="middle" x="322.78" y="-108.3" font-family="Helvetica,sans-Serif" font-size="8.00">2</text>
+</g>
+<!-- Return24&#45;&gt;Region22 -->
+<g id="edge25" class="edge">
+<title>Return24&#45;&gt;Region22</title>
+<path fill="none" stroke="red" d="M383.95,-124.12C397.04,-133.11 413.26,-144.24 427.44,-153.97"/>
+<polygon fill="red" stroke="red" points="425.73,-157.04 435.95,-159.81 429.69,-151.27 425.73,-157.04"/>
+<text text-anchor="middle" x="380.95" y="-126.72" font-family="Helvetica,sans-Serif" font-size="8.00">0</text>
+</g>
+<!-- Return24&#45;&gt;Phi_Vector2D_y23 -->
+<g id="edge28" class="edge">
+<title>Return24&#45;&gt;Phi_Vector2D_y23</title>
+<path fill="none" stroke="black" d="M346.89,-124.3C341.13,-132.53 334.14,-142.52 327.79,-151.58"/>
+<polygon fill="black" stroke="black" points="324.84,-149.7 321.97,-159.9 330.58,-153.71 324.84,-149.7"/>
+<text text-anchor="middle" x="343.89" y="-126.9" font-family="Helvetica,sans-Serif" font-size="8.00">3</text>
+</g>
+</g>
+</svg>
diff --git a/chapter10/docs/lattice.drawio b/chapter10/docs/lattice.drawio
new file mode 100644
index 00000000..cf20e9f3
--- /dev/null
+++ b/chapter10/docs/lattice.drawio
@@ -0,0 +1,379 @@
+<mxfile host="65bd71144e">
+ <diagram id="N4dXkE2PwwjNiNYLmPMg" name="Page-1">
+ <mxGraphModel dx="1868" dy="907" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
+ <root>
+ <mxCell id="0"/>
+ <mxCell id="1" parent="0"/>
+ <mxCell id="2" value="Top" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" parent="1" vertex="1">
+ <mxGeometry x="527.5" y="40" width="40" height="40" as="geometry"/>
+ </mxCell>
+ <mxCell id="3" value="Int Top" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" parent="1" vertex="1">
+ <mxGeometry x="140" y="195" width="50" height="50" as="geometry"/>
+ </mxCell>
+ <mxCell id="4" value="Int Bot" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" parent="1" vertex="1">
+ <mxGeometry x="140" y="350" width="50" height="50" as="geometry"/>
+ </mxCell>
+ <mxCell id="5" value="Bot" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" parent="1" vertex="1">
+ <mxGeometry x="524" y="710" width="47" height="47" as="geometry"/>
+ </mxCell>
+ <mxCell id="7" value="1" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
+ <mxGeometry x="100" y="280" width="30" height="30" as="geometry"/>
+ </mxCell>
+ <mxCell id="8" value="2" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
+ <mxGeometry x="130" y="278.85" width="30" height="30" as="geometry"/>
+ </mxCell>
+ <mxCell id="9" value="3" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
+ <mxGeometry x="170" y="278.85" width="30" height="30" as="geometry"/>
+ </mxCell>
+ <mxCell id="10" value="n" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
+ <mxGeometry x="220" y="278.85" width="20" height="30" as="geometry"/>
+ </mxCell>
+ <mxCell id="11" value="" style="endArrow=none;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;" parent="1" source="7" target="3" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="400" y="490" as="sourcePoint"/>
+ <mxPoint x="450" y="440" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="12" value="" style="endArrow=none;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" parent="1" source="4" target="7" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="110" y="360" as="sourcePoint"/>
+ <mxPoint x="160" y="310" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="14" value="" style="endArrow=none;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;" parent="1" source="8" target="3" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="300" y="640" as="sourcePoint"/>
+ <mxPoint x="350" y="590" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="15" value="" style="endArrow=none;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" parent="1" source="4" target="8" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="210" y="510" as="sourcePoint"/>
+ <mxPoint x="260" y="460" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="16" value="" style="endArrow=none;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;" parent="1" source="9" target="3" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="110" y="590" as="sourcePoint"/>
+ <mxPoint x="160" y="540" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="17" value="" style="endArrow=none;html=1;" parent="1" source="4" target="9" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="140" y="410" as="sourcePoint"/>
+ <mxPoint x="190" y="360" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="18" value="" style="endArrow=none;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;" parent="1" source="10" target="3" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="350" y="280" as="sourcePoint"/>
+ <mxPoint x="400" y="230" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="19" value="" style="endArrow=none;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" parent="1" source="4" target="10" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="280" y="380" as="sourcePoint"/>
+ <mxPoint x="330" y="330" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="20" value="" style="endArrow=none;html=1;" parent="1" source="3" target="2" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="280" y="200" as="sourcePoint"/>
+ <mxPoint x="330" y="150" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="21" value="" style="endArrow=none;html=1;" parent="1" source="5" target="4" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="180" y="540" as="sourcePoint"/>
+ <mxPoint x="230" y="490" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="22" value="*$TOP" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" parent="1" vertex="1">
+ <mxGeometry x="580" y="115" width="50" height="50" as="geometry"/>
+ </mxCell>
+ <mxCell id="23" value="*$BOT?" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" parent="1" vertex="1">
+ <mxGeometry x="562.5" y="540" width="75" height="75" as="geometry"/>
+ </mxCell>
+ <mxCell id="24" value="" style="endArrow=none;html=1;" parent="1" source="5" target="23" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="400" y="490" as="sourcePoint"/>
+ <mxPoint x="450" y="440" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="31" value="" style="endArrow=none;html=1;entryX=0;entryY=1;entryDx=0;entryDy=0;" parent="1" source="79" target="22" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="550" y="280" as="sourcePoint"/>
+ <mxPoint x="520" y="200" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="33" value="" style="endArrow=none;html=1;" parent="1" source="22" target="87" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="715" y="240" as="sourcePoint"/>
+ <mxPoint x="690" y="200" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="34" value="" style="endArrow=none;html=1;" parent="1" source="2" target="22" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="600" y="120" as="sourcePoint"/>
+ <mxPoint x="650" y="70" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="41" value="Struct&lt;br&gt;S1" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" parent="1" vertex="1">
+ <mxGeometry x="295" y="270" width="50" height="50" as="geometry"/>
+ </mxCell>
+ <mxCell id="42" value="Struct&lt;br&gt;S2" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" parent="1" vertex="1">
+ <mxGeometry x="370" y="270" width="50" height="50" as="geometry"/>
+ </mxCell>
+ <mxCell id="43" value="" style="endArrow=none;html=1;" parent="1" source="41" target="56" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="343" y="240" as="sourcePoint"/>
+ <mxPoint x="333" y="210" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="44" value="" style="endArrow=none;html=1;" parent="1" source="57" target="41" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="343" y="360" as="sourcePoint"/>
+ <mxPoint x="383" y="400" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="45" value="" style="endArrow=none;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=1;entryY=1;entryDx=0;entryDy=0;" parent="1" source="42" target="56" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="393" y="230" as="sourcePoint"/>
+ <mxPoint x="353" y="220" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="46" value="" style="endArrow=none;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" parent="1" source="57" target="42" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="373" y="360" as="sourcePoint"/>
+ <mxPoint x="463" y="360" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="47" value="Ctrl" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" parent="1" vertex="1">
+ <mxGeometry x="30" y="200" width="40" height="40" as="geometry"/>
+ </mxCell>
+ <mxCell id="48" value="~Ctrl" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" parent="1" vertex="1">
+ <mxGeometry x="30" y="360" width="40" height="40" as="geometry"/>
+ </mxCell>
+ <mxCell id="49" value="" style="endArrow=none;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;" parent="1" source="47" target="2" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="400" y="470" as="sourcePoint"/>
+ <mxPoint x="450" y="420" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="50" value="" style="endArrow=none;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;" parent="1" source="48" target="47" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="82" y="330" as="sourcePoint"/>
+ <mxPoint x="132" y="280" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="51" value="" style="endArrow=none;html=1;entryX=1;entryY=1;entryDx=0;entryDy=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="5" target="48" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="120" y="500" as="sourcePoint"/>
+ <mxPoint x="170" y="450" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="55" value="" style="endArrow=none;html=1;" parent="1" source="23" target="89" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="680" y="390" as="sourcePoint"/>
+ <mxPoint x="660" y="310" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="56" value="$TOP" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" parent="1" vertex="1">
+ <mxGeometry x="340" y="200" width="40" height="40" as="geometry"/>
+ </mxCell>
+ <mxCell id="57" value="$BOT" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" parent="1" vertex="1">
+ <mxGeometry x="340" y="348.07" width="45" height="45" as="geometry"/>
+ </mxCell>
+ <mxCell id="58" value="" style="endArrow=none;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" parent="1" source="56" target="2" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="400" y="490" as="sourcePoint"/>
+ <mxPoint x="450" y="440" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="59" value="" style="endArrow=none;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;" parent="1" source="5" target="57" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="540" y="710" as="sourcePoint"/>
+ <mxPoint x="590" y="660" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="61" value="MEM#TOP" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" parent="1" vertex="1">
+ <mxGeometry x="870" y="145" width="70" height="70" as="geometry"/>
+ </mxCell>
+ <mxCell id="62" value="#3" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
+ <mxGeometry x="870" y="303.85" width="40" height="30" as="geometry"/>
+ </mxCell>
+ <mxCell id="63" value="#4" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
+ <mxGeometry x="910" y="303.85" width="40" height="30" as="geometry"/>
+ </mxCell>
+ <mxCell id="64" value="#n" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
+ <mxGeometry x="960" y="303.85" width="50" height="30" as="geometry"/>
+ </mxCell>
+ <mxCell id="65" value="" style="endArrow=none;html=1;" parent="1" source="73" target="61" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="850" y="303.85" as="sourcePoint"/>
+ <mxPoint x="895.9153428657555" y="264.99855603666845" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="66" value="" style="endArrow=none;html=1;" parent="1" source="74" target="73" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="897.322330470336" y="381.1723304703361" as="sourcePoint"/>
+ <mxPoint x="850" y="333.85" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="67" value="" style="endArrow=none;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;" parent="1" source="62" target="61" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="990" y="663.85" as="sourcePoint"/>
+ <mxPoint x="901.5781126951365" y="269.941537193357" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="68" value="" style="endArrow=none;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" parent="1" source="74" target="62" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="903.1475044241415" y="376.8382225019774" as="sourcePoint"/>
+ <mxPoint x="950" y="483.85" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="69" value="" style="endArrow=none;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;" parent="1" source="63" target="61" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="800" y="613.85" as="sourcePoint"/>
+ <mxPoint x="912.7366063488612" y="273.7473301625265" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="70" value="" style="endArrow=none;html=1;" parent="1" source="74" target="63" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="910" y="430" as="sourcePoint"/>
+ <mxPoint x="880" y="383.85" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="71" value="" style="endArrow=none;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;" parent="1" source="64" target="61" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="1040" y="303.85" as="sourcePoint"/>
+ <mxPoint x="934.0846571342449" y="264.9985560366689" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="72" value="" style="endArrow=none;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;exitX=1;exitY=0;exitDx=0;exitDy=0;" parent="1" source="74" target="64" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="932.677669529664" y="381.1723304703361" as="sourcePoint"/>
+ <mxPoint x="1020" y="353.85" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="73" value="#2" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
+ <mxGeometry x="820" y="303.85" width="40" height="30" as="geometry"/>
+ </mxCell>
+ <mxCell id="74" value="MEM#BOT" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" parent="1" vertex="1">
+ <mxGeometry x="870" y="425" width="70" height="70" as="geometry"/>
+ </mxCell>
+ <mxCell id="75" value="" style="endArrow=none;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitX=0;exitY=0;exitDx=0;exitDy=0;" parent="1" source="61" target="2" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="1030" y="130" as="sourcePoint"/>
+ <mxPoint x="1080" y="80" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="76" value="" style="endArrow=none;html=1;entryX=0.219;entryY=0.957;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="5" target="74" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="610" y="640" as="sourcePoint"/>
+ <mxPoint x="660" y="590" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="79" value="Null" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" parent="1" vertex="1">
+ <mxGeometry x="450" y="296.15" width="51.92" height="51.92" as="geometry"/>
+ </mxCell>
+ <mxCell id="81" value="*void" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" parent="1" vertex="1">
+ <mxGeometry x="700" y="295.19" width="53.85" height="53.85" as="geometry"/>
+ </mxCell>
+ <mxCell id="82" value="" style="endArrow=none;html=1;" parent="1" source="23" target="81" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="540" y="550" as="sourcePoint"/>
+ <mxPoint x="760" y="310" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="85" value="" style="endArrow=none;html=1;" parent="1" source="23" target="79" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="555" y="340" as="sourcePoint"/>
+ <mxPoint x="690" y="230" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="87" value="*S1" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" parent="1" vertex="1">
+ <mxGeometry x="630" y="240" width="38.85" height="38.85" as="geometry"/>
+ </mxCell>
+ <mxCell id="88" value="" style="endArrow=none;html=1;" parent="1" source="79" target="89" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="620" y="290" as="sourcePoint"/>
+ <mxPoint x="650" y="290" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="89" value="*S1?" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" parent="1" vertex="1">
+ <mxGeometry x="700" y="430" width="50" height="50" as="geometry"/>
+ </mxCell>
+ <mxCell id="90" value="" style="endArrow=none;html=1;" parent="1" source="89" target="87" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="640" y="240" as="sourcePoint"/>
+ <mxPoint x="690" y="190" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="92" value="*S2" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" parent="1" vertex="1">
+ <mxGeometry x="540" y="240" width="40" height="40" as="geometry"/>
+ </mxCell>
+ <mxCell id="93" value="" style="endArrow=none;html=1;" parent="1" source="22" target="92" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="650" y="70" as="sourcePoint"/>
+ <mxPoint x="700" y="20" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="94" value="" style="endArrow=none;html=1;" parent="1" source="81" target="92" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="680" y="640" as="sourcePoint"/>
+ <mxPoint x="730" y="590" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="95" value="" style="endArrow=none;html=1;" parent="1" source="81" target="87" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="650" y="640" as="sourcePoint"/>
+ <mxPoint x="700" y="590" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="96" value="*S2?" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;" parent="1" vertex="1">
+ <mxGeometry x="598.85" y="430" width="50" height="50" as="geometry"/>
+ </mxCell>
+ <mxCell id="101" value="" style="endArrow=none;html=1;exitX=0;exitY=0;exitDx=0;exitDy=0;" parent="1" source="96" target="79" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="800" y="760" as="sourcePoint"/>
+ <mxPoint x="850" y="710" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="102" value="" style="endArrow=none;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;" parent="1" source="96" target="92" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="730" y="670" as="sourcePoint"/>
+ <mxPoint x="600" y="240" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="103" value="" style="endArrow=none;html=1;" parent="1" source="23" target="96" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="700" y="530" as="sourcePoint"/>
+ <mxPoint x="750" y="480" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="104" value="" style="endArrow=none;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;" parent="1" source="81" target="22" edge="1">
+ <mxGeometry width="50" height="50" relative="1" as="geometry">
+ <mxPoint x="210" y="720" as="sourcePoint"/>
+ <mxPoint x="610" y="160" as="targetPoint"/>
+ </mxGeometry>
+ </mxCell>
+ <mxCell id="105" value="Control" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
+ <mxGeometry x="20" y="165" width="60" height="30" as="geometry"/>
+ </mxCell>
+ <mxCell id="106" value="Integer" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
+ <mxGeometry x="140" y="165" width="60" height="30" as="geometry"/>
+ </mxCell>
+ <mxCell id="107" value="Struct" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
+ <mxGeometry x="330" y="165" width="60" height="30" as="geometry"/>
+ </mxCell>
+ <mxCell id="108" value="MemPtr" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
+ <mxGeometry x="580" y="85" width="60" height="30" as="geometry"/>
+ </mxCell>
+ <mxCell id="109" value="Mem" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
+ <mxGeometry x="875" y="115" width="60" height="30" as="geometry"/>
+ </mxCell>
+ </root>
+ </mxGraphModel>
+ </diagram>
+</mxfile> \ No newline at end of file
diff --git a/chapter10/docs/lattice.gv b/chapter10/docs/lattice.gv
new file mode 100644
index 00000000..406a62c3
--- /dev/null
+++ b/chapter10/docs/lattice.gv
@@ -0,0 +1,122 @@
+digraph Lattice {
+ // Define nodes
+ splines=false;
+ node [shape=circle, style=filled, fillcolor=lightblue];
+ nodesep=0.6;
+
+ TOP [label="⊤"];
+ BOT [label="⊥"];
+
+ CTRL [label="Ctrl"];
+ XCTRL [label="~Ctrl"];
+
+ INT_TOP [label="IntTop"];
+ INT_BOT [label="IntBot"];
+
+ INT_MIN [label="-n"];
+ INT_MINDOTS [label="..." shape=none style=none];
+ INT_MINUS [label="-1"];
+ INT_ZERO [label="0"];
+ INT_PLUS [label="1"];
+ INT_MAXDOTS [label="..." shape=none style=none];
+ INT_MAX [label="+n"];
+
+ STRUCT_TOP [label="$TOP"];
+ STRUCT_S1 [label="S1"];
+ STRUCT_S2 [label="S2"];
+ STRUCT_BOT [label="$BOT"]
+
+ PTR_TOP [label="*$TOP"];
+ PTR_NULL [label="null"];
+ PTR_VOIDPTR [label="*void"];
+ PTR_S1 [label="*S1"];
+ PTR_S2 [label="*S2"];
+ PTR_S1OPT [label="*S1?"];
+ PTR_S2OPT [label="*S2?"];
+ PTR_BOT [label="*$BOT?"];
+
+ MEM_TOP [label="MEM#TOP"];
+ MEM_ALIAS1 [label="#1"];
+ MEM_ALIAS2 [label="#2"];
+ MEM_ALIASDOTS [label="..." shape=none style=none];
+ MEM_ALIASN [label="#n"];
+ MEM_BOT [label="MEM#BOT"];
+
+ // Define edges
+ edge [arrowhead=none];
+
+ TOP -> CTRL;
+ CTRL -> XCTRL;
+ XCTRL -> BOT;
+
+ TOP -> INT_TOP;
+ INT_TOP -> INT_MIN;
+ INT_TOP -> INT_MINDOTS;
+ INT_TOP -> INT_MINUS;
+ INT_TOP -> INT_ZERO;
+ INT_TOP -> INT_PLUS;
+ INT_TOP -> INT_MAXDOTS;
+ INT_TOP -> INT_MAX;
+ INT_MIN -> INT_BOT;
+ INT_MINDOTS -> INT_BOT;
+ INT_MINUS -> INT_BOT;
+ INT_ZERO -> INT_BOT;
+ INT_PLUS -> INT_BOT;
+ INT_MAXDOTS -> INT_BOT;
+ INT_MAX -> INT_BOT;
+ INT_BOT -> BOT;
+
+ INT_MIN -> INT_MINDOTS -> INT_MINUS -> INT_ZERO -> INT_PLUS -> INT_MAXDOTS -> INT_MAX [style=invis];
+
+ TOP -> STRUCT_TOP;
+ STRUCT_TOP -> STRUCT_S1;
+ STRUCT_TOP -> STRUCT_S2;
+ STRUCT_S1 -> STRUCT_BOT;
+ STRUCT_S2 -> STRUCT_BOT;
+ STRUCT_BOT -> BOT;
+
+ TOP -> PTR_TOP;
+ PTR_TOP -> PTR_S1;
+ PTR_TOP -> PTR_S2;
+ PTR_TOP -> PTR_NULL;
+ PTR_TOP -> PTR_VOIDPTR;
+ PTR_NULL -> PTR_BOT;
+ PTR_NULL -> PTR_S1OPT;
+ PTR_NULL -> PTR_S2OPT;
+ PTR_S1 -> PTR_S1OPT;
+ PTR_S1 -> PTR_VOIDPTR;
+ PTR_S2 -> PTR_S2OPT;
+ PTR_S2 -> PTR_VOIDPTR;
+ PTR_VOIDPTR -> PTR_BOT;
+ PTR_S1OPT -> PTR_BOT;
+ PTR_S2OPT -> PTR_BOT;
+ PTR_BOT -> BOT;
+
+ TOP -> MEM_TOP;
+ MEM_TOP -> MEM_ALIAS1;
+ MEM_TOP -> MEM_ALIAS2;
+ MEM_TOP -> MEM_ALIASDOTS;
+ MEM_TOP -> MEM_ALIASN;
+ MEM_ALIAS1 -> MEM_BOT;
+ MEM_ALIAS2 -> MEM_BOT;
+ MEM_ALIASDOTS -> MEM_BOT;
+ MEM_ALIASN -> MEM_BOT;
+ MEM_BOT -> BOT;
+
+ MEM_ALIAS1 -> MEM_ALIAS2 -> MEM_ALIASDOTS -> MEM_ALIASN [style=invis];
+
+ // Additional customization
+ {rank=same; TOP}
+ {rank=same; INT_TOP; CTRL; STRUCT_TOP; PTR_TOP; MEM_TOP;}
+ {rank=same; PTR_S1; PTR_S2;}
+ {rank=same; INT_MIN; INT_MINDOTS;
+ INT_MINUS; INT_ZERO;
+ INT_PLUS; INT_MAXDOTS;
+ INT_MAX; STRUCT_S1; STRUCT_S2;
+ PTR_NULL; PTR_VOIDPTR;
+ MEM_ALIAS1; MEM_ALIAS2;
+ MEM_ALIASDOTS; MEM_ALIASN;}
+ {rank=same; PTR_S1OPT; PTR_S2OPT;}
+ {rank=same; INT_BOT; XCTRL; STRUCT_BOT; PTR_BOT; MEM_BOT;}
+ {rank=same; BOT;}
+} \ No newline at end of file
diff --git a/chapter10/docs/lattice.svg b/chapter10/docs/lattice.svg
new file mode 100644
index 00000000..5ec4bc16
--- /dev/null
+++ b/chapter10/docs/lattice.svg
@@ -0,0 +1,460 @@
+<?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: Lattice Pages: 1 -->
+<svg width="1512pt" height="745pt"
+ viewBox="0.00 0.00 1512.20 745.25" 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 741.25)">
+<title>Lattice</title>
+<polygon fill="white" stroke="transparent" points="-4,4 -4,-741.25 1508.2,-741.25 1508.2,4 -4,4"/>
+<!-- TOP -->
+<g id="node1" class="node">
+<title>TOP</title>
+<ellipse fill="lightblue" stroke="black" cx="677.5" cy="-717.76" rx="19.5" ry="19.5"/>
+<text text-anchor="middle" x="677.5" y="-714.06" font-family="Times,serif" font-size="14.00">⊤</text>
+</g>
+<!-- CTRL -->
+<g id="node3" class="node">
+<title>CTRL</title>
+<ellipse fill="lightblue" stroke="black" cx="53.5" cy="-598.56" rx="28.7" ry="28.7"/>
+<text text-anchor="middle" x="53.5" y="-594.86" font-family="Times,serif" font-size="14.00">Ctrl</text>
+</g>
+<!-- TOP&#45;&gt;CTRL -->
+<g id="edge1" class="edge">
+<title>TOP&#45;&gt;CTRL</title>
+<path fill="none" stroke="black" d="M658.48,-713.18C568.82,-696.34 188.15,-624.85 81.42,-604.81"/>
+</g>
+<!-- INT_TOP -->
+<g id="node5" class="node">
+<title>INT_TOP</title>
+<ellipse fill="lightblue" stroke="black" cx="326.5" cy="-598.56" rx="40.09" ry="40.09"/>
+<text text-anchor="middle" x="326.5" y="-594.86" font-family="Times,serif" font-size="14.00">IntTop</text>
+</g>
+<!-- TOP&#45;&gt;INT_TOP -->
+<g id="edge4" class="edge">
+<title>TOP&#45;&gt;INT_TOP</title>
+<path fill="none" stroke="black" d="M659.12,-710.62C604.04,-692.23 439.49,-637.29 364.5,-612.25"/>
+</g>
+<!-- STRUCT_TOP -->
+<g id="node14" class="node">
+<title>STRUCT_TOP</title>
+<ellipse fill="lightblue" stroke="black" cx="677.5" cy="-598.56" rx="36.29" ry="36.29"/>
+<text text-anchor="middle" x="677.5" y="-594.86" font-family="Times,serif" font-size="14.00">$TOP</text>
+</g>
+<!-- TOP&#45;&gt;STRUCT_TOP -->
+<g id="edge26" class="edge">
+<title>TOP&#45;&gt;STRUCT_TOP</title>
+<path fill="none" stroke="black" d="M677.5,-698.19C677.5,-681.28 677.5,-655.85 677.5,-635.05"/>
+</g>
+<!-- PTR_TOP -->
+<g id="node18" class="node">
+<title>PTR_TOP</title>
+<ellipse fill="lightblue" stroke="black" cx="909.5" cy="-598.56" rx="40.09" ry="40.09"/>
+<text text-anchor="middle" x="909.5" y="-594.86" font-family="Times,serif" font-size="14.00">*$TOP</text>
+</g>
+<!-- TOP&#45;&gt;PTR_TOP -->
+<g id="edge32" class="edge">
+<title>TOP&#45;&gt;PTR_TOP</title>
+<path fill="none" stroke="black" d="M694.56,-708.14C732.23,-689.11 823.19,-643.16 873.88,-617.56"/>
+</g>
+<!-- MEM_TOP -->
+<g id="node26" class="node">
+<title>MEM_TOP</title>
+<ellipse fill="lightblue" stroke="black" cx="1289.5" cy="-598.56" rx="63.89" ry="63.89"/>
+<text text-anchor="middle" x="1289.5" y="-594.86" font-family="Times,serif" font-size="14.00">MEM#TOP</text>
+</g>
+<!-- TOP&#45;&gt;MEM_TOP -->
+<g id="edge48" class="edge">
+<title>TOP&#45;&gt;MEM_TOP</title>
+<path fill="none" stroke="black" d="M696.49,-713.12C776.94,-697.71 1091.76,-637.43 1227.1,-611.51"/>
+</g>
+<!-- BOT -->
+<g id="node2" class="node">
+<title>BOT</title>
+<ellipse fill="lightblue" stroke="black" cx="676.5" cy="-19.5" rx="19.5" ry="19.5"/>
+<text text-anchor="middle" x="676.5" y="-15.8" font-family="Times,serif" font-size="14.00">⊥</text>
+</g>
+<!-- XCTRL -->
+<g id="node4" class="node">
+<title>XCTRL</title>
+<ellipse fill="lightblue" stroke="black" cx="49.5" cy="-138.69" rx="36.29" ry="36.29"/>
+<text text-anchor="middle" x="49.5" y="-134.99" font-family="Times,serif" font-size="14.00">~Ctrl</text>
+</g>
+<!-- CTRL&#45;&gt;XCTRL -->
+<g id="edge2" class="edge">
+<title>CTRL&#45;&gt;XCTRL</title>
+<path fill="none" stroke="black" d="M53.25,-569.8C52.55,-489.67 50.59,-264 49.82,-175.22"/>
+</g>
+<!-- XCTRL&#45;&gt;BOT -->
+<g id="edge3" class="edge">
+<title>XCTRL&#45;&gt;BOT</title>
+<path fill="none" stroke="black" d="M85.46,-130.97C202.43,-109.11 569.57,-40.49 657.46,-24.06"/>
+</g>
+<!-- INT_MIN -->
+<g id="node7" class="node">
+<title>INT_MIN</title>
+<ellipse fill="lightblue" stroke="black" cx="19.5" cy="-373.83" rx="19.5" ry="19.5"/>
+<text text-anchor="middle" x="19.5" y="-370.13" font-family="Times,serif" font-size="14.00">&#45;n</text>
+</g>
+<!-- INT_TOP&#45;&gt;INT_MIN -->
+<g id="edge5" class="edge">
+<title>INT_TOP&#45;&gt;INT_MIN</title>
+<path fill="none" stroke="black" d="M293.93,-574.72C228.5,-526.83 83.24,-420.49 35.23,-385.35"/>
+</g>
+<!-- INT_MINDOTS -->
+<g id="node8" class="node">
+<title>INT_MINDOTS</title>
+<text text-anchor="middle" x="108.5" y="-370.13" font-family="Times,serif" font-size="14.00">...</text>
+</g>
+<!-- INT_TOP&#45;&gt;INT_MINDOTS -->
+<g id="edge6" class="edge">
+<title>INT_TOP&#45;&gt;INT_MINDOTS</title>
+<path fill="none" stroke="black" d="M298,-569.18C252.29,-522.07 163.93,-430.97 126.23,-392.11"/>
+</g>
+<!-- INT_MINUS -->
+<g id="node9" class="node">
+<title>INT_MINUS</title>
+<ellipse fill="lightblue" stroke="black" cx="197.5" cy="-373.83" rx="19.5" ry="19.5"/>
+<text text-anchor="middle" x="197.5" y="-370.13" font-family="Times,serif" font-size="14.00">&#45;1</text>
+</g>
+<!-- INT_TOP&#45;&gt;INT_MINUS -->
+<g id="edge7" class="edge">
+<title>INT_TOP&#45;&gt;INT_MINUS</title>
+<path fill="none" stroke="black" d="M306.34,-563.45C278.21,-514.44 228.26,-427.43 207.25,-390.81"/>
+</g>
+<!-- INT_ZERO -->
+<g id="node10" class="node">
+<title>INT_ZERO</title>
+<ellipse fill="lightblue" stroke="black" cx="281.5" cy="-373.83" rx="18" ry="18"/>
+<text text-anchor="middle" x="281.5" y="-370.13" font-family="Times,serif" font-size="14.00">0</text>
+</g>
+<!-- INT_TOP&#45;&gt;INT_ZERO -->
+<g id="edge8" class="edge">
+<title>INT_TOP&#45;&gt;INT_ZERO</title>
+<path fill="none" stroke="black" d="M318.56,-558.9C308.69,-509.63 292.28,-427.68 285.08,-391.7"/>
+</g>
+<!-- INT_PLUS -->
+<g id="node11" class="node">
+<title>INT_PLUS</title>
+<ellipse fill="lightblue" stroke="black" cx="360.5" cy="-373.83" rx="18" ry="18"/>
+<text text-anchor="middle" x="360.5" y="-370.13" font-family="Times,serif" font-size="14.00">1</text>
+</g>
+<!-- INT_TOP&#45;&gt;INT_PLUS -->
+<g id="edge9" class="edge">
+<title>INT_TOP&#45;&gt;INT_PLUS</title>
+<path fill="none" stroke="black" d="M332.54,-558.64C340,-509.33 352.37,-427.55 357.8,-391.65"/>
+</g>
+<!-- INT_MAXDOTS -->
+<g id="node12" class="node">
+<title>INT_MAXDOTS</title>
+<text text-anchor="middle" x="448.5" y="-370.13" font-family="Times,serif" font-size="14.00">...</text>
+</g>
+<!-- INT_TOP&#45;&gt;INT_MAXDOTS -->
+<g id="edge10" class="edge">
+<title>INT_TOP&#45;&gt;INT_MAXDOTS</title>
+<path fill="none" stroke="black" d="M345.83,-562.95C372.23,-514.31 418.59,-428.91 438.72,-391.84"/>
+</g>
+<!-- INT_MAX -->
+<g id="node13" class="node">
+<title>INT_MAX</title>
+<ellipse fill="lightblue" stroke="black" cx="543.5" cy="-373.83" rx="24.9" ry="24.9"/>
+<text text-anchor="middle" x="543.5" y="-370.13" font-family="Times,serif" font-size="14.00">+n</text>
+</g>
+<!-- INT_TOP&#45;&gt;INT_MAX -->
+<g id="edge11" class="edge">
+<title>INT_TOP&#45;&gt;INT_MAX</title>
+<path fill="none" stroke="black" d="M354.87,-569.18C400.48,-521.95 488.77,-430.51 526.13,-391.82"/>
+</g>
+<!-- INT_BOT -->
+<g id="node6" class="node">
+<title>INT_BOT</title>
+<ellipse fill="lightblue" stroke="black" cx="326.5" cy="-138.69" rx="40.09" ry="40.09"/>
+<text text-anchor="middle" x="326.5" y="-134.99" font-family="Times,serif" font-size="14.00">IntBot</text>
+</g>
+<!-- INT_BOT&#45;&gt;BOT -->
+<g id="edge19" class="edge">
+<title>INT_BOT&#45;&gt;BOT</title>
+<path fill="none" stroke="black" d="M364.62,-124.93C439.56,-99.83 603.55,-44.92 658.3,-26.59"/>
+</g>
+<!-- INT_MIN&#45;&gt;INT_BOT -->
+<g id="edge12" class="edge">
+<title>INT_MIN&#45;&gt;INT_BOT</title>
+<path fill="none" stroke="black" d="M35.14,-361.84C83.16,-325.06 229.13,-213.27 294.38,-163.29"/>
+</g>
+<!-- INT_MIN&#45;&gt;INT_MINDOTS -->
+<!-- INT_MINDOTS&#45;&gt;INT_BOT -->
+<g id="edge13" class="edge">
+<title>INT_MINDOTS&#45;&gt;INT_BOT</title>
+<path fill="none" stroke="black" d="M125.24,-355.77C162.63,-315.44 253.21,-217.74 299.01,-168.34"/>
+</g>
+<!-- INT_MINDOTS&#45;&gt;INT_MINUS -->
+<!-- INT_MINUS&#45;&gt;INT_BOT -->
+<g id="edge14" class="edge">
+<title>INT_MINUS&#45;&gt;INT_BOT</title>
+<path fill="none" stroke="black" d="M206.98,-356.54C227.99,-318.25 278.79,-225.65 306.93,-174.37"/>
+</g>
+<!-- INT_MINUS&#45;&gt;INT_ZERO -->
+<!-- INT_ZERO&#45;&gt;INT_BOT -->
+<g id="edge15" class="edge">
+<title>INT_ZERO&#45;&gt;INT_BOT</title>
+<path fill="none" stroke="black" d="M284.95,-355.77C292.18,-318.02 309.02,-230.01 318.9,-178.39"/>
+</g>
+<!-- INT_ZERO&#45;&gt;INT_PLUS -->
+<!-- INT_PLUS&#45;&gt;INT_BOT -->
+<g id="edge16" class="edge">
+<title>INT_PLUS&#45;&gt;INT_BOT</title>
+<path fill="none" stroke="black" d="M357.89,-355.77C352.44,-318.13 339.77,-230.51 332.3,-178.83"/>
+</g>
+<!-- INT_PLUS&#45;&gt;INT_MAXDOTS -->
+<!-- INT_MAXDOTS&#45;&gt;INT_BOT -->
+<g id="edge17" class="edge">
+<title>INT_MAXDOTS&#45;&gt;INT_BOT</title>
+<path fill="none" stroke="black" d="M439.13,-355.77C419.04,-317.05 371.52,-225.46 345.07,-174.49"/>
+</g>
+<!-- INT_MAXDOTS&#45;&gt;INT_MAX -->
+<!-- INT_MAX&#45;&gt;INT_BOT -->
+<g id="edge18" class="edge">
+<title>INT_MAX&#45;&gt;INT_BOT</title>
+<path fill="none" stroke="black" d="M526.47,-355.38C489.1,-314.89 399.62,-217.93 354.11,-168.61"/>
+</g>
+<!-- STRUCT_S1 -->
+<g id="node15" class="node">
+<title>STRUCT_S1</title>
+<ellipse fill="lightblue" stroke="black" cx="633.5" cy="-373.83" rx="23" ry="23"/>
+<text text-anchor="middle" x="633.5" y="-370.13" font-family="Times,serif" font-size="14.00">S1</text>
+</g>
+<!-- STRUCT_TOP&#45;&gt;STRUCT_S1 -->
+<g id="edge27" class="edge">
+<title>STRUCT_TOP&#45;&gt;STRUCT_S1</title>
+<path fill="none" stroke="black" d="M670.43,-562.46C661.25,-515.55 645.49,-435.09 637.88,-396.19"/>
+</g>
+<!-- STRUCT_S2 -->
+<g id="node16" class="node">
+<title>STRUCT_S2</title>
+<ellipse fill="lightblue" stroke="black" cx="721.5" cy="-373.83" rx="23" ry="23"/>
+<text text-anchor="middle" x="721.5" y="-370.13" font-family="Times,serif" font-size="14.00">S2</text>
+</g>
+<!-- STRUCT_TOP&#45;&gt;STRUCT_S2 -->
+<g id="edge28" class="edge">
+<title>STRUCT_TOP&#45;&gt;STRUCT_S2</title>
+<path fill="none" stroke="black" d="M684.57,-562.46C693.75,-515.55 709.5,-435.09 717.12,-396.19"/>
+</g>
+<!-- STRUCT_BOT -->
+<g id="node17" class="node">
+<title>STRUCT_BOT</title>
+<ellipse fill="lightblue" stroke="black" cx="676.5" cy="-138.69" rx="36.29" ry="36.29"/>
+<text text-anchor="middle" x="676.5" y="-134.99" font-family="Times,serif" font-size="14.00">$BOT</text>
+</g>
+<!-- STRUCT_S1&#45;&gt;STRUCT_BOT -->
+<g id="edge29" class="edge">
+<title>STRUCT_S1&#45;&gt;STRUCT_BOT</title>
+<path fill="none" stroke="black" d="M637.62,-351.28C645.08,-310.47 660.89,-224.05 669.88,-174.89"/>
+</g>
+<!-- STRUCT_S2&#45;&gt;STRUCT_BOT -->
+<g id="edge30" class="edge">
+<title>STRUCT_S2&#45;&gt;STRUCT_BOT</title>
+<path fill="none" stroke="black" d="M717.18,-351.28C709.35,-310.36 692.74,-223.57 683.35,-174.47"/>
+</g>
+<!-- STRUCT_BOT&#45;&gt;BOT -->
+<g id="edge31" class="edge">
+<title>STRUCT_BOT&#45;&gt;BOT</title>
+<path fill="none" stroke="black" d="M676.5,-102.21C676.5,-81.46 676.5,-56.11 676.5,-39.2"/>
+</g>
+<!-- PTR_NULL -->
+<g id="node19" class="node">
+<title>PTR_NULL</title>
+<ellipse fill="lightblue" stroke="black" cx="815.5" cy="-373.83" rx="28.7" ry="28.7"/>
+<text text-anchor="middle" x="815.5" y="-370.13" font-family="Times,serif" font-size="14.00">null</text>
+</g>
+<!-- PTR_TOP&#45;&gt;PTR_NULL -->
+<g id="edge35" class="edge">
+<title>PTR_TOP&#45;&gt;PTR_NULL</title>
+<path fill="none" stroke="black" d="M893.87,-561.2C874.87,-515.77 843.27,-440.23 826.59,-400.36"/>
+</g>
+<!-- PTR_VOIDPTR -->
+<g id="node20" class="node">
+<title>PTR_VOIDPTR</title>
+<ellipse fill="lightblue" stroke="black" cx="1008.5" cy="-373.83" rx="34.39" ry="34.39"/>
+<text text-anchor="middle" x="1008.5" y="-370.13" font-family="Times,serif" font-size="14.00">*void</text>
+</g>
+<!-- PTR_TOP&#45;&gt;PTR_VOIDPTR -->
+<g id="edge36" class="edge">
+<title>PTR_TOP&#45;&gt;PTR_VOIDPTR</title>
+<path fill="none" stroke="black" d="M925.85,-561.45C945.01,-517.94 976.45,-446.57 994.53,-405.54"/>
+</g>
+<!-- PTR_S1 -->
+<g id="node21" class="node">
+<title>PTR_S1</title>
+<ellipse fill="lightblue" stroke="black" cx="909.5" cy="-471.57" rx="27.1" ry="27.1"/>
+<text text-anchor="middle" x="909.5" y="-467.87" font-family="Times,serif" font-size="14.00">*S1</text>
+</g>
+<!-- PTR_TOP&#45;&gt;PTR_S1 -->
+<g id="edge33" class="edge">
+<title>PTR_TOP&#45;&gt;PTR_S1</title>
+<path fill="none" stroke="black" d="M909.5,-558.01C909.5,-538.85 909.5,-516.34 909.5,-499.23"/>
+</g>
+<!-- PTR_S2 -->
+<g id="node22" class="node">
+<title>PTR_S2</title>
+<ellipse fill="lightblue" stroke="black" cx="1093.5" cy="-471.57" rx="27.1" ry="27.1"/>
+<text text-anchor="middle" x="1093.5" y="-467.87" font-family="Times,serif" font-size="14.00">*S2</text>
+</g>
+<!-- PTR_TOP&#45;&gt;PTR_S2 -->
+<g id="edge34" class="edge">
+<title>PTR_TOP&#45;&gt;PTR_S2</title>
+<path fill="none" stroke="black" d="M942.39,-575.22C979.61,-549.94 1039.19,-509.46 1071.29,-487.66"/>
+</g>
+<!-- PTR_S1OPT -->
+<g id="node23" class="node">
+<title>PTR_S1OPT</title>
+<ellipse fill="lightblue" stroke="black" cx="904.5" cy="-270.88" rx="32.49" ry="32.49"/>
+<text text-anchor="middle" x="904.5" y="-267.18" font-family="Times,serif" font-size="14.00">*S1?</text>
+</g>
+<!-- PTR_NULL&#45;&gt;PTR_S1OPT -->
+<g id="edge38" class="edge">
+<title>PTR_NULL&#45;&gt;PTR_S1OPT</title>
+<path fill="none" stroke="black" d="M833.94,-351.91C848.3,-335.63 868.24,-313 883.27,-295.96"/>
+</g>
+<!-- PTR_S2OPT -->
+<g id="node24" class="node">
+<title>PTR_S2OPT</title>
+<ellipse fill="lightblue" stroke="black" cx="1012.5" cy="-270.88" rx="32.49" ry="32.49"/>
+<text text-anchor="middle" x="1012.5" y="-267.18" font-family="Times,serif" font-size="14.00">*S2?</text>
+</g>
+<!-- PTR_NULL&#45;&gt;PTR_S2OPT -->
+<g id="edge39" class="edge">
+<title>PTR_NULL&#45;&gt;PTR_S2OPT</title>
+<path fill="none" stroke="black" d="M840.85,-359.84C877.53,-341.04 945.23,-306.35 983.73,-286.62"/>
+</g>
+<!-- PTR_BOT -->
+<g id="node25" class="node">
+<title>PTR_BOT</title>
+<ellipse fill="lightblue" stroke="black" cx="904.5" cy="-138.69" rx="46.29" ry="46.29"/>
+<text text-anchor="middle" x="904.5" y="-134.99" font-family="Times,serif" font-size="14.00">*$BOT?</text>
+</g>
+<!-- PTR_NULL&#45;&gt;PTR_BOT -->
+<g id="edge37" class="edge">
+<title>PTR_NULL&#45;&gt;PTR_BOT</title>
+<path fill="none" stroke="black" d="M825.71,-346.85C840.96,-306.56 869.84,-230.25 888.08,-182.07"/>
+</g>
+<!-- PTR_VOIDPTR&#45;&gt;PTR_BOT -->
+<g id="edge44" class="edge">
+<title>PTR_VOIDPTR&#45;&gt;PTR_BOT</title>
+<path fill="none" stroke="black" d="M994.47,-342.11C976.06,-300.5 943.7,-227.32 923.22,-181.01"/>
+</g>
+<!-- PTR_S1&#45;&gt;PTR_VOIDPTR -->
+<g id="edge41" class="edge">
+<title>PTR_S1&#45;&gt;PTR_VOIDPTR</title>
+<path fill="none" stroke="black" d="M929.06,-451.65C944.84,-436.4 967.12,-414.85 984.07,-398.46"/>
+</g>
+<!-- PTR_S1&#45;&gt;PTR_S1OPT -->
+<g id="edge40" class="edge">
+<title>PTR_S1&#45;&gt;PTR_S1OPT</title>
+<path fill="none" stroke="black" d="M908.81,-444.09C907.9,-407.48 906.29,-342.81 905.32,-303.69"/>
+</g>
+<!-- PTR_S2&#45;&gt;PTR_VOIDPTR -->
+<g id="edge43" class="edge">
+<title>PTR_S2&#45;&gt;PTR_VOIDPTR</title>
+<path fill="none" stroke="black" d="M1075.47,-450.27C1062.51,-435.66 1044.94,-415.87 1031.05,-400.22"/>
+</g>
+<!-- PTR_S2&#45;&gt;PTR_S2OPT -->
+<g id="edge42" class="edge">
+<title>PTR_S2&#45;&gt;PTR_S2OPT</title>
+<path fill="none" stroke="black" d="M1083.24,-446.15C1068.34,-409.24 1040.75,-340.89 1024.79,-301.33"/>
+</g>
+<!-- PTR_S1OPT&#45;&gt;PTR_BOT -->
+<g id="edge45" class="edge">
+<title>PTR_S1OPT&#45;&gt;PTR_BOT</title>
+<path fill="none" stroke="black" d="M904.5,-238.37C904.5,-222.41 904.5,-202.75 904.5,-185.19"/>
+</g>
+<!-- PTR_S2OPT&#45;&gt;PTR_BOT -->
+<g id="edge46" class="edge">
+<title>PTR_S2OPT&#45;&gt;PTR_BOT</title>
+<path fill="none" stroke="black" d="M992.18,-245.39C975.69,-225.51 952.2,-197.2 933.69,-174.88"/>
+</g>
+<!-- PTR_BOT&#45;&gt;BOT -->
+<g id="edge47" class="edge">
+<title>PTR_BOT&#45;&gt;BOT</title>
+<path fill="none" stroke="black" d="M863.74,-116.74C813.14,-90.73 729.17,-47.57 693.44,-29.2"/>
+</g>
+<!-- MEM_ALIAS1 -->
+<g id="node27" class="node">
+<title>MEM_ALIAS1</title>
+<ellipse fill="lightblue" stroke="black" cx="1197.5" cy="-373.83" rx="24.9" ry="24.9"/>
+<text text-anchor="middle" x="1197.5" y="-370.13" font-family="Times,serif" font-size="14.00">#1</text>
+</g>
+<!-- MEM_TOP&#45;&gt;MEM_ALIAS1 -->
+<g id="edge49" class="edge">
+<title>MEM_TOP&#45;&gt;MEM_ALIAS1</title>
+<path fill="none" stroke="black" d="M1265.33,-539.52C1246.21,-492.83 1220.56,-430.15 1206.93,-396.86"/>
+</g>
+<!-- MEM_ALIAS2 -->
+<g id="node28" class="node">
+<title>MEM_ALIAS2</title>
+<ellipse fill="lightblue" stroke="black" cx="1289.5" cy="-373.83" rx="24.9" ry="24.9"/>
+<text text-anchor="middle" x="1289.5" y="-370.13" font-family="Times,serif" font-size="14.00">#2</text>
+</g>
+<!-- MEM_TOP&#45;&gt;MEM_ALIAS2 -->
+<g id="edge50" class="edge">
+<title>MEM_TOP&#45;&gt;MEM_ALIAS2</title>
+<path fill="none" stroke="black" d="M1289.5,-534.76C1289.5,-489.47 1289.5,-431.15 1289.5,-398.6"/>
+</g>
+<!-- MEM_ALIASDOTS -->
+<g id="node29" class="node">
+<title>MEM_ALIASDOTS</title>
+<text text-anchor="middle" x="1384.5" y="-370.13" font-family="Times,serif" font-size="14.00">...</text>
+</g>
+<!-- MEM_TOP&#45;&gt;MEM_ALIASDOTS -->
+<g id="edge51" class="edge">
+<title>MEM_TOP&#45;&gt;MEM_ALIASDOTS</title>
+<path fill="none" stroke="black" d="M1314.33,-539.81C1335.12,-490.65 1363.46,-423.61 1376.87,-391.88"/>
+</g>
+<!-- MEM_ALIASN -->
+<g id="node30" class="node">
+<title>MEM_ALIASN</title>
+<ellipse fill="lightblue" stroke="black" cx="1479.5" cy="-373.83" rx="24.9" ry="24.9"/>
+<text text-anchor="middle" x="1479.5" y="-370.13" font-family="Times,serif" font-size="14.00">#n</text>
+</g>
+<!-- MEM_TOP&#45;&gt;MEM_ALIASN -->
+<g id="edge52" class="edge">
+<title>MEM_TOP&#45;&gt;MEM_ALIASN</title>
+<path fill="none" stroke="black" d="M1330.73,-549.8C1372.31,-500.61 1434.39,-427.19 1463.43,-392.83"/>
+</g>
+<!-- MEM_ALIAS1&#45;&gt;MEM_ALIAS2 -->
+<!-- MEM_BOT -->
+<g id="node31" class="node">
+<title>MEM_BOT</title>
+<ellipse fill="lightblue" stroke="black" cx="1289.5" cy="-138.69" rx="63.89" ry="63.89"/>
+<text text-anchor="middle" x="1289.5" y="-134.99" font-family="Times,serif" font-size="14.00">MEM#BOT</text>
+</g>
+<!-- MEM_ALIAS1&#45;&gt;MEM_BOT -->
+<g id="edge53" class="edge">
+<title>MEM_ALIAS1&#45;&gt;MEM_BOT</title>
+<path fill="none" stroke="black" d="M1206.66,-350.42C1220.43,-315.22 1246.91,-247.53 1266.24,-198.13"/>
+</g>
+<!-- MEM_ALIAS2&#45;&gt;MEM_ALIASDOTS -->
+<!-- MEM_ALIAS2&#45;&gt;MEM_BOT -->
+<g id="edge54" class="edge">
+<title>MEM_ALIAS2&#45;&gt;MEM_BOT</title>
+<path fill="none" stroke="black" d="M1289.5,-349.11C1289.5,-314.68 1289.5,-251.08 1289.5,-202.78"/>
+</g>
+<!-- MEM_ALIASDOTS&#45;&gt;MEM_ALIASN -->
+<!-- MEM_ALIASDOTS&#45;&gt;MEM_BOT -->
+<g id="edge55" class="edge">
+<title>MEM_ALIASDOTS&#45;&gt;MEM_BOT</title>
+<path fill="none" stroke="black" d="M1377.2,-355.77C1363.73,-322.43 1334.42,-249.88 1313.39,-197.83"/>
+</g>
+<!-- MEM_ALIASN&#45;&gt;MEM_BOT -->
+<g id="edge56" class="edge">
+<title>MEM_ALIASN&#45;&gt;MEM_BOT</title>
+<path fill="none" stroke="black" d="M1463.95,-354.58C1434.88,-318.61 1371.46,-240.12 1329.63,-188.36"/>
+</g>
+<!-- MEM_BOT&#45;&gt;BOT -->
+<g id="edge57" class="edge">
+<title>MEM_BOT&#45;&gt;BOT</title>
+<path fill="none" stroke="black" d="M1227.06,-125.75C1091.6,-99.86 776.38,-39.59 695.62,-24.15"/>
+</g>
+</g>
+</svg>
diff --git a/chapter10/pom.xml b/chapter10/pom.xml
new file mode 100644
index 00000000..fccb1140
--- /dev/null
+++ b/chapter10/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>chapter10</artifactId>
+ <packaging>jar</packaging>
+ <name>Chapter 10</name>
+</project> \ No newline at end of file
diff --git a/chapter10/src/main/java/com/seaofnodes/simple/GraphEvaluator.java b/chapter10/src/main/java/com/seaofnodes/simple/GraphEvaluator.java
new file mode 100644
index 00000000..9fc51c8e
--- /dev/null
+++ b/chapter10/src/main/java/com/seaofnodes/simple/GraphEvaluator.java
@@ -0,0 +1,181 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.HashMap;
+
+public class GraphEvaluator {
+
+ public enum ResultType { VALUE, FALLTHROUGH, TIMEOUT }
+
+ public record Result(ResultType type, long value) {}
+
+ /**
+ * Find the start node from some node in the graph or null if there is no start node
+ */
+ private static StartNode findStart(BitSet visit, Node node) {
+ if (node == null) return null;
+ if (node instanceof StartNode start) return start;
+ if( visit.get(node._nid) ) return null;
+ visit.set(node._nid);
+ for( Node def : node._inputs ) {
+ var res = findStart(visit,def);
+ if( res != null ) return res;
+ }
+ for( Node use : node._outputs ) {
+ var res = findStart(visit,use);
+ if( res != null ) return res;
+ }
+ return null;
+ }
+
+ /**
+ * Find the control output from a control node
+ */
+ private static Node findControl(Node control) {
+ for( Node use : control._outputs ) {
+ if (use.isCFG()) return use;
+ }
+ return null;
+ }
+
+ /**
+ * Find the projection for a node
+ */
+ private static ProjNode findProjection(Node node, int idx) {
+ for( Node use : node._outputs ) {
+ if (use instanceof ProjNode proj && proj._idx == idx) return proj;
+ }
+ return null;
+ }
+
+ /**
+ * Cache values for phi and parameter projection nodes.
+ */
+ private final HashMap<Node, Long> cacheValues = new HashMap<>();
+ /**
+ * Cache for loop phis as they can depend on itself or other loop phis
+ */
+ private long[] loopPhiCache = new long[16];
+
+ private GraphEvaluator() {
+ }
+
+ private long div(DivNode div) {
+ long in2 = getValue(div.in(2));
+ return in2 == 0 ? 0 : getValue(div.in(1)) / in2;
+ }
+
+ /**
+ * Calculate the value of a node
+ */
+ private long getValue(Node node) {
+ var cache = cacheValues.get(node);
+ if (cache != null) return cache;
+ return switch (node) {
+ case ConstantNode cons -> ((TypeInteger)cons.compute()).value();
+ case AddNode add -> getValue(add.in(1)) + getValue(add.in(2));
+ case BoolNode.EQ eq -> getValue(eq.in(1)) == getValue(eq.in(2)) ? 1 : 0;
+ case BoolNode.LE le -> getValue(le.in(1)) <= getValue(le.in(2)) ? 1 : 0;
+ case BoolNode.LT lt -> getValue(lt.in(1)) < getValue(lt.in(2)) ? 1 : 0;
+ case DivNode div -> div(div);
+ case MinusNode minus -> -getValue(minus.in(1));
+ case MulNode mul -> getValue(mul.in(1)) * getValue(mul.in(2));
+ case NotNode not -> getValue(not.in(1)) == 0 ? 1 : 0;
+ case SubNode sub -> getValue(sub.in(1)) - getValue(sub.in(2));
+ default -> throw Utils.TODO("Unexpected node " + node);
+ };
+ }
+
+ /**
+ * Special case of latchPhis when phis can depend on phis of the same region.
+ */
+ private void latchLoopPhis(RegionNode region, Node prev) {
+ int idx = Utils.find(region._inputs, prev);
+ assert idx > 0;
+ int i = 0;
+ for( Node use : region._outputs ) {
+ if (use instanceof PhiNode phi) {
+ var value = getValue(phi.in(idx));
+ if (i == loopPhiCache.length) loopPhiCache = Arrays.copyOf(loopPhiCache, loopPhiCache.length*2);
+ loopPhiCache[i++] = value;
+ }
+ }
+ i = 0;
+ for( Node use : region._outputs ) {
+ if (use instanceof PhiNode phi) cacheValues.put(phi, loopPhiCache[i++]);
+ }
+ }
+
+ /**
+ * Calculate the values of phis of the region and caches the values. The phis are not allowed to depend on other phis of the region.
+ */
+ private void latchPhis(RegionNode region, Node prev) {
+ int idx = Utils.find(region._inputs, prev);
+ assert idx > 0;
+ for( Node use : region._outputs ) {
+ if (use instanceof PhiNode phi) {
+ var value = getValue(phi.in(idx));
+ cacheValues.put(phi, value);
+ }
+ }
+ }
+
+ /**
+ * Run the graph until either a return is found or the number of loop iterations are done.
+ */
+ private Result evaluate(StartNode start, long parameter, int loops) {
+ var parameter1 = findProjection(start, 1);
+ if (parameter1 != null) cacheValues.put(parameter1, parameter);
+ Node control = findProjection(start, 0);
+ Node prev = start;
+ while (control != null) {
+ Node next;
+ switch (control) {
+ case RegionNode region -> {
+ if (region instanceof LoopNode && region.in(1) != prev) {
+ if (loops--<=0) return new Result(ResultType.TIMEOUT, 0);
+ latchLoopPhis(region, prev);
+ } else {
+ latchPhis(region, prev);
+ }
+ next = findControl(region);
+ }
+ case IfNode cond -> next = findProjection(cond, getValue(cond.in(1)) != 0 ? 0 : 1);
+ case ReturnNode ret -> {
+ return new Result(ResultType.VALUE, getValue(ret.in(1)));
+ }
+ case ProjNode ignored -> next = findControl(control);
+ default -> throw Utils.TODO("Unexpected control node " + control);
+ }
+ prev = control;
+ control = next;
+ }
+ return new Result(ResultType.FALLTHROUGH, 0);
+ }
+
+ public static long evaluate(Node graph) {
+ return evaluate(graph, 0);
+ }
+
+ public static long evaluate(Node graph, long parameter) {
+ return evaluate(graph, parameter, 1000);
+ }
+
+ public static long evaluate(Node graph, long parameter, int loops) {
+ var res = evaluateWithResult(graph, parameter, loops);
+ if (res.type== ResultType.TIMEOUT) throw new RuntimeException("Timeout");
+ return res.value;
+ }
+
+ public static Result evaluateWithResult(Node graph, long parameter, int loops) {
+ var start = findStart(new BitSet(), graph);
+ if (start == null) return new Result(ResultType.TIMEOUT, 0);
+ var evaluator = new GraphEvaluator();
+ return evaluator.evaluate(start, parameter, loops);
+ }
+
+}
diff --git a/chapter10/src/main/java/com/seaofnodes/simple/GraphVisualizer.java b/chapter10/src/main/java/com/seaofnodes/simple/GraphVisualizer.java
new file mode 100644
index 00000000..cfe6d5cc
--- /dev/null
+++ b/chapter10/src/main/java/com/seaofnodes/simple/GraphVisualizer.java
@@ -0,0 +1,269 @@
+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 parse) { return generateDotOutput(parse.STOP,parse._scope,parse._xScopes); }
+ public String generateDotOutput(StopNode stop, Node scope, Stack<ScopeNode> xScopes) {
+
+ // Since the graph has cycles, we need to create a flat list of all the
+ // nodes in the graph.
+ Collection<Node> all = findAll(stop, scope);
+ StringBuilder sb = new StringBuilder();
+ sb.append("digraph chapter10 {\n");
+ sb.append("/*\n");
+ sb.append(stop._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
+
+ // CNC - turned off Apr/8/2024, gives more flex in the layout and
+ // removes some of the more ludicrous layout choices.
+ // Preserve node input order
+ //sb.append("\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
+ if( xScopes != null )
+ for( ScopeNode xscope : xScopes )
+ if( !xscope.isDead() )
+ scope( sb, xscope );
+
+ // Walk the Node edges
+ nodeEdges(sb, all);
+
+ // Walk the active Scope edges
+ if( xScopes != null )
+ for( ScopeNode xscope : xScopes )
+ if( !xscope.isDead() )
+ scopeEdges( sb, xscope );
+
+ 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);
+ // The edge from New to ctrl is just for anchoring the New
+ if ( n instanceof NewNode )
+ sb.append(" color=green");
+ // control edges are colored red
+ else if( def.isCFG() )
+ sb.append(" color=red");
+ else if( def.isMem() )
+ sb.append(" color=blue");
+ // 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(Node stop, Node scope) {
+ final HashMap<Integer, Node> all = new HashMap<>();
+ for( Node n : stop._inputs )
+ walk(all, n);
+ if( scope != null )
+ for( Node n : scope._inputs )
+ walk(all, n);
+ return all.values();
+ }
+
+ /**
+ * Walk a subgraph and populate distinct nodes in the all list.
+ */
+ private void walk(HashMap<Integer,Node> all, Node n) {
+ 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/chapter10/src/main/java/com/seaofnodes/simple/IRPrinter.java b/chapter10/src/main/java/com/seaofnodes/simple/IRPrinter.java
new file mode 100644
index 00000000..f08f3208
--- /dev/null
+++ b/chapter10/src/main/java/com/seaofnodes/simple/IRPrinter.java
@@ -0,0 +1,188 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.node.LoopNode;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.node.ProjNode;
+
+import java.util.ArrayList;
+import java.util.BitSet;
+
+public class IRPrinter {
+
+ // 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 static void _printLine( Node n, StringBuilder sb ) {
+ sb.append("%4d %-7.7s ".formatted(n._nid,n.label()));
+ if( n._inputs==null ) {
+ sb.append("DEAD\n");
+ return;
+ }
+ for( Node def : n._inputs )
+ sb.append(def==null ? "____ " : "%4d ".formatted(def._nid));
+ for( int i = n._inputs.size(); i<3; i++ )
+ sb.append(" ");
+ sb.append(" [[ ");
+ for( Node use : n._outputs )
+ sb.append(use==null ? "____ " : "%4d ".formatted(use._nid));
+ int lim = 5 - Math.max(n._inputs.size(),3);
+ for( int i = n._outputs.size(); i<lim; i++ )
+ sb.append(" ");
+ sb.append(" ]] ");
+ if( n._type!= null ) sb.append(n._type.str());
+ sb.append("\n");
+ }
+
+ private static StringBuilder nodeId(StringBuilder sb, Node n) {
+ sb.append("%%%d".formatted(n._nid));
+ if (n instanceof ProjNode proj) {
+ sb.append(".").append(proj._idx);
+ }
+ return sb;
+ }
+
+ // Print a node on 1 line, format is inspired by LLVM
+ // %id: TYPE = NODE(inputs ....)
+ // Nodes as referred to as %id
+ public static void _printLineLlvmFormat( Node n, StringBuilder sb ) {
+ nodeId(sb, n).append(": ");
+ if( n._inputs==null ) {
+ sb.append("DEAD\n");
+ return;
+ }
+ if( n._type!= null ) n._type.typeName(sb);
+ sb.append(" = ").append( n.label() ).append( "(" );
+ for( int i = 0; i < n._inputs.size(); i++ ) {
+ Node def = n.in(i);
+ if (i > 0)
+ sb.append(", ");
+ if (def == null) sb.append("_");
+ else nodeId(sb, def);
+ }
+ sb.append(")").append("\n");
+ }
+
+ public static void printLine( Node n, StringBuilder sb, boolean llvmFormat ) {
+ if (llvmFormat) _printLineLlvmFormat( n, sb );
+ else _printLine ( n, sb );
+ }
+
+ public static String prettyPrint(Node node, int depth) {
+ return prettyPrint( node, depth, false );
+ }
+
+ // Another bulk pretty-printer. Makes more effort at basic-block grouping.
+ public static String prettyPrint( Node node, int depth, boolean llvmFormat ) {
+ // 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
+ printLine( n, sb, llvmFormat ); // Print head
+ while( --i >= 0 ) {
+ Node t = rpos.get(i);
+ if( !t.isMultiTail() ) { i++; break; }
+ printLine( t, sb, llvmFormat );
+ }
+ sb.append("\n"); // Blank after multitail
+ gap = true;
+ } else {
+ printLine( n, sb, llvmFormat );
+ 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!=null && use.isCFG() && use.nOuts()>=1 && !(use._outputs.get(0) instanceof LoopNode) )
+ postOrd(use, rpos,visit,bfs);
+ for( Node use : n._outputs )
+ if( use!=null && use.isCFG() )
+ postOrd(use,rpos,visit,bfs);
+ }
+ for( Node use : n._outputs )
+ if( use != null )
+ 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/chapter10/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter10/src/main/java/com/seaofnodes/simple/IterPeeps.java
new file mode 100644
index 00000000..1cee1ccb
--- /dev/null
+++ b/chapter10/src/main/java/com/seaofnodes/simple/IterPeeps.java
@@ -0,0 +1,204 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.node.*;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Random;
+
+/**
+ * The IterOptim runs after parsing. It iterates the peepholes to a fixed point
+ * so that no more peepholes apply. This should be linear because peepholes rarely
+ * (never?) increase code size. The graph should monotonically reduce in some
+ * dimension, which is usually size. It might also reduce in e.g. number of
+ * MulNodes or Load/Store nodes, swapping out more "expensive" Nodes for cheaper
+ * ones.
+ *
+ * The theoretical overall worklist is mindless just grabbing the next thing and
+ * doing it. If the graph changes, put the neighbors on the worklist. Lather,
+ * Rinse, Repeat until the worklist runs dry.
+ *
+ * The main issues we have to deal with:
+ *
+ * <ul>
+ * <li>Nodes have uses; replacing some set of Nodes with another requires more graph
+ * reworking. Not rocket science, but it can be fiddly. Its helpful to have a
+ * small set of graph munging utilities, and the strong invariant that the graph
+ * is stable and correct between peepholes. In our case `Node.subsume` does
+ * most of the munging, building on our prior stable Node utilities.</li>
+ *
+ * <li>Changing a Node also changes the graph "neighborhood". The neigbors need to
+ * be checked to see if THEY can also peephole, and so on. After any peephole
+ * or graph update we put a Nodes uses and defs on the worklist.</li>
+ *
+ * <li>Our strong invariant is that for all Nodes, either they are on the worklist
+ * OR no peephole applies. This invariant is easy to check, although expensive.
+ * Basically the normal "iterate peepholes to a fixed point" is linear, and this
+ * check is linear at each peephole step... so quadratic overall. Its a useful
+ * assert, but one we can disable once the overall algorithm is stable - and
+ * then turn it back on again when some new set of peepholes is misbehaving.
+ * The code for this is turned on in `IterOptim.iterate` as `assert
+ * progressOnList(stop);`</li>
+ * </ul>
+ */
+public abstract class IterPeeps {
+
+ private static final WorkList<Node> WORK = new WorkList<>();
+
+ public static <N extends Node> N add( N n ) {
+ return (N)WORK.push(n);
+ }
+
+ public static void addAll( ArrayList<Node> ary ) {
+ WORK.addAll(ary);
+ }
+
+ /**
+ * Iterate peepholes to a fixed point
+ */
+ public static StopNode iterate(StopNode stop, boolean show) {
+ assert progressOnList(stop);
+ int cnt=0;
+
+ Node n;
+ while( (n=WORK.pop()) != null ) {
+ if( n.isDead() ) continue;
+ cnt++; // Useful for debugging, searching which peephole broke things
+ Node x = n.peepholeOpt();
+ if( x != null ) {
+ if( x.isDead() ) continue;
+ // peepholeOpt can return brand-new nodes, needing an initial type set
+ if( x._type==null ) x.setType(x.compute());
+ // Changes require neighbors onto the worklist
+ if( x != n || !(x instanceof ConstantNode) ) {
+ // All outputs of n (changing node) not x (prior existing node).
+ for( Node z : n._outputs ) WORK.push(z);
+ // Everybody gets a free "go again" in case they didn't get
+ // made in their final form.
+ WORK.push(x);
+ // If the result is not self, revisit all inputs (because
+ // there's a new user), and replace in the graph.
+ if( x != n ) {
+ for( Node z : n. _inputs ) WORK.push(z);
+ n.subsume(x);
+ }
+ }
+ // If there are distant neighbors, move to worklist
+ n.moveDepsToWorklist();
+ assert progressOnList(stop); // Very expensive assert
+ }
+ if( n.isUnused() && !(n instanceof StopNode) )
+ n.kill(); // Just plain dead
+ }
+
+ if( show )
+ System.out.println(new GraphVisualizer().generateDotOutput(stop,null,null));
+ return stop;
+ }
+
+ // Visit ALL nodes and confirm the invariant:
+ // Either you are on the WORK worklist OR running `iter()` makes no progress.
+
+ // This invariant ensures that no progress is missed, i.e., when the
+ // worklist is empty we have indeed done all that can be done. To help
+ // with debugging, the {@code assert} is broken out in a place where it is easy to
+ // stop if a change is found.
+
+ // Also, the normal usage of `iter()` may attempt peepholes with distance
+ // neighbors and these should fail, but will then try to add dependencies
+ // {@link #Node.addDep} which is a side effect in an assert. The {@link
+ // #midAssert} is used to stop this side effect.
+ private static boolean MID_ASSERT;
+ public static boolean midAssert() { return MID_ASSERT; }
+ private static boolean progressOnList(Node stop) {
+ MID_ASSERT = true;
+ int old_cnt = Node.ITER_CNT, old_nop = Node.ITER_NOP_CNT;
+ Node changed = stop.walk( n -> {
+ if( WORK.on(n) ) return null;
+ Node m = n.peepholeOpt();
+ if( m==null ) return null;
+ System.err.println("BREAK HERE FOR BUG");
+ return m;
+ });
+ Node.ITER_CNT = old_cnt; Node.ITER_NOP_CNT = old_nop;
+ MID_ASSERT = false;
+ return changed==null;
+ }
+
+ public static void reset() {
+ WORK.clear();
+ }
+
+ /**
+ * Classic WorkList, with a fast add/remove, dup removal, random pull.
+ * The Node's nid is used to check membership in the worklist.
+ */
+ public static class WorkList<E extends Node> {
+
+ private Node[] _es;
+ private int _len;
+ private final BitSet _on; // Bit set if Node._nid is on WorkList
+ private final Random _R; // For randomizing pull from the WorkList
+ private final long _seed;
+
+ /* Useful stat - how many nodes are processed in the post parse iterative opt */
+ private long _totalWork = 0;
+
+ WorkList() { this(123); }
+ WorkList(long seed) {
+ _es = new Node[1];
+ _len=0;
+ _on = new BitSet();
+ _seed = seed;
+ _R = new Random();
+ _R.setSeed(_seed);
+ }
+
+ /**
+ * Pushes a Node on the WorkList, ensuring no duplicates
+ * If Node is null it will not be added.
+ */
+ public E push( E x ) {
+ if( x==null ) return null;
+ int idx = x._nid;
+ if( !_on.get(idx) ) {
+ _on.set(idx);
+ if( _len==_es.length )
+ _es = Arrays.copyOf(_es,_len<<1);
+ _es[_len++] = x;
+ _totalWork++;
+ }
+ return x;
+ }
+
+ public void addAll( ArrayList<E> ary ) {
+ for( E n : ary )
+ push(n);
+ }
+
+ /**
+ * True if Node is on the WorkList
+ */
+ boolean on( E x ) { return _on.get(x._nid); }
+
+ /**
+ * Removes a random Node from the WorkList; null if WorkList is empty
+ */
+ E pop() {
+ if( _len == 0 ) return null;
+ int idx = _R.nextInt(_len);
+ E x = (E)_es[idx];
+ _es[idx] = _es[--_len]; // Compress array
+ _on.clear(x._nid);
+ return x;
+ }
+
+ public void clear() {
+ _len = 0;
+ _on.clear();
+ _R.setSeed(_seed);
+ _totalWork = 0;
+ }
+ }
+}
diff --git a/chapter10/src/main/java/com/seaofnodes/simple/Parser.java b/chapter10/src/main/java/com/seaofnodes/simple/Parser.java
new file mode 100644
index 00000000..267e0eb4
--- /dev/null
+++ b/chapter10/src/main/java/com/seaofnodes/simple/Parser.java
@@ -0,0 +1,825 @@
+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 static final HashSet<String> KEYWORDS = new HashSet<>(){{
+ add("break");
+ add("continue");
+ add("else");
+ add("false");
+ add("if");
+ add("int");
+ add("new");
+ add("null");
+ add("return");
+ add("struct");
+ 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<>();
+
+ ScopeNode _continueScope;
+ ScopeNode _breakScope;
+
+ public static Map<String, TypeStruct> OBJS = new HashMap<>();
+
+ public Parser(String source, TypeInteger arg) {
+ Node.reset();
+ IterPeeps.reset();
+ Type.reset();
+ OBJS.clear();
+ _lexer = new Lexer(source);
+ _scope = new ScopeNode();
+ _continueScope = _breakScope = null;
+ START = new StartNode(new Type[]{ Type.CONTROL, arg });
+ STOP = new StopNode(source);
+ }
+
+ 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) {
+ _xScopes.push(_scope);
+ // Enter a new scope for the initial control and arguments
+ _scope.push();
+ _scope.define(ScopeNode.CTRL, Type.CONTROL , new ProjNode(START, 0, ScopeNode.CTRL).peephole());
+ _scope.define(ScopeNode.ARG0, TypeInteger.BOT, new ProjNode(START, 1, ScopeNode.ARG0).peephole());
+ parseBlock();
+ _scope.pop();
+ _xScopes.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( false ) return null;
+ else if (matchx("return") ) return parseReturn();
+ else if (matchx("int") ) return parseDecl(TypeInteger.BOT);
+ else if (match ("{") ) return require(parseBlock(),"}");
+ else if (matchx("if") ) return parseIf();
+ else if (matchx("while") ) return parseWhile();
+ else if (matchx("break") ) return parseBreak();
+ else if (matchx("continue")) return parseContinue();
+ else if (matchx("struct") ) return parseStruct();
+ else if (matchx("#showGraph")) return require(showGraph(),";");
+ // declarations of vars with struct type are handled in parseExpressionStatement due
+ // to ambiguity
+ else return parseExpressionStatement();
+ }
+
+ /**
+ * Parse a struct field.
+ * <pre>
+ * int IDENTIFIER ;
+ * </pre>
+ */
+ private Field parseField(String sname) {
+ if( matchx("int") ) // Currently only parsing "int" type fields
+ return require(Field.make(sname,TypeInteger.BOT,requireId()),";");
+ throw errorSyntax("A field type is expected, only type 'int' is supported at present");
+ }
+
+ /**
+ * Parse a struct declaration, and return the following statement.
+ * Only allowed in top level scope.
+ * Structs cannot be redefined.
+ *
+ * @return The statement following the struct
+ */
+ private Node parseStruct() {
+ if (_xScopes.size() > 1) throw errorSyntax("struct declarations can only appear in top level scope");
+ String typeName = requireId();
+ if ( OBJS.containsKey(typeName)) throw errorSyntax("struct '" + typeName + "' cannot be redefined");
+ ArrayList<Field> fields = new ArrayList<>();
+ require("{");
+ while (!peek('}') && !_lexer.isEOF()) {
+ Field field = parseField(typeName);
+ if (fields.contains(field)) throw errorSyntax("Field '" + field + "' already defined in struct '" + typeName + "'");
+ fields.add(field);
+ }
+ require("}");
+ // Build and install the TypeStruct
+ TypeStruct ts = TypeStruct.make(typeName, fields);
+ OBJS.put(typeName, ts); // Insert the struct name in the collection of all struct names
+ START.addMemProj(ts, _scope); // Insert memory edges
+ return parseStatement();
+ }
+
+ /**
+ * Parses a while statement
+ *
+ * <pre>
+ * while ( expression ) statement
+ * </pre>
+ * @return a {@link Node}, never {@code null}
+ */
+ private Node parseWhile() {
+
+ var savedContinueScope = _continueScope;
+ var savedBreakScope = _breakScope;
+
+ require("(");
+
+ // Loop region has two control inputs, the first is the entry
+ // point, and second 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; see {@code inProgress()}.
+
+ ctrl(new LoopNode(ctrl()).peephole()); // Note we set back edge to null here
+
+ // At loop head, we clone the current Scope (this includes all
+ // names in every nesting level within the Scope).
+ // We create phis eagerly for all the names we find, see dup().
+
+ // Save the current scope as the loop head
+ ScopeNode head = _scope.keep();
+ // Clone the head Scope to create a new Scope for the body.
+ // Create phis eagerly as part of cloning
+ _xScopes.push(_scope = _scope.dup(true)); // The true argument triggers creating phis
+
+ // Parse predicate
+ var pred = require(parseExpression(), ")");
+ // IfNode takes current control and predicate
+ Node ifNode = new IfNode(ctrl(), pred).peephole();
+ // Setup projection nodes
+ Node ifT = new ProjNode(ifNode. keep(), 0, "True" ).peephole().keep();
+ Node ifF = new ProjNode(ifNode.unkeep(), 1, "False").peephole();
+
+ // Clone the body Scope to create the break/exit Scope which accounts for any
+ // side effects in the predicate. The break/exit Scope will be the final
+ // scope after the loop, and its control input is the False branch of
+ // the loop predicate. Note that body Scope is still our current scope.
+ ctrl(ifF);
+ _xScopes.push(_breakScope = _scope.dup());
+
+ // No continues yet
+ _continueScope = null;
+
+ // Parse the true side, which corresponds to loop body
+ // Our current scope is the body Scope
+ ctrl(ifT.unkeep()); // set ctrl token to ifTrue projection
+ parseStatement(); // Parse loop body
+
+ // Merge the loop bottom into other continue statements
+ if (_continueScope != null) {
+ _continueScope = jumpTo(_continueScope);
+ _scope.kill();
+ _scope = _continueScope;
+ }
+
+ // The true branch loops back, so whatever is current _scope.ctrl gets
+ // added to head loop as input. endLoop() updates the head scope, and
+ // goes through all the phis that were created earlier. For each phi,
+ // it sets the second input to the corresponding input from the back
+ // edge. If the phi is redundant, it is replaced by its sole input.
+ var exit = _breakScope;
+ head.endLoop(_scope, exit);
+ head.unkeep().kill();
+
+ _xScopes.pop(); // Cleanup
+ _xScopes.pop(); // Cleanup
+
+ _continueScope = savedContinueScope;
+ _breakScope = savedBreakScope;
+
+ // At exit the false control is the current control, and
+ // the scope is the exit scope after the exit test.
+ return _scope = exit;
+ }
+
+ private ScopeNode jumpTo(ScopeNode toScope) {
+ ScopeNode cur = _scope.dup();
+ ctrl(new ConstantNode(Type.XCONTROL).peephole()); // Kill current scope
+ // Prune nested lexical scopes that have depth > than the loop head
+ // We use _breakScope as a proxy for the loop head scope to obtain the depth
+ while( cur._scopes.size() > _breakScope._scopes.size() )
+ cur.pop();
+ // If this is a continue then first time the target is null
+ // So we just use the pruned current scope as the base for the
+ // continue
+ if (toScope == null)
+ return cur;
+ // toScope is either the break scope, or a scope that was created here
+ assert toScope._scopes.size() <= _breakScope._scopes.size();
+ toScope.ctrl(toScope.mergeScopes(cur));
+ return toScope;
+ }
+
+ private void checkLoopActive() { if (_breakScope == null) throw Parser.error("No active loop for a break or continue"); }
+
+ private Node parseBreak () { checkLoopActive(); return ( _breakScope = require(jumpTo( _breakScope ),";")); }
+ private Node parseContinue() { checkLoopActive(); return (_continueScope = require(jumpTo( _continueScope ),";")); }
+
+ /**
+ * 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(), ")").keep();
+ // IfNode takes current control and predicate
+ Node ifNode = new IfNode(ctrl(), pred).peephole();
+ // Setup projection nodes
+ Node ifT = new ProjNode(ifNode. keep(), 0, "True" ).peephole().keep();
+ Node ifF = new ProjNode(ifNode.unkeep(), 1, "False").peephole().keep();
+ // 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.unkeep()); // set ctrl token to ifTrue projection
+ _scope.upcast(ifT,pred,false); // Up-cast predicate
+ parseStatement(); // Parse true-side
+ ScopeNode tScope = _scope;
+
+ // Parse the false side
+ _scope = fScope; // Restore scope, then parse else block if any
+ ctrl(ifF.unkeep()); // Ctrl token is now set to ifFalse projection
+ if (matchx("else")) {
+ _scope.upcast(ifF,pred,true); // Up-cast predicate
+ parseStatement();
+ fScope = _scope;
+ }
+ pred.unkeep();
+
+ 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, _scope).peephole());
+ ctrl(new ConstantNode(Type.XCONTROL).peephole()); // Kill control
+ return ret;
+ }
+
+ /**
+ * Dumps out the node graph
+ * @return {@code null}
+ */
+ Node showGraph() {
+ System.out.println(new GraphVisualizer().generateDotOutput(STOP,_scope,_xScopes));
+ return null;
+ }
+
+ /**
+ * Parses an expression statement or a declaration statement where type is a struct
+ *
+ * <pre>
+ * name; // Error
+ * type name; // Define name with default initial value
+ * type name = expr; // Define name with given initial value
+ * name = expr; // Reassign existing
+ * expr // Something else
+ * </pre>
+ * @return an expression {@link Node}, never {@code null}
+ */
+ private Node parseExpressionStatement() {
+ int old = _lexer._position;
+ Type t = type();
+ String name = requireId();
+ Node expr;
+ if( match(";") ) { // Assign a default value
+ // No type and no expr is an error
+ if( t==null ) throw errorSyntax("expression");
+ expr = new ConstantNode(t.makeInit()).peephole();
+ } else if( match("=") ) { // Assign "= expr;"
+ expr = require(parseExpression(), ";");
+ } else { // Neither, so just a normal expression parse
+ _lexer._position = old;
+ return require(parseExpression(),";");
+ }
+
+ // Defining a new variable vs updating an old one
+ if( t != null ) {
+ if( _scope.define(name,t,expr) == null )
+ throw error("Redefining name '" + name + "'");
+ } else {
+ Node n = _scope.lookup(name);
+ t = _scope.lookupDeclaredType(name);
+ if( n==null )
+ throw error("Undefined name '" + name + "'");
+ _scope.update(name,expr);
+ }
+ if( !expr._type.isa(t) )
+ throw error("Type " + expr._type.str() + " is not of declared type " + t.str());
+ return expr;
+ }
+
+ // Parse a type-or-null
+ private Type type() {
+ int old = _lexer._position;
+ String tname = _lexer.matchId();
+ if( tname==null ) return null;
+ if( tname.equals("int") ) return TypeInteger.BOT;
+ TypeStruct obj = OBJS.get(tname);
+ if( obj != null )
+ return TypeMemPtr.make(obj,match("?"));
+ // Not a type; unwind the parse
+ _lexer._position = old;
+ return null;
+ }
+
+ /**
+ * Parses a declStatement
+ *
+ * <pre>
+ * type name = expression ';'
+ * </pre>
+ * @return an expression {@link Node}
+ */
+ private Node parseDecl(Type t) {
+ var name = requireId();
+ var expr = match(";")
+ // Assign a null value
+ ? new ConstantNode(t.makeInit()).peephole()
+ // Assign "= expr;"
+ : require(require("=").parseExpression(), ";");
+ if( !expr._type.isa(t) )
+ throw error("Type " + expr._type.str() + " is not of declared type " + t.str());
+ if( _scope.define(name,t,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();
+ while( true ) {
+ int idx=0; boolean negate=false;
+ // Test for any local nodes made, and "keep" lhs during peepholes
+ if( false ) ;
+ else if( match("==") ) { idx=2; lhs = new BoolNode.EQ(lhs, null); }
+ else if( match("!=") ) { idx=2; lhs = new BoolNode.EQ(lhs, null); negate=true; }
+ else if( match("<=") ) { idx=2; lhs = new BoolNode.LE(lhs, null); }
+ else if( match("<" ) ) { idx=2; lhs = new BoolNode.LT(lhs, null); }
+ else if( match(">=") ) { idx=1; lhs = new BoolNode.LE(null, lhs); }
+ else if( match(">" ) ) { idx=1; lhs = new BoolNode.LT(null, lhs); }
+ else break;
+ // Peepholes can fire, but lhs is already "hooked", kept alive
+ lhs.setDef(idx,parseAddition());
+ lhs = lhs.peephole();
+ if( negate ) // Extra negate for !=
+ lhs = new NotNode(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() {
+ Node lhs = parseMultiplication();
+ while( true ) {
+ if( false ) ;
+ else if( match("+") ) lhs = new AddNode(lhs,null);
+ else if( match("-") ) lhs = new SubNode(lhs,null);
+ else break;
+ lhs.setDef(2,parseMultiplication());
+ lhs = lhs.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();
+ while( true ) {
+ if( false ) ;
+ else if( match("*") ) lhs = new MulNode(lhs,null);
+ else if( match("/") ) lhs = new DivNode(lhs,null);
+ else break;
+ lhs.setDef(2,parseUnary());
+ lhs = lhs.peephole();
+ }
+ return lhs;
+ }
+
+ /**
+ * Parse a unary minus expression.
+ *
+ * <pre>
+ * unaryExpr : ('-') | '!') unaryExpr | postfixExpr | primaryExpr
+ * </pre>
+ * @return a unary expression {@link Node}, never {@code null}
+ */
+ private Node parseUnary() {
+ if (match("-")) return new MinusNode(parseUnary()).peephole();
+ if (match("!")) return new NotNode(parseUnary()).peephole();
+ return parsePostfix(parsePrimary());
+ }
+
+ /**
+ * Parse a primary expression:
+ *
+ * <pre>
+ * primaryExpr : integerLiteral | Identifier | true | false | null | new Identifier | '(' 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();
+ if( matchx("null" ) ) return new ConstantNode(TypeMemPtr.NULLPTR).peephole();
+ if( matchx("new") ) {
+ String structName = requireId();
+ TypeStruct obj = OBJS.get(structName);
+ if( obj == null) throw errorSyntax("Unknown struct type '" + structName + "'");
+ return newStruct(obj);
+ }
+ 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 + "'");
+ }
+
+ /**
+ * Return a NewNode but also generate instructions to initialize it.
+ */
+ private Node newStruct(TypeStruct obj) {
+ Node n = new NewNode(TypeMemPtr.make(obj), ctrl()).peephole().keep();
+ Node initValue = new ConstantNode(TypeInteger.constant(0)).peephole();
+ for (Field field: obj._fields) {
+ memAlias(field, new StoreNode(field, memAlias(field), n, initValue).peephole());
+ }
+ return n.unkeep();
+ }
+
+ // We set up memory aliases by inserting special vars in the scope these
+ // variables are prefixed by $ so they cannot be referenced in Simple code.
+ // Using vars has the benefit that all the existing machinery of scoping
+ // and phis work as expected
+ private Node memAlias(Field fld ) { return _scope.lookup(fld.aliasName() ); }
+ private Node memAlias(Field fld, Node st) { return _scope.update(fld.aliasName(), st); }
+
+ /**
+ * Parse postfix expression. For now this is just a field
+ * expression, but in future could be array index too.
+ *
+ * <pre>
+ * expr ('.' IDENTIFIER)* [ = expr ]
+ * </pre>
+ */
+ private Node parsePostfix(Node expr) {
+ if( !match(".") ) return expr;
+
+ if( !(expr._type instanceof TypeMemPtr ptr) )
+ throw error("Expected struct reference but got " + expr._type.str());
+
+ String name = requireId();
+ Field field = ptr._obj==null ? null : ptr._obj.get(name);
+ if( field == null ) throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'");
+
+ if( match("=") ) {
+ // Disambiguate "obj.fld==x" boolean test from "obj.fld=x" field assignment
+ if( peek('=') ) _lexer._position--;
+ else {
+ Node val = parseExpression();
+ memAlias(field, new StoreNode(field, memAlias(field), expr, val).peephole());
+ return expr; // "obj.a = expr" returns the expression while updating memory
+ }
+ }
+
+ return parsePostfix(new LoadNode(field, memAlias(field), expr).peephole());
+ }
+
+ /**
+ * 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); }
+ private boolean peekIsId() { return _lexer.peekIsId(); }
+
+ // 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 Parser require(String syntax) { require(null, syntax); return this; }
+ private <N> N require(N n, String syntax) {
+ if (match(syntax)) return n;
+ throw errorSyntax(syntax);
+ }
+
+ RuntimeException errorSyntax(String syntax) {
+ return error("Syntax error, expected " + syntax + ": " + _lexer.getAnyNextToken());
+ }
+
+ public 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;
+ }
+
+ // Match must be exact and not followed by more ID characters.
+ // Prevents identifier "ifxy" from matching an "if" statement.
+ 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;
+ }
+
+ boolean peekIsId() {
+ skipWhiteSpace();
+ return isIdStart(peek());
+ }
+
+ // Return an identifier or null
+ String matchId() {
+ return peekIsId() ? 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/chapter10/src/main/java/com/seaofnodes/simple/Utils.java b/chapter10/src/main/java/com/seaofnodes/simple/Utils.java
new file mode 100644
index 00000000..e3f29b3d
--- /dev/null
+++ b/chapter10/src/main/java/com/seaofnodes/simple/Utils.java
@@ -0,0 +1,58 @@
+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;
+ }
+ /**
+ * 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( E[] ary, E x ) {
+ for( int i=0; i<ary.length; i++ )
+ if( ary[i]==x )
+ return i;
+ return -1;
+ }
+
+ // Rotate a long, nice for hashes
+ public static long rot( long x, int n ) { return (x<<n) | (x>>>n); }
+
+ // Fold a 64bit hash into 32 bits
+ public static int fold( long x ) { return (int)((x>>32) ^ x); }
+} \ No newline at end of file
diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/AddNode.java
new file mode 100644
index 00000000..2cc0b465
--- /dev/null
+++ b/chapter10/src/main/java/com/seaofnodes/simple/node/AddNode.java
@@ -0,0 +1,174 @@
+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 in(1)._type.meet(in(2)._type);
+ }
+
+ @Override
+ public Node idealize () {
+ Node lhs = in(1);
+ Node rhs = in(2);
+ Type t2 = rhs._type;
+
+ // 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();
+
+ // x+(-y) becomes x-y
+ if( rhs instanceof MinusNode minus )
+ return new SubNode(lhs,minus.in(1));
+
+ // 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 spine_cmp(lhs,rhs,this) ? swap12() : phiCon(this,true);
+
+ // Now we only see (add add non)
+
+ // Dead data cycle; comes about from dead infinite loops. Do nothing,
+ // the loop will peep as dead after a bit.
+ if( lhs.in(1) == lhs )
+ return null;
+
+ // Do we have (x + con1) + con2?
+ // Replace with (x + (con1+con2) which then fold the constants
+ // lhs.in(2) is con1 here
+ // If lhs.in(2) is not a constant, we add ourselves as a dependency
+ // because if it later became a constant then we could make this
+ // transformation.
+ if( lhs.in(2).addDep(this)._type.isConstant() && rhs._type.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 spine via rotates, to gather similar things together.
+
+ // Do we rotate (x + y) + z
+ // into (x + z) + y ?
+ if( spine_cmp(lhs.in(2),rhs,this) )
+ 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,op);
+ 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),op); // Will rotate with the Phi push
+ }
+ if( lphi==null ) return null;
+
+ // RHS is a constant or a Phi of constants
+ if( !(rhs instanceof ConstantNode) && pcon(rhs,op)==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,lphi._declaredType,ns).peephole();
+ // Rotate needs another op, otherwise just the phi
+ return lhs==lphi ? phi : op.copy(lhs.in(1),phi);
+ }
+
+ /**
+ * Tests if the op is a phi and has all constant inputs.
+ * If not, returns null.
+ * If op is a phi, but its inputs are not all constants, then dep is added as
+ * a dependency to the phi's non-const input, because if later the input turn into a constant
+ * dep can make progress.
+ */
+ static PhiNode pcon(Node op, Node dep) {
+ return op instanceof PhiNode phi && phi.allCons(dep) ? phi : null;
+ }
+
+ // Compare two off-spine 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 spine_cmp( Node hi, Node lo, Node dep ) {
+ if( lo._type.isConstant() ) return false;
+ if( hi._type.isConstant() ) return true ;
+
+ if( lo instanceof PhiNode lphi && lphi.region()._type==Type.XCONTROL ) return false;
+ if( hi instanceof PhiNode hphi && hphi.region()._type==Type.XCONTROL ) return false;
+
+ if( lo instanceof PhiNode && lo.allCons(dep) ) return false;
+ if( hi instanceof PhiNode && hi.allCons(dep) ) 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/chapter10/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/BoolNode.java
new file mode 100644
index 00000000..9c15f01b
--- /dev/null
+++ b/chapter10/src/main/java/com/seaofnodes/simple/node/BoolNode.java
@@ -0,0 +1,75 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.type.TypeMemPtr;
+
+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 in(1)._type.meet(in(2)._type);
+ }
+
+ 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));
+
+ // Equals pushes constant to the right; 5==X becomes X==5.
+ if( this instanceof EQ ) {
+ if( !(in(2) instanceof ConstantNode) ) {
+ // con==noncon becomes noncon==con
+ if( in(1) instanceof ConstantNode )
+ return new EQ(in(2),in(1));
+ // Equals sorts by NID otherwise: non.high == non.low becomes non.low == non.high
+ else if( in(1)._nid > in(2)._nid )
+ return new EQ(in(2),in(1));
+ }
+ // Equals X==0 becomes a !X
+ if( (in(2)._type == TypeInteger.ZERO || in(2)._type == TypeMemPtr.NULLPTR) )
+ return new NotNode(in(1));
+ }
+
+ // 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,this instanceof EQ);
+ 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/chapter10/src/main/java/com/seaofnodes/simple/node/CastNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/CastNode.java
new file mode 100644
index 00000000..fec540f7
--- /dev/null
+++ b/chapter10/src/main/java/com/seaofnodes/simple/node/CastNode.java
@@ -0,0 +1,38 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.IterPeeps;
+
+import java.util.BitSet;
+
+// Upcast (join) the input to a t. Used after guard test to lift an input.
+// Can also be used to make a type-assertion if ctrl is null.
+public class CastNode extends Node {
+ private final Type _t;
+ public CastNode(Type t, Node ctrl, Node in) {
+ super(ctrl, in);
+ _t = t;
+ setType(compute());
+ }
+
+ @Override public String label() { return "("+_t.str()+")"; }
+
+ @Override
+ public String uniqueName() { return "Cast_" + _nid; }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return in(1)._print0(sb.append(label()), visited);
+ }
+
+ @Override
+ public Type compute() {
+ return in(1)._type.join(_t);
+ }
+
+ @Override
+ public Node idealize() {
+ return in(1)._type.isa(_t) ? in(1) : null;
+ }
+}
diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/ConstantNode.java
new file mode 100644
index 00000000..c4ea08b5
--- /dev/null
+++ b/chapter10/src/main/java/com/seaofnodes/simple/node/ConstantNode.java
@@ -0,0 +1,58 @@
+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; }
+
+ @Override
+ boolean eq(Node n) {
+ ConstantNode con = (ConstantNode)n; // Contract
+ return _con==con._con;
+ }
+
+ @Override
+ int hash() { return _con.hashCode(); }
+
+ // Dead control is its own idom root
+ Node idom() { return null; }
+
+}
diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/DivNode.java
new file mode 100644
index 00000000..ed1a233c
--- /dev/null
+++ b/chapter10/src/main/java/com/seaofnodes/simple/node/DivNode.java
@@ -0,0 +1,38 @@
+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 in(1)._type.meet(in(2)._type);
+ }
+
+ @Override
+ public Node idealize() { return null; }
+
+ @Override Node copy(Node lhs, Node rhs) { return new DivNode(lhs,rhs); }
+}
diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/IfNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/IfNode.java
new file mode 100644
index 00000000..1cf4ddaf
--- /dev/null
+++ b/chapter10/src/main/java/com/seaofnodes/simple/node/IfNode.java
@@ -0,0 +1,62 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.IterPeeps;
+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);
+ IterPeeps.add(this); // Because idoms are complex, just add it
+ }
+
+ @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;
+ Node pred = pred();
+ Type t = pred._type;
+ // High types mean NEITHER side is reachable.
+ // Wait until the type falls to decide which way to go.
+ if( t == Type.TOP || t == TypeInteger.TOP )
+ return TypeTuple.IF_NEITHER;
+ // If constant is 0 then false branch is reachable
+ // Else true branch is reachable
+ if (t instanceof TypeInteger ti && ti.isConstant())
+ return ti==TypeInteger.ZERO ? TypeTuple.IF_FALSE : TypeTuple.IF_TRUE;
+
+ return TypeTuple.IF_BOTH;
+ }
+
+ @Override
+ public Node idealize() {
+ // Hunt up the immediate dominator tree. If we find an identical if
+ // test on either the true or false branch, that side wins.
+ if( !pred()._type.isHighOrConst() )
+ for( Node dom = idom(), prior=this; dom!=null; prior = dom, dom = dom.idom() )
+ if( dom.addDep(this) instanceof IfNode iff && iff.pred().addDep(this)==pred() && prior instanceof ProjNode prj ) {
+ setDef(1,new ConstantNode(TypeInteger.make(true,prj._idx==0?1:0)).peephole());
+ return this;
+ }
+ return null;
+ }
+}
diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/LoadNode.java
new file mode 100644
index 00000000..26e57727
--- /dev/null
+++ b/chapter10/src/main/java/com/seaofnodes/simple/node/LoadNode.java
@@ -0,0 +1,43 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.Field;
+
+import java.util.BitSet;
+
+/**
+ * Load represents extracting a value from inside a memory object,
+ * in chapter 10 this means Struct fields.
+ */
+public class LoadNode extends MemOpNode {
+
+ /**
+ * Load a value from a ptr.field.
+ *
+ * @param field The struct field we are loading
+ * @param memSlice The memory alias node - this is updated after a Store
+ * @param memPtr The ptr to the struct from where we load a field
+ */
+ public LoadNode(Field field, Node memSlice, Node memPtr) {
+ super(field, memSlice, memPtr, null);
+ }
+
+ @Override
+ public String label() { return "Load"; }
+
+ @Override
+ public String glabel() { return "."+_field._fname; }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(".").append(_field._fname); }
+
+ @Override
+ public Type compute() {
+ return _field._type;
+ }
+
+ @Override
+ public Node idealize() {
+ return null;
+ }
+}
diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/LoopNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/LoopNode.java
new file mode 100644
index 00000000..0b84f897
--- /dev/null
+++ b/chapter10/src/main/java/com/seaofnodes/simple/node/LoopNode.java
@@ -0,0 +1,29 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+
+import java.util.BitSet;
+
+public class LoopNode extends RegionNode {
+
+ public LoopNode( Node entry ) { super(null,entry,null); }
+
+ Node entry() { return in(1); }
+ Node back () { return in(2); }
+
+ @Override
+ public String label() { return "Loop"; }
+
+ @Override
+ public Type compute() {
+ if( inProgress() ) return Type.CONTROL;
+ return entry()._type;
+ }
+
+ @Override
+ public Node idealize() {
+ return inProgress() ? null : super.idealize();
+ }
+
+ @Override Node idom() { return entry(); }
+}
diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
new file mode 100644
index 00000000..4376bb0f
--- /dev/null
+++ b/chapter10/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
@@ -0,0 +1,41 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Field;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeMemPtr;
+
+/**
+ * Convenience common base for Load and Store.
+ */
+abstract class MemOpNode extends Node {
+
+ protected Field _field;
+
+ public MemOpNode(Field field, Node memSlice, Node memPtr, Node value) {
+ super(null, memSlice, memPtr, value);
+ this._field = field;
+ }
+
+ public Node mem() { return in(1); }
+ public Node ptr() { return in(2); }
+ public Node val() { return nIns() > 3 ? in(3) : null; }
+
+ @Override
+ boolean eq(Node n) {
+ MemOpNode mem = (MemOpNode)n; // Invariant
+ return _field.equals(mem._field);
+ }
+
+ @Override
+ int hash() {
+ return _field._alias;
+ }
+
+ @Override
+ String err() {
+ Type ptr = ptr()._type;
+ return (ptr==Type.BOTTOM || (ptr instanceof TypeMemPtr tmp && tmp._nil) )
+ ? "Might be null accessing '" + _field.str() + "'"
+ : null;
+ }
+}
diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/MinusNode.java
new file mode 100644
index 00000000..524f63a0
--- /dev/null
+++ b/chapter10/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 TypeInteger.TOP.meet(in(1)._type);
+ }
+
+ @Override
+ public Node idealize() { return null; }
+}
diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/MulNode.java
new file mode 100644
index 00000000..d63d2cb1
--- /dev/null
+++ b/chapter10/src/main/java/com/seaofnodes/simple/node/MulNode.java
@@ -0,0 +1,57 @@
+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 in(1)._type.meet(in(2)._type);
+ }
+
+ @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/chapter10/src/main/java/com/seaofnodes/simple/node/MultiNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/MultiNode.java
new file mode 100644
index 00000000..eced9a14
--- /dev/null
+++ b/chapter10/src/main/java/com/seaofnodes/simple/node/MultiNode.java
@@ -0,0 +1,14 @@
+package com.seaofnodes.simple.node;
+
+public abstract class MultiNode extends Node {
+
+ public MultiNode(Node... inputs) { super(inputs); }
+
+ // Find a projection by index
+ ProjNode proj( int idx ) {
+ for( Node out : _outputs )
+ if( out instanceof ProjNode prj && prj._idx==idx )
+ return prj;
+ return null;
+ }
+}
diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/NewNode.java
new file mode 100644
index 00000000..352689b5
--- /dev/null
+++ b/chapter10/src/main/java/com/seaofnodes/simple/node/NewNode.java
@@ -0,0 +1,40 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeMemPtr;
+
+import java.util.BitSet;
+
+public class NewNode extends Node {
+
+ TypeMemPtr _ptr;
+
+ public NewNode(TypeMemPtr ptr, Node ctrl) {
+ super(ctrl);
+ this._ptr = ptr;
+ }
+
+ @Override
+ public String label() { return "new"; }
+
+ @Override
+ public String glabel() { return "new " + _ptr._obj._name; }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ sb.append("new ");
+ return sb.append(_ptr._obj.str());
+ }
+
+ @Override
+ public Type compute() { return _ptr; }
+
+ @Override
+ public Node idealize() { return null; }
+
+ @Override
+ boolean eq(Node n) { return this == n; }
+
+ @Override
+ int hash() { return _ptr.hashCode(); }
+}
diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter10/src/main/java/com/seaofnodes/simple/node/Node.java
new file mode 100644
index 00000000..f96b88bd
--- /dev/null
+++ b/chapter10/src/main/java/com/seaofnodes/simple/node/Node.java
@@ -0,0 +1,623 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.IRPrinter;
+import com.seaofnodes.simple.IterPeeps;
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.type.Type;
+
+import java.util.*;
+import java.util.function.Function;
+
+/**
+ * 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;
+ public static int UID() { return UNIQUE_ID; }
+
+ 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() {
+ // Get rid of $ as graphviz doesn't like it
+ String label = label().replaceAll("\\$", "");
+ 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) && !(this instanceof ConstantNode) )
+ 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);
+
+ public String p(int depth) { return IRPrinter.prettyPrint(this,depth); }
+
+ 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 Node out(int i) { return _outputs.get(i); }
+
+ public int nIns() { return _inputs.size(); }
+
+ public int nOuts() { return _outputs.size(); }
+
+ public boolean isUnused() { return nOuts() == 0; }
+
+ public boolean isCFG() { return false; }
+
+ public boolean isMem() { 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
+ */
+ public Node setDef(int idx, Node new_def ) {
+ unlock();
+ Node old_def = in(idx);
+ if( old_def == new_def ) return new_def; // 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. If the def goes dead, it is recursively
+ // killed, which may include 'this' Node.
+ Node delDef(int idx) {
+ unlock();
+ 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
+ old_def.moveDepsToWorklist();
+ Utils.del(_inputs, idx);
+ return this;
+ }
+
+ /**
+ * 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
+ */
+ Node addDef(Node new_def) {
+ unlock();
+ // 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.isEmpty();
+ }
+
+ // Shortcut for "popping" n nodes. A "pop" is basically a
+ // setDef(last,null) followed by lowering the nIns() count.
+ void popN(int n) {
+ unlock();
+ 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.
+ */
+ public void kill( ) {
+ unlock();
+ assert isUnused(); // Has no uses, so it is dead
+ _type=null; // Flag as dead
+ while( nIns()>0 ) { // Set all inputs to null, recursively killing unused Nodes
+ Node old_def = _inputs.removeLast();
+ if( old_def != null ) {
+ IterPeeps.add(old_def);// Revisit neighbor because removed use
+ if( old_def.delUse(this) ) // If we removed the last use, the old def is now dead
+ old_def.kill(); // Kill old def
+ }
+ }
+ assert isDead(); // Really dead now
+ }
+
+ // Mostly used for asserts and printing.
+ public 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
+ public void subsume( Node nnn ) {
+ assert nnn!=this;
+ while( nOuts() > 0 ) {
+ Node n = _outputs.removeLast();
+ n.unlock();
+ 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.
+ * Always returns some not-null Node (often this).
+ */
+ public final Node peephole( ) {
+ if (_disablePeephole) {
+ _type = compute();
+ return this; // Peephole optimizations turned off
+ }
+ Node n = peepholeOpt();
+ return n==null ? this : deadCodeElim(n.peephole()); // Cannot return null for no-progress
+ }
+
+ /**
+ * 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>
+ *
+ * Unlike peephole above, this explicitly returns null for no-change, or not-null
+ * for a better replacement (which can be this).
+ * </ul>
+ */
+ public final Node peepholeOpt( ) {
+ ITER_CNT++;
+ // Compute initial or improved Type
+ Type old = setType(compute());
+
+ // Replace constant computations from non-constants with a constant node
+ if( !(this instanceof ConstantNode) && _type.isHighOrConst() )
+ return new ConstantNode(_type).peepholeOpt();
+
+ // Global Value Numbering
+ if( _hash==0 ) {
+ Node n = GVN.get(this); // Will set _hash as a side effect
+ if( n==null )
+ GVN.put(this,this); // Put in table now
+ else {
+ // Because of random worklist ordering, the two equal nodes
+ // might have different types. Because of monotonicity, both
+ // types are valid. To preserve monotonicity, the resulting
+ // shared Node has to have the best of both types.
+ n.setType(n._type.join(_type));
+ _hash = 0; // Clear, since it never went in the table
+ return deadCodeElim(n);// Return previous; does Common Subexpression Elimination
+ }
+ }
+
+ // Ask each node for a better replacement
+ Node n = idealize();
+ if( n != null ) // Something changed
+ return n; // Report progress
+
+ if( old==_type ) ITER_NOP_CNT++;
+ return old==_type ? null : this; // Report progress
+ }
+ public static int ITER_CNT, ITER_NOP_CNT;
+
+ // 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>
+ * {@link #compute()} needs to be stand-alone, and cannot recursively call compute
+ * on its inputs, because 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();
+
+ // Set the type. Assert monotonic progress.
+ // If changing, add users to worklist.
+ public Type setType(Type type) {
+ Type old = _type;
+ assert old==null || type.isa(old); // Since _type not set, can just re-run this in assert in the debugger
+ if( old == type ) return old;
+ _type = type; // Set _type late for easier assert debugging
+ IterPeeps.addAll(_outputs);
+ moveDepsToWorklist();
+ return old;
+ }
+
+
+ /**
+ * 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();
+
+
+ // Some of the peephole rules get complex, and search further afield than
+ // just the nearest neighbor. These peepholes can fail the pattern match
+ // on a node some distance away, and if that node ever changes we should
+ // retry the peephole. Track a set of Nodes dependent on `this`, and
+ // revisit them if `this` changes.
+ ArrayList<Node> _deps;
+
+ /**
+ * Add a node to the list o dependencies. Only add it if its not
+ * an input or output of this node, that is, it is at least one step
+ * away. The node being added must benefit from this node being peepholed.
+ */
+ Node addDep( Node dep ) {
+ // Running peepholes during the big assert cannot have side effects
+ // like adding dependencies.
+ if( IterPeeps.midAssert() ) return this;
+ if( _deps==null ) _deps = new ArrayList<>();
+ if( Utils.find(_deps ,dep) != -1 ) return this; // Already on list
+ if( Utils.find(_inputs,dep) != -1 ) return this; // No need for deps on immediate neighbors
+ if( Utils.find(_outputs,dep)!= -1 ) return this;
+ _deps.add(dep);
+ return this;
+ }
+
+ // Move the dependents onto a worklist, and clear for future dependents.
+ public void moveDepsToWorklist( ) {
+ if( _deps==null ) return;
+ IterPeeps.addAll(_deps);
+ _deps.clear();
+ }
+
+
+ // Global Value Numbering. Hash over opcode and inputs; hits in this table
+ // are structurally equal.
+ public static final HashMap<Node,Node> GVN = new HashMap<>();
+
+ // Two nodes are equal if they have the same inputs and the same "opcode"
+ // which means the same Java class, plus same internal parts.
+ @Override public final boolean equals( Object o ) {
+ if( o==this ) return true;
+ if( o.getClass() != getClass() ) return false;
+ Node n = (Node)o;
+ int len = _inputs.size();
+ if( len != n._inputs.size() ) return false;
+ for( int i=0; i<len; i++ )
+ if( in(i) != n.in(i) )
+ return false;
+ return eq(n);
+ }
+ // Subclasses add extra checks (such as ConstantNodes have same constant),
+ // and can assume "this!=n" and has the same Java class.
+ boolean eq( Node n ) { return true; }
+
+
+ // Cached hash. If zero, then not computed AND this Node is NOT in the GVN
+ // table - and can have its edges hacked (which will change his hash
+ // anyway). If Non-Zero then this Node is IN the GVN table, or is being
+ // probed to see if it can be inserted. His edges are "locked", because
+ // hacking his edges will change his hash.
+ int _hash;
+
+ // If the _hash is set, then the Node is in the GVN table; remove it.
+ void unlock() {
+ if( _hash==0 ) return;
+ Node old = GVN.remove(this); // Pull from table
+ assert old==this;
+ _hash=0; // Out of table now
+ }
+
+
+ // Hash of opcode and inputs
+ @Override public final int hashCode() {
+ if( _hash != 0 ) return _hash;
+ int hash = hash();
+ for( Node n : _inputs )
+ if( n != null )
+ hash = hash ^ (hash<<17) ^ (hash>>13) ^ n._nid;
+ if( hash==0 ) hash = 0xDEADBEEF; // Bad hash, so use some junky thing
+ return (_hash=hash);
+ }
+ // Subclasses add extra hash info (such as ConstantNodes constant)
+ int hash() { return 0; }
+
+
+ // ------------------------------------------------------------------------
+ // Peephole utilities
+
+ // Swap inputs without letting either input go dead during the swap.
+ Node swap12() {
+ unlock(); // Hash is order dependent
+ 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.
+ * In an input is not a constant, we add dep as
+ * a dependency to it, because dep can make progress
+ * if the input becomes a constant later.
+ * It is sufficient for one of the non-const
+ * inputs to have the dependency so we don't bother
+ * checking the rest.
+ */
+ boolean allCons(Node dep) {
+ for( int i=1; i<nIns(); i++ )
+ if( !(in(i)._type.isConstant()) ) {
+ in(i).addDep(dep); // If in(i) becomes a constant later, will trigger some peephole
+ 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"); }
+
+ // Report any post-optimize errors
+ String err() { return null; }
+
+ /**
+ * 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;
+ GVN.clear();
+ ITER_CNT = ITER_NOP_CNT = 0;
+ }
+
+
+ // Utility to walk the entire graph applying a function; return the first
+ // not-null result.
+ private static final BitSet WVISIT = new BitSet();
+ final public <E> E walk( Function<Node,E> pred ) {
+ assert WVISIT.isEmpty();
+ E rez = _walk(pred);
+ WVISIT.clear();
+ return rez;
+ }
+
+ private <E> E _walk( Function<Node,E> pred ) {
+ if( WVISIT.get(_nid) ) return null; // Been there, done that
+ WVISIT.set(_nid);
+ E x = pred.apply(this);
+ if( x != null ) return x;
+ for( Node def : _inputs ) if( def != null && (x = def._walk(pred)) != null ) return x;
+ for( Node use : _outputs ) if( use != null && (x = use._walk(pred)) != null ) return x;
+ return null;
+ }
+
+ /**
+ * Debugging utility to find a Node by index
+ */
+ public Node find(int nid) {
+ return walk( n -> n._nid==nid ? n : null );
+ }
+}
diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/NotNode.java
new file mode 100644
index 00000000..05e44eb0
--- /dev/null
+++ b/chapter10/src/main/java/com/seaofnodes/simple/node/NotNode.java
@@ -0,0 +1,44 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.type.*;
+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() {
+ Type t0 = in(1)._type;
+ switch( t0 ) {
+ case TypeInteger i0:
+ return i0.isConstant() ? TypeInteger.constant(i0.value()==0 ? 1 : 0) : i0;
+ case TypeMemPtr p0:
+ // top->top, bot->bot, null->1, *void->0, not-null ptr->0, ptr/nil->bot
+ // If input in null then true
+ // If input is not null ptr then false
+ if( p0 == TypeMemPtr.TOP ) return TypeInteger.TOP;
+ if( p0 == TypeMemPtr.NULLPTR ) return TypeInteger.constant(1);
+ if( !p0._nil ) return TypeInteger.constant(0);
+ return TypeInteger.BOT;
+ case Type t:
+ if( t0.getClass() != Type.class )
+ // Only doing NOT on ints and ptrs
+ throw Utils.TODO();
+ return t0==Type.TOP ? Type.TOP : Type.BOTTOM;
+ }
+ }
+
+ @Override
+ public Node idealize() { return null; }
+}
diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/PhiNode.java
new file mode 100644
index 00000000..6261c446
--- /dev/null
+++ b/chapter10/src/main/java/com/seaofnodes/simple/node/PhiNode.java
@@ -0,0 +1,156 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.Utils;
+
+import java.util.BitSet;
+
+public class PhiNode extends Node {
+
+ final String _label;
+
+ // The Phi type we compute must stay within the domain of the Phi.
+ // Example Int stays Int, Ptr stays Ptr, Control stays Control, Mem stays Mem.
+ final Type _declaredType;
+
+ public PhiNode(String label, Type declaredType, Node... inputs) { super(inputs); _label = label; assert declaredType!=null; _declaredType = declaredType; }
+
+ @Override public String label() { return "Phi_"+_label; }
+
+ @Override public String glabel() { return "&phi;_"+_label; }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ if( !(region() instanceof RegionNode r) || r.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;
+ }
+
+ Node region() { return in(0); }
+ @Override public boolean isMultiTail() { return true; }
+
+ @Override
+ public Type compute() {
+ if( !(region() instanceof RegionNode r) )
+ return region()._type==Type.XCONTROL ? Type.TOP : _type;
+ // During parsing Phis have to be computed type pessimistically.
+ if( r.inProgress() ) return _declaredType;
+ // Set type to local top of the starting type
+ Type t = _declaredType.glb().dual();
+ for (int i = 1; i < nIns(); i++)
+ // If the region's control input is live, add this as a dependency
+ // to the control because we can be peeped should it become dead.
+ if( r.in(i).addDep(this)._type != Type.XCONTROL )
+ t = t.meet(in(i)._type);
+ return t;
+ }
+
+ @Override
+ public Node idealize() {
+ if( !(region() instanceof RegionNode r ) )
+ return in(1); // Input has collapse to e.g. starting control.
+ if( r.inProgress() || r.nIns()<=1 )
+ return null; // Input is in-progress
+
+ // 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, _declaredType,lhss).peephole();
+ Node phi_rhs = new PhiNode(_label, _declaredType,rhss).peephole();
+ return op.copy(phi_lhs,phi_rhs);
+ }
+
+ // If merging Phi(N, cast(N)) - we are losing the cast JOIN effects, so just remove.
+ if( nIns()==3 ) {
+ if( in(1) instanceof CastNode cast && cast.in(1).addDep(this)==in(2) ) return in(2);
+ if( in(2) instanceof CastNode cast && cast.in(1).addDep(this)==in(1) ) return in(1);
+ }
+ // If merging a null-checked null and the checked value, just use the value.
+ // if( val ) ..; phi(Region,False=0/null,True=val);
+ // then replace with plain val.
+ if( nIns()==3 ) {
+ int nullx = -1;
+ if( in(1)._type == in(1)._type.makeInit() ) nullx = 1;
+ if( in(2)._type == in(2)._type.makeInit() ) nullx = 2;
+ if( nullx != -1 ) {
+ Node val = in(3-nullx);
+ if( region().idom() instanceof IfNode iff && iff.pred()==val ) {
+ // Must walk the idom on the null side to make sure we hit False.
+ Node idom = region().in(nullx);
+ while( idom.in(0) != iff ) idom = idom.idom();
+ if( idom instanceof ProjNode proj && proj._idx==1 )
+ return val;
+ }
+ }
+ }
+
+ 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() {
+ if( region() instanceof LoopNode loop && loop.entry()._type == Type.XCONTROL )
+ return null; // Dead entry loops just ignore and let the loop collapse
+ Node live = null;
+ for( int i=1; i<nIns(); i++ ) {
+ // If the region's control input is live, add this as a dependency
+ // to the control because we can be peeped should it become dead.
+ if( region().in(i).addDep(this)._type != Type.XCONTROL && in(i) != this )
+ if( live == null || live == in(i) ) live = in(i);
+ else return null;
+ }
+ return live;
+ }
+
+ @Override
+ boolean allCons(Node dep) {
+ if( !(region() instanceof RegionNode r) ) return false;
+ // When the region completes (is no longer in progress) the Phi can
+ // become a "all constants" Phi, and the "dep" might make progress.
+ addDep(dep);
+ if( r.inProgress() ) return false;
+ return super.allCons(dep);
+ }
+
+ // True if last input is null
+ public final boolean inProgress() {
+ return in(nIns()-1) == null;
+ }
+
+ // Never equal if inProgress
+ @Override boolean eq( Node n ) {
+ return !inProgress();
+ }
+}
diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/ProjNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/ProjNode.java
new file mode 100644
index 00000000..18526677
--- /dev/null
+++ b/chapter10/src/main/java/com/seaofnodes/simple/node/ProjNode.java
@@ -0,0 +1,62 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeMem;
+import com.seaofnodes.simple.type.TypeTuple;
+
+import java.util.BitSet;
+
+public class ProjNode extends Node {
+
+ // Which slice of the incoming multipart 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(); }
+ @Override public boolean isMem() { return _type instanceof TypeMem; }
+
+ public MultiNode ctrl() { return (MultiNode)in(0); }
+
+ @Override
+ public Type compute() {
+ Type t = in(0)._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( ctrl() instanceof IfNode && tt._types[1-_idx]==Type.XCONTROL ) // Only true for IfNodes
+ return ctrl().in(0); // We become our input control
+ }
+
+ // Flip a negating if-test, to remove the not
+ if( ctrl() instanceof IfNode iff && iff.pred().addDep(this) instanceof NotNode not )
+ return new ProjNode((MultiNode)new IfNode(iff.ctrl(),not.in(1)).peephole(),1-_idx,_idx==0 ? "False" : "True");
+
+ return null;
+ }
+
+ @Override
+ boolean eq( Node n ) { return _idx == ((ProjNode)n)._idx; }
+
+ @Override
+ int hash() { return _idx; }
+}
diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/RegionNode.java
new file mode 100644
index 00000000..f06a8234
--- /dev/null
+++ b/chapter10/src/main/java/com/seaofnodes/simple/node/RegionNode.java
@@ -0,0 +1,117 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.IterPeeps;
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.type.Type;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+
+public class RegionNode extends Node {
+
+ public RegionNode(Node... nodes) { super(nodes); }
+
+ @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() {
+ if( inProgress() ) return Type.CONTROL;
+ Type t = Type.XCONTROL;
+ for (int i = 1; i < nIns(); i++)
+ t = t.meet(in(i)._type);
+ return t;
+ }
+
+ @Override
+ public Node idealize() {
+ if( inProgress() ) return null;
+ int path = findDeadInput();
+ if( path != 0 &&
+ // Do not delete the entry path of a loop (ok to remove the back
+ // edge and make the loop a single-entry Region which folds away
+ // the Loop). Folding the entry path confused the loop structure,
+ // moving the backedge to the entry point.
+ !(this instanceof LoopNode loop && loop.entry()==in(path)) ) {
+ // Cannot use the obvious output iterator here, because a Phi
+ // deleting an input might recursively delete *itself*. This
+ // shuffles the output array, and we might miss iterating an
+ // unrelated Phi. So on rare occasions we repeat the loop to get
+ // all the Phis.
+ int nouts = 0;
+ while( nouts != nOuts() ) {
+ nouts = nOuts();
+ for( int i=0; i<nOuts(); i++ )
+ if( out(i) instanceof PhiNode phi && phi.nIns()==nIns() ) {
+ phi.delDef(path);
+ IterPeeps.addAll(phi._outputs);
+ }
+ }
+ _idom = null; // Clear idom cache
+ return isDead() ? new ConstantNode(Type.XCONTROL) : delDef(path);
+ }
+ // If down to a single input, become that input
+ if( nIns()==2 && !hasPhi() ) {
+ _idom = null; // Clear idom cache
+ return in(1); // Collapse if no Phis; 1-input Phis will collapse on their own
+ }
+ 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
+ }
+
+ private boolean hasPhi() {
+ for( Node phi : _outputs )
+ if( phi instanceof PhiNode )
+ return true;
+ return false;
+ }
+
+ // Immediate dominator of Region is a little more complicated.
+ private Node _idom; // Immediate dominator cache
+ @Override Node idom() {
+ if( _idom != null ) {
+ if( _idom.isDead() ) _idom=null;
+ else return _idom; // Return cached copy
+ }
+ if( nIns()==2 ) return in(1); // 1-input is that one input
+ 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;
+ if( !IterPeeps.midAssert() ) _idom=lhs;
+ return lhs;
+ }
+
+ // True if last input is null
+ public final boolean inProgress() {
+ return nIns()>1 && in(nIns()-1) == null;
+ }
+
+ // Never equal if inProgress
+ @Override boolean eq( Node n ) {
+ return !inProgress();
+ }
+}
diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
new file mode 100644
index 00000000..92bda5bf
--- /dev/null
+++ b/chapter10/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
@@ -0,0 +1,55 @@
+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, ScopeNode scope) {
+ super(ctrl, data);
+ // We lookup memory slices by the naming convention that they start with $
+ // We could also use implicit knowledge that all memory projects are at offset >= 2
+ String[] names = scope.reverseNames();
+ for (String name: names) {
+ if (!name.equals("$ctrl") && name.startsWith("$"))
+ addDef(scope.lookup(name));
+ }
+ }
+
+ 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) {
+ sb.append("return ");
+ expr()._print0(sb, visited);
+ return sb.append(";");
+ }
+
+ @Override public boolean isCFG() { return true; }
+
+ @Override
+ public Type compute() {
+ return TypeTuple.make(ctrl()._type,expr()._type);
+ }
+
+ @Override
+ public Node idealize() {
+ if( ctrl()._type==Type.XCONTROL )
+ return ctrl();
+ return null;
+ }
+}
diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
new file mode 100644
index 00000000..44c1bab7
--- /dev/null
+++ b/chapter10/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
@@ -0,0 +1,296 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.IterPeeps;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.type.TypeMemPtr;
+
+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;
+
+ /**
+ * Tracks declared types for every name
+ */
+ public Stack<HashMap<String, Type>> _types;
+
+
+ // A new ScopeNode
+ public ScopeNode() {
+ _scopes = new Stack<>();
+ _types = new Stack<>();
+ _type = Type.BOTTOM;
+ }
+
+
+ @Override public String label() { return "Scope"; }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ sb.append("Scope[ ");
+ String[] names = reverseNames();
+ 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("]");
+ }
+
+ /**
+ * Recover the names for all variable bindings.
+ * The result is an array of names that is aligned with the
+ * inputs to the Node.
+ *
+ * This is an expensive operation.
+ */
+ public String[] reverseNames() {
+ 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<>()); _types.push(new HashMap<>()); }
+ public void pop() { popN(_scopes.pop().size()); _types.pop(); }
+
+ /**
+ * Create a new name in the current scope
+ */
+ public Node define( String name, Type declaredType, Node n ) {
+ HashMap<String,Integer> syms = _scopes.lastElement();
+ _types.lastElement().put(name,declaredType);
+ if( syms.put(name,nIns()) != null )
+ return null; // Double define
+ return addDef(n);
+ }
+ /**
+ * Lookup a name in all scopes starting from most deeply nested.
+ *
+ * @param name Name to be looked up
+ * @see #update(String, Node, int)
+ */
+ public Node lookup( String name ) { return update(name,null,_scopes.size()-1); }
+ /**
+ * If the name is present in any scope, then redefine else null
+ *
+ * @param name Name being redefined
+ * @param n The node to bind to the name
+ */
+ public Node update( String name, Node n ) { return update(name,n,_scopes.size()-1); }
+ /**
+ * Both recursive lookup and update.
+ * <p>
+ * A shared implementation allows us to create lazy phis both during
+ * lookups and updates; the lazy phi creation is part of chapter 8.
+ *
+ * @param name Name whose binding is being updated or looked up
+ * @param n If null, do a lookup, else update binding
+ * @param nestingLevel The starting nesting level
+ * @return node being looked up, or the one that was updated
+ */
+ private Node update( String name, Node n, int nestingLevel ) {
+ if( nestingLevel<0 ) return null; // Missed in all scopes, not found
+ var syms = _scopes.get(nestingLevel); // Get the symbol table for nesting level
+ var idx = syms.get(name);
+ if( idx == null ) return update(name,n,nestingLevel-1); // Not found in this scope, recursively look in parent scope
+ 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.setDef(idx,new PhiNode(name, lookupDeclaredType(name),loop.ctrl(),loop.update(name,null,nestingLevel),null).peephole());
+ setDef(idx,old);
+ }
+ return n==null ? old : setDef(idx,n); // Not lazy, so this is the answer
+ }
+
+ // Return declared type
+ public Type lookupDeclaredType( String name ) {
+ for( int i=_types.size(); i>0; i-- ) {
+ Type t = _types.get(i-1).get(name);
+ if( t != null ) return t;
+ }
+ return null;
+ }
+
+ 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 setDef(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));
+ for( HashMap<String,Type> ts : _types )
+ dup._types.push(ts); // Types don't change, just keep stacks aligned
+
+ dup.addDef(ctrl()); // Control input is just copied
+ for( int i=1; i<nIns(); i++ )
+ // For lazy phis on loops we use a sentinel
+ // that will trigger phi creation on update
+ dup.addDef(loop ? this : 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 = reverseNames();
+ // 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
+ // If we are in lazy phi mode we need to a lookup
+ // by name as it will trigger a phi creation
+ setDef(i, new PhiNode(ns[i], this.lookupDeclaredType(ns[i]), r, this.lookup(ns[i]), that.lookup(ns[i])).peephole());
+ that.kill(); // Kill merged scope
+ IterPeeps.add(r);
+ 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.setDef(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.setDef(2,back.in(i));
+ }
+ if( exit.in(i) == this ) // Replace a lazy-phi on the exit path also
+ exit.setDef(i,in(i));
+ }
+ back.kill(); // Loop backedge is dead
+ // Now one-time do a useless-phi removal
+ for( int i=1; i<nIns(); i++ ) {
+ if( in(i) instanceof PhiNode phi ) {
+ // Do an eager useless-phi removal
+ Node in = phi.peephole();
+ IterPeeps.addAll(phi._outputs);
+ phi.moveDepsToWorklist();
+ if( in != phi ) {
+ phi.subsume(in);
+ setDef(i,in); // Set the update back into Scope
+ }
+ }
+ }
+
+ }
+
+
+ // Up-casting: using the results of an If to improve a value.
+ // E.g. "if( ptr ) ptr.field;" is legal because ptr is known not-null.
+
+ // This Scope looks for direct variable uses, or certain simple
+ // combinations, and replaces the variable with the upcast variant.
+ public Node upcast( Node ctrl, Node pred, boolean invert ) {
+ if( ctrl._type==Type.XCONTROL ) return null;
+ // Invert the If conditional
+ if( invert )
+ pred = pred instanceof NotNode not ? not.in(1) : new NotNode(pred).peephole();
+
+ // Direct use of a value as predicate. This is a zero/null test.
+ if( Utils.find(_inputs, pred) != -1 ) {
+ if( !(pred._type instanceof TypeMemPtr tmp) )
+ // Must be an `int`, since int and ptr are the only two value types
+ // being tested. No representation for a generic not-null int, so no upcast.
+ return null;
+ if( tmp.isa(TypeMemPtr.VOIDPTR) )
+ return null; // Already not-null, no reason to upcast
+ // Upcast the ptr to not-null ptr, and replace in scope
+ return replace(pred,new CastNode(TypeMemPtr.VOIDPTR,ctrl,pred).peephole());
+ }
+
+ if( pred instanceof NotNode not ) {
+ // Direct use of a !value as predicate. This is a zero/null test.
+ if( Utils.find(_inputs, not.in(1)) != -1 ) {
+ Type tinit = not.in(1)._type.makeInit();
+ if( not.in(1)._type.isa(tinit) ) return null; // Already zero/null, no reason to upcast
+ return replace(not.in(1), new ConstantNode(tinit).peephole());
+ }
+ }
+ // Apr/9/2024: Attempted to replace X with Y if guarded by a test of
+ // X==Y. This didn't seem to help very much, or at least in the test
+ // cases seen so far was a very minor help.
+
+ // No upcast
+ return null;
+ }
+
+ private Node replace( Node old, Node cast ) {
+ assert old!=null && old!=cast;
+ for( int i=0; i<nIns(); i++ )
+ if( in(i)==old )
+ setDef(i,cast);
+ return cast;
+ }
+
+}
diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/StartNode.java
new file mode 100644
index 00000000..5cfeb5f9
--- /dev/null
+++ b/chapter10/src/main/java/com/seaofnodes/simple/node/StartNode.java
@@ -0,0 +1,70 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.*;
+
+import java.util.Arrays;
+import java.util.BitSet;
+
+/**
+ * The Start node represents the start of the function.
+ *
+ * Start initially has 1 input (arg) from outside and the initial control.
+ * In ch10 we also add mem aliases as structs get defined; each field in struct
+ * adds a distinct alias to Start's tuple.
+ */
+public class StartNode extends MultiNode {
+
+ // This field is NOT final, because the tuple expands as we add memory aliases
+ TypeTuple _args;
+
+ public StartNode(Type[] args) {
+ super();
+ _args = TypeTuple.make(args);
+ _type = _args;
+ }
+
+ /**
+ * Creates a projection for each of the struct's fields, using the field alias
+ * as the key.
+ */
+ public void addMemProj( TypeStruct ts, ScopeNode scope) {
+ if( ts._fields.length==0 ) return; // No projections to add
+ int len = _args._types.length;
+ int max = ts._fields[ts._fields.length-1]._alias; // Max alias
+ if( len > max ) return; // Already big enough
+ // resize the tuple's type array to include all fields of the struct
+ Type[] args = Arrays.copyOf(_args._types, max+1);
+ // The new members of the tuple get a mem type with an alias
+ for( int alias = len; alias <= max; alias++ )
+ args[alias] = TypeMem.make(alias);
+ _type = _args = TypeTuple.make(args);
+ // For each of the fields we now add a mem projection. Note that the
+ // alias matches the slot of the field in the tuple
+ for( int alias = len; alias <= max; alias++ ) {
+ String name = "$"+alias;
+ Node n = new ProjNode(this, alias, name).peephole();
+ scope.define(name, args[alias], n);
+ }
+ }
+
+ @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/chapter10/src/main/java/com/seaofnodes/simple/node/StopNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/StopNode.java
new file mode 100644
index 00000000..0bf9eb66
--- /dev/null
+++ b/chapter10/src/main/java/com/seaofnodes/simple/node/StopNode.java
@@ -0,0 +1,65 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.IterPeeps;
+
+import java.util.BitSet;
+
+public class StopNode extends Node {
+
+ public final String _src;
+
+ public StopNode(String src) {
+ super();
+ _src = src;
+ }
+
+ @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 addDef(node);
+ }
+
+ public StopNode iterate( ) { return IterPeeps.iterate(this,false).typeCheck(); }
+ public StopNode iterate(boolean show) { return IterPeeps.iterate(this,show ).typeCheck(); }
+ StopNode typeCheck() {
+ String err = walk( Node::err );
+ if( err != null ) throw new RuntimeException(err);
+ return this;
+ }
+}
diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/StoreNode.java
new file mode 100644
index 00000000..07e4e3f7
--- /dev/null
+++ b/chapter10/src/main/java/com/seaofnodes/simple/node/StoreNode.java
@@ -0,0 +1,46 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.Field;
+import com.seaofnodes.simple.type.TypeMem;
+
+import java.util.BitSet;
+
+/**
+ * Store represents setting a value to a memory based object, in chapter 10
+ * this means a field inside a struct.
+ */
+public class StoreNode extends MemOpNode {
+
+ /**
+ * @param field The struct field we are assigning to
+ * @param memSlice The memory alias node - this is updated after a Store
+ * @param memPtr The ptr to the struct where we will store a value
+ * @param value Value to be stored
+ */
+ public StoreNode(Field field, Node memSlice, Node memPtr, Node value) {
+ super(field, memSlice, memPtr, value);
+ }
+
+ @Override
+ public String label() { return "Store"; }
+ @Override
+ public String glabel() { return "."+_field._fname+" ="; }
+ @Override
+ public boolean isMem() { return true; }
+
+ @Override
+ StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return sb.append(".").append(_field._fname).append("=").append( val()).append(";");
+ }
+
+ @Override
+ public Type compute() {
+ return TypeMem.make(_field._alias);
+ }
+
+ @Override
+ public Node idealize() {
+ return null;
+ }
+}
diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/SubNode.java
new file mode 100644
index 00000000..f81ae6d0
--- /dev/null
+++ b/chapter10/src/main/java/com/seaofnodes/simple/node/SubNode.java
@@ -0,0 +1,42 @@
+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 in(1)._type.meet(in(2)._type);
+ }
+
+ @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/chapter10/src/main/java/com/seaofnodes/simple/type/Field.java b/chapter10/src/main/java/com/seaofnodes/simple/type/Field.java
new file mode 100644
index 00000000..179eacf5
--- /dev/null
+++ b/chapter10/src/main/java/com/seaofnodes/simple/type/Field.java
@@ -0,0 +1,96 @@
+package com.seaofnodes.simple.type;
+
+import com.seaofnodes.simple.Utils;
+import java.util.ArrayList;
+
+/**
+ * Represents a field in a struct. This is not a Type in the type system.
+ */
+public class Field extends Type {
+
+ /**
+ * Represents the starting alias ID - which is 2 because then it nicely
+ * slots into Start's projections. Start already uses slots 0-1.
+ */
+ private static final int _RESET_ALIAS_ID = 2;
+
+ /**
+ * Alias ID generator - we start at 2 because START uses 0 and 1 slots,
+ * by starting at 2, our alias ID is nicely mapped to a slot in Start.
+ */
+ private static int _ALIAS_ID = _RESET_ALIAS_ID;
+
+ // The triple {structName,type,fieldName} uniquely identifies a field.
+
+ // Struct name
+ public final String _sname;
+ // Type of the field
+ public final Type _type;
+ // Field name
+ public final String _fname;
+
+ // Convenience field: Owning TypeStruct
+ public TypeStruct _obj;
+
+ // Convenience field: unique alias number
+ public final int _alias;
+
+ private Field(String sname, Type type, String fname, int alias) {
+ super(TFLD);
+ _sname = sname;
+ _type = type;
+ _fname = fname;
+ _alias = alias;
+ }
+ // Make with existing alias
+ private static Field make( String sname, Type type, String fname, int alias ) {
+ return new Field(sname,type,fname,alias).intern();
+ }
+ // Make with new alias
+ public static Field make( String sname, Type type, String fname ) {
+ Field fld = make(sname,type,fname,_ALIAS_ID).intern();
+ if( fld._alias == _ALIAS_ID )
+ _ALIAS_ID++;
+ return fld;
+ }
+
+ public static final Field TEST = make("test",TypeInteger.ZERO,"test");
+ public static void gather(ArrayList<Type> ts) { ts.add(TEST); }
+
+ @Override Field xmeet( Type that ) {
+ Field fld = (Field)that; // Invariant
+ assert _sname.equals(fld._sname);
+ assert _fname.equals(fld._fname);
+ return make(_sname,_type.meet(fld._type),_fname);
+ }
+
+ @Override
+ public Field dual() { return make(_sname,_type.dual(),_fname,_alias); }
+
+ public String aliasName() { return "$"+_alias; };
+
+ @Override
+ public Field glb() {
+ return make(_sname,_type.glb(),_fname,_alias);
+ }
+
+ // Override in subclasses
+ int hash() { return _sname.hashCode() ^ _type.hashCode() ^ _fname.hashCode(); }
+
+ boolean eq(Type t) {
+ Field f = (Field)t;
+ return _sname.equals(f._sname) && _type==f._type && _fname.equals(f._fname);
+ }
+
+
+ @Override
+ public StringBuilder _print( StringBuilder sb ) {
+ return _type._print(sb.append(_fname).append(":"));
+ }
+
+ @Override public String str() { return _fname; }
+
+ static void resetField() {
+ _ALIAS_ID = _RESET_ALIAS_ID;
+ }
+}
diff --git a/chapter10/src/main/java/com/seaofnodes/simple/type/Type.java b/chapter10/src/main/java/com/seaofnodes/simple/type/Type.java
new file mode 100644
index 00000000..34e66a0c
--- /dev/null
+++ b/chapter10/src/main/java/com/seaofnodes/simple/type/Type.java
@@ -0,0 +1,191 @@
+package com.seaofnodes.simple.type;
+
+import com.seaofnodes.simple.Utils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * 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 {
+ static final HashMap<Type,Type> INTERN = new HashMap<>();
+
+ // ----------------------------------------------------------
+ // 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
+ static final byte TMEM = 7; // All memory (alias 0) or A slice of memory - with specific alias
+ static final byte TMEMPTR = 8; // Memory pointer type
+ static final byte TSTRUCT = 9; // Structs; tuples with named fields
+ static final byte TFLD =10; // Fields into struct
+
+ 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 ).intern(); // ALL
+ public static final Type TOP = new Type( TTOP ).intern(); // ANY
+ public static final Type CONTROL = new Type( TCTRL ).intern(); // Ctrl
+ public static final Type XCONTROL = new Type( TXCTRL ).intern(); // ~Ctrl
+ public static Type[] gather() {
+ ArrayList<Type> ts = new ArrayList<>();
+ ts.add(BOTTOM);
+ ts.add(CONTROL);
+ Field.gather(ts);
+ TypeInteger.gather(ts);
+ TypeMem.gather(ts);
+ TypeMemPtr.gather(ts);
+ TypeStruct.gather(ts);
+ TypeTuple.gather(ts);
+ int sz = ts.size();
+ for( int i = 0; i < sz; i++ )
+ ts.add(ts.get(i).dual());
+ return ts.toArray(new Type[ts.size()]);
+ }
+
+ // Is high or on the lattice centerline.
+ public boolean isHighOrConst() { return _type==TTOP || _type==TXCTRL; }
+
+ // Strict constant values, things on the lattice centerline.
+ // Excludes both high and low values
+ public boolean isConstant() { return false; }
+
+ /**
+ * Display Type name in a format that's good for IR printer
+ */
+ public StringBuilder typeName( StringBuilder sb) { return _print(sb); }
+
+ public Type makeInit() { return null; }
+
+ // ----------------------------------------------------------
+
+ // Notes on Type interning.
+ // At the moment it is not easy to reset the interned types
+ // because we hold static references to several types and these are scattered
+ // around. This means the INTERN cache will retain all types from
+ // every run of the Parser. For this to work correctly types must be
+ // rigorous about defining when they are the same. Also types need to be
+ // immutable once defined.
+ // The rationale for interning is performance.
+
+ // Factory method which interns "this"
+ public <T extends Type> T intern() {
+ T nnn = (T)INTERN.get(this);
+ if( nnn==null )
+ INTERN.put(nnn=(T)this,this);
+ return nnn;
+ }
+
+ private int _hash; // Hash cache; not-zero when set.
+ @Override
+ public final int hashCode() {
+ if( _hash!=0 ) return _hash;
+ _hash = hash();
+ if( _hash==0 ) _hash = 0xDEADBEEF; // Bad hash from subclass; use some junk thing
+ return _hash;
+ }
+ // Override in subclasses
+ int hash() { return _type; }
+
+ @Override
+ public final boolean equals( Object o ) {
+ if( o==this ) return true;
+ if( !(o instanceof Type t)) return false;
+ if( _type != t._type ) return false;
+ return eq(t);
+ }
+ // Overridden in subclasses; subclass can assume "this!=t" and java classes are same
+ boolean eq(Type t) { return true; }
+
+
+ // ----------------------------------------------------------
+ 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 Type.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()'.
+ 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;
+ }
+
+ public Type dual() {
+ return switch( _type ) {
+ case TBOT -> TOP;
+ case TTOP -> BOTTOM;
+ case TCTRL -> XCONTROL;
+ case TXCTRL -> CONTROL;
+ default -> throw Utils.TODO(); // Should not reach here
+ };
+ }
+
+ // ----------------------------------------------------------
+ // Our lattice is defined with a MEET and a DUAL.
+ // JOIN is dual of meet of both duals.
+ public final Type join(Type t) {
+ if( this==t ) return this;
+ return dual().meet(t.dual()).dual();
+ }
+
+ // True if this "isa" t; e.g. 17 isa TypeInteger.BOT
+ public boolean isa( Type t ) { return meet(t)==t; }
+
+ /**
+ * Compute greatest lower bound in the lattice
+ */
+ public Type glb() { return _type==TCTRL ? XCONTROL : BOTTOM; }
+
+ // ----------------------------------------------------------
+ // Useful in the debugger, which calls toString everywhere.
+ // This is a more verbose dev-friendly print.
+ @Override
+ public final String toString() {
+ return _print(new StringBuilder()).toString();
+ }
+
+ public StringBuilder _print(StringBuilder sb) { return is_simple() ? sb.append(STRS[_type]) : sb;}
+
+ // This is used by error messages, and is a shorted print.
+ public String str() { return STRS[_type]; }
+
+ public static void reset() {
+ Field.resetField();
+ }
+}
diff --git a/chapter10/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/chapter10/src/main/java/com/seaofnodes/simple/type/TypeInteger.java
new file mode 100644
index 00000000..f2a93a3a
--- /dev/null
+++ b/chapter10/src/main/java/com/seaofnodes/simple/type/TypeInteger.java
@@ -0,0 +1,101 @@
+package com.seaofnodes.simple.type;
+
+import java.util.ArrayList;
+
+/**
+ * Integer Type
+ */
+public class TypeInteger extends Type {
+
+ public final static TypeInteger TOP = make(false, 0);
+ public final static TypeInteger BOT = make(false, 1);
+ public final static TypeInteger ZERO= make(true , 0);
+
+ public final boolean _is_con;
+
+ /**
+ * The constant value or
+ * if not constant then 1=bottom, 0=top.
+ */
+ public final long _con;
+
+ private TypeInteger(boolean is_con, long con) {
+ super(TINT);
+ _is_con = is_con;
+ _con = con;
+ }
+ public static TypeInteger make(boolean is_con, long con) {
+ return new TypeInteger(is_con,con).intern();
+ }
+
+ public static TypeInteger constant(long con) { return make(true, con); }
+
+ public static void gather(ArrayList<Type> ts) { ts.add(ZERO); ts.add(BOT); }
+
+ // FIXME this display format is problematic
+ // In visualizer '#' gets prepended if its a constant
+ @Override
+ public StringBuilder _print(StringBuilder sb) {
+ if( this==TOP ) return sb.append("IntTop");
+ if( this==BOT ) return sb.append("IntBot");
+ return sb.append(_con);
+ }
+
+ @Override public String str() {
+ if( this==TOP ) return "~int";
+ if( this==BOT ) return "int";
+ return ""+_con;
+ }
+
+ /**
+ * Display Type name in a format that's good for IR printer
+ */
+ @Override
+ public StringBuilder typeName(StringBuilder sb) {
+ if( this==TOP ) return sb.append("IntTop");
+ if( this==BOT ) return sb.append("IntBot");
+ return sb.append("Int");
+ }
+
+ @Override
+ public boolean isHighOrConst() { return _is_con || _con==0; }
+
+ @Override
+ public boolean isConstant() { return _is_con; }
+
+ public long value() { return _con; }
+
+ @Override
+ public Type xmeet(Type other) {
+ // Invariant from caller: 'this' != 'other' and same class (TypeInteger)
+ TypeInteger i = (TypeInteger)other; // Contract
+ // BOT wins
+ if ( this==BOT ) return this;
+ if ( i ==BOT ) return i ;
+ // TOP loses
+ if ( i ==TOP ) return this;
+ if ( this==TOP ) return i ;
+ // Since both are constants, and are never equals (contract) unequals
+ // constants fall to bottom
+ return BOT;
+ }
+
+ @Override
+ public Type dual() {
+ if( isConstant() ) return this; // Constants are a self-dual
+ return _con==0 ? BOT : TOP;
+ }
+
+ @Override
+ public Type glb() { return BOT; }
+
+ @Override public TypeInteger makeInit() { return ZERO; }
+
+ @Override
+ int hash() { return (int)(_con ^ (_is_con ? 0 : 0x4000)); }
+ @Override
+ public boolean eq( Type t ) {
+ TypeInteger i = (TypeInteger)t; // Contract
+ return _con==i._con && _is_con==i._is_con;
+ }
+}
diff --git a/chapter10/src/main/java/com/seaofnodes/simple/type/TypeMem.java b/chapter10/src/main/java/com/seaofnodes/simple/type/TypeMem.java
new file mode 100644
index 00000000..198d49ee
--- /dev/null
+++ b/chapter10/src/main/java/com/seaofnodes/simple/type/TypeMem.java
@@ -0,0 +1,64 @@
+package com.seaofnodes.simple.type;
+
+import com.seaofnodes.simple.Utils;
+import java.lang.Long;
+import java.util.BitSet;
+import java.util.ArrayList;
+
+
+/**
+ * Represents a slice of memory corresponding to a set of aliases
+ */
+public class TypeMem extends Type {
+
+ // Which slice of memory?
+ // 0 means TOP, no slice.
+ // -1 means BOT, all memory.
+ // N means slice#N.
+ private final int _alias;
+
+ private TypeMem(int alias) { super(TMEM); _alias = alias; }
+
+ public static TypeMem make(int alias) { return new TypeMem(alias).intern(); }
+ public static final TypeMem TOP = make( 0);
+ public static final TypeMem BOT = make(-1);
+
+
+ public static void gather(ArrayList<Type> ts) { ts.add(make(1)); ts.add(BOT); }
+
+ @Override
+ TypeMem xmeet(Type t) {
+ TypeMem that = (TypeMem) t; // Invariant: TypeMem and unequal
+ return _alias==0 ? that : (that._alias==0 ? this : BOT);
+ }
+
+ @Override
+ public Type dual() {
+ if( _alias== 0 ) return BOT;
+ if( _alias==-1 ) return TOP;
+ return this;
+ }
+
+ @Override
+ public Type glb() { return TypeMem.BOT; }
+
+ @Override
+ int hash() { return 9876543 + _alias; }
+
+ @Override
+ boolean eq(Type t) {
+ TypeMem that = (TypeMem) t; // Invariant
+ return _alias == that._alias;
+ }
+
+ @Override
+ public StringBuilder _print(StringBuilder sb) {
+ return sb.append("MEM#").append( switch(_alias) {
+ case 0 -> "TOP";
+ case -1 -> "BOT";
+ default -> ""+_alias;
+ });
+ }
+
+ @Override public String str() { return _print(new StringBuilder()).toString(); }
+}
diff --git a/chapter10/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java b/chapter10/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java
new file mode 100644
index 00000000..65352402
--- /dev/null
+++ b/chapter10/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java
@@ -0,0 +1,78 @@
+package com.seaofnodes.simple.type;
+
+import java.util.ArrayList;
+
+/**
+ * Represents a Pointer to memory.
+ *
+ * Null is generic pointer to non-existent memory.
+ * *void is a non-Null pointer to all possible structs.
+ * Pointers can be to specific struct types, or a union with Null.
+ * The distinguished *$BOT ptr represents union of *void and Null.
+ * The distinguished *$TOP ptr represents the dual of *$BOT.
+ */
+public class TypeMemPtr extends Type {
+ // A TypeMemPtr is pair (obj,nil)
+ // where obj is one of
+ // (null,TypeStruct).
+ // where nil is one of
+ // (true,false) meaning an explicit null is allowed or not
+
+ public final TypeStruct _obj; // null, a TypeStruct, or sentinel TypeStruct.MANY
+ public final boolean _nil;
+
+ private TypeMemPtr(TypeStruct obj, boolean nil) {
+ super(TMEMPTR);
+ _obj = obj;
+ _nil = nil;
+ }
+ public static TypeMemPtr make(TypeStruct obj, boolean nil) { return new TypeMemPtr(obj, nil).intern(); }
+ public static TypeMemPtr make(TypeStruct obj) { return TypeMemPtr.make(obj, false); }
+
+ public static TypeMemPtr BOT = make(TypeStruct.BOT,true);
+ public static TypeMemPtr TOP = BOT.dual();
+ public static TypeMemPtr NULLPTR = make(TypeStruct.TOP,true);
+ public static TypeMemPtr VOIDPTR = NULLPTR.dual(); // A bottom mix of not-null ptrs, like C's void* but not null
+ public static TypeMemPtr TEST= make(TypeStruct.TEST,false);
+ public static void gather(ArrayList<Type> ts) { ts.add(NULLPTR); ts.add(BOT); ts.add(TEST); }
+
+ @Override
+ Type xmeet(Type t) {
+ TypeMemPtr that = (TypeMemPtr) t;
+ return TypeMemPtr.make((TypeStruct)_obj.meet(that._obj), _nil | that._nil);
+ }
+
+ @Override
+ public TypeMemPtr dual() { return TypeMemPtr.make(_obj==null ? null : _obj.dual(), !_nil); }
+
+ @Override
+ public Type glb() {
+ if( _obj==null ) return BOT;
+ return make(_obj.glb(),true);
+ }
+ @Override public TypeMemPtr makeInit() { return NULLPTR; }
+
+ @Override
+ int hash() { return (_obj==null ? 0xDEADBEEF : _obj.hashCode()) ^ (_nil ? 1024 : 0); }
+
+ @Override
+ boolean eq(Type t) {
+ TypeMemPtr ptr = (TypeMemPtr)t; // Invariant
+ return _obj == ptr._obj && _nil == ptr._nil;
+ }
+
+ // [void,name,MANY]*[,?]
+ @Override
+ public StringBuilder _print(StringBuilder sb) {
+ if( this== NULLPTR) return sb.append("null");
+ if( this== VOIDPTR) return sb.append("*void");
+ return _obj._print(sb.append("*")).append(_nil ? "?" : "");
+ }
+
+ @Override public String str() {
+ if( this== NULLPTR) return "null";
+ if( this== VOIDPTR) return "*void";
+ return "*"+_obj.str()+(_nil ? "?" : "");
+ }
+
+}
diff --git a/chapter10/src/main/java/com/seaofnodes/simple/type/TypeStruct.java b/chapter10/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
new file mode 100644
index 00000000..a98093b2
--- /dev/null
+++ b/chapter10/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
@@ -0,0 +1,123 @@
+package com.seaofnodes.simple.type;
+
+import com.seaofnodes.simple.Utils;
+import java.util.ArrayList;
+
+/**
+ * Represents a struct type.
+ */
+public class TypeStruct extends Type {
+
+ // For now in terms of the lattice
+ // a struct type just stays as is
+
+ public final String _name;
+ public final Field[] _fields;
+
+ private TypeStruct(String name, Field[] fields) {
+ super(Type.TSTRUCT);
+ _name = name;
+ _fields = fields;
+ for( Field f : _fields )
+ f._obj = this;
+ }
+
+ public static TypeStruct make(String name, Field[] fields) { return new TypeStruct(name, fields).intern(); }
+ public static TypeStruct make(String name, ArrayList<Field> fields) { return make(name,fields.toArray(new Field[fields.size()])); }
+
+ public static final TypeStruct TOP = make("$TOP",new Field [0]);
+ public static final TypeStruct BOT = make("$BOT",new Field [0]);
+ public static final TypeStruct TEST = make("test",new Field[]{Field.TEST});
+ public static void gather(ArrayList<Type> ts) { ts.add(TEST); ts.add(BOT); }
+
+ public Field get(String fname) {
+ for( Field f : _fields )
+ if( f._fname.equals(fname) )
+ return f;
+ return null;
+ }
+
+ @Override
+ Type xmeet(Type t) {
+ TypeStruct that = (TypeStruct) t;
+ if( this==TOP ) return that;
+ if( that==TOP ) return this;
+ if( this==BOT ) return BOT;
+ if( that==BOT ) return BOT;
+
+ // Within the same compilation unit, struct names are unique. If the
+ // names differ, its different structs. Across many compilation units,
+ // structs with the same name but different field layouts can be
+ // interned... which begs the question:
+ // "What is the meet of structs from two different compilation units?"
+ // And the answer is: "don't ask".
+ if( !_name.equals(that._name) )
+ return BOT; // It's a struct; that's about all we know
+ // Now all fields should be the same, so just do field meets
+ assert _fields.length == that._fields.length;
+ Field[] flds = new Field[_fields.length];
+ for( int i=0; i<_fields.length; i++ )
+ flds[i] = (Field)_fields[i].meet(that._fields[i]);
+ return make(_name,flds);
+ }
+
+ @Override
+ public TypeStruct dual() {
+ if( this==TOP ) return BOT;
+ if( this==BOT ) return TOP;
+ Field[] flds = new Field[_fields.length];
+ for( int i=0; i<_fields.length; i++ )
+ flds[i] = _fields[i].dual();
+ return make(_name,flds);
+ }
+
+ // Keeps the same struct, but lower-bounds all fields.
+ @Override
+ public TypeStruct glb() {
+ if( _glb() ) return this;
+ // Need to glb each field
+ Field[] flds = new Field[_fields.length];
+ for( int i=0; i<_fields.length; i++ )
+ flds[i] = _fields[i].glb();
+ return make(_name,flds);
+ }
+ private boolean _glb() {
+ for( Field f : _fields )
+ if( f.glb() != f )
+ return false;
+ return true;
+ }
+
+ @Override
+ boolean eq(Type t) {
+ TypeStruct ts = (TypeStruct)t; // Invariant
+ if( !_name.equals(ts._name) )
+ return false;
+ if( _fields.length!=ts._fields.length )
+ return false;
+ for( int i = 0; i < _fields.length; i++ )
+ if( _fields[i] != ts._fields[i] )
+ return false;
+ return true;
+ }
+
+ @Override
+ int hash() {
+ long hash = _name.hashCode();
+ for( Field f : _fields ) hash = Utils.rot(hash,13) ^ f.hashCode();
+ return Utils.fold(hash);
+ }
+
+ @Override
+ public StringBuilder _print(StringBuilder sb) {
+ sb.append(_name).append(" {\n");
+ for( Field f : _fields ) {
+ sb.append(" ").append(f._fname).append(":");
+ f._type._print(sb);
+ sb.append(";\n");
+ }
+ return sb.append("}");
+ }
+
+ @Override public String str() { return _name; }
+}
diff --git a/chapter10/src/main/java/com/seaofnodes/simple/type/TypeTuple.java b/chapter10/src/main/java/com/seaofnodes/simple/type/TypeTuple.java
new file mode 100644
index 00000000..d57f6c98
--- /dev/null
+++ b/chapter10/src/main/java/com/seaofnodes/simple/type/TypeTuple.java
@@ -0,0 +1,88 @@
+package com.seaofnodes.simple.type;
+
+import java.util.ArrayList;
+
+public class TypeTuple extends Type {
+
+ public final Type[] _types;
+
+ private TypeTuple(Type[] types) { super(TTUPLE); _types = types; }
+ public static TypeTuple make(Type... types) { return new TypeTuple(types).intern(); }
+
+ private static TypeTuple TEST = make(TypeInteger.BOT,TypeMemPtr.TEST);
+ public static void gather(ArrayList<Type> ts) { ts.add(TEST); }
+
+ @Override
+ Type xmeet(Type other) {
+ TypeTuple tt = (TypeTuple)other; // contract from xmeet
+ assert _types.length == tt._types.length;
+ Type[] ts = new Type[_types.length];
+ for( int i=0; i<_types.length; i++ )
+ ts[i] = _types[i].meet(tt._types[i]);
+ return make(ts);
+ }
+
+ @Override public Type dual() {
+ Type[] ts = new Type[_types.length];
+ for( int i=0; i<_types.length; i++ )
+ ts[i] = _types[i].dual();
+ return make(ts);
+ }
+
+ @Override
+ public Type glb() {
+ Type[] ts = new Type[_types.length];
+ for( int i=0; i<_types.length; i++ )
+ ts[i] = _types[i].glb();
+ return make(ts);
+ }
+
+ @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;
+ }
+
+ @Override public String str() { return _print(new StringBuilder()).toString(); }
+
+ /**
+ * Display Type name in a format that's good for IR printer
+ */
+ @Override
+ public StringBuilder typeName(StringBuilder sb) {
+ sb.append("[");
+ for( Type t : _types )
+ t.typeName(sb).append(",");
+ sb.setLength(sb.length()-1);
+ sb.append("]");
+ return sb;
+ }
+
+ public static final TypeTuple IF_BOTH = make(new Type[]{Type. CONTROL,Type. CONTROL});
+ public static final TypeTuple IF_NEITHER = make(new Type[]{Type.XCONTROL,Type.XCONTROL});
+ public static final TypeTuple IF_TRUE = make(new Type[]{Type. CONTROL,Type.XCONTROL});
+ public static final TypeTuple IF_FALSE = make(new Type[]{Type.XCONTROL,Type. CONTROL});
+
+ @Override
+ int hash() {
+ int sum = 0;
+ for( Type type : _types ) sum ^= type.hashCode();
+ return sum;
+ }
+
+ @Override
+ boolean eq( Type t ) {
+ TypeTuple tt = (TypeTuple)t; // Contract
+ if( _types.length != tt._types.length ) return false;
+ for( int i=0; i<_types.length; i++ )
+ if( _types[i]!=tt._types[i] )
+ return false;
+ return true;
+ }
+
+
+}
diff --git a/chapter10/src/test/java/com/seaofnodes/simple/Chapter01Test.java b/chapter10/src/test/java/com/seaofnodes/simple/Chapter01Test.java
new file mode 100644
index 00000000..48cc716f
--- /dev/null
+++ b/chapter10/src/test/java/com/seaofnodes/simple/Chapter01Test.java
@@ -0,0 +1,50 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.Ignore;
+
+import static org.junit.Assert.*;
+
+public class Chapter01Test {
+
+ @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 && con._type instanceof TypeInteger )
+ assertEquals(TypeInteger.constant(0),con._type);
+ }
+
+
+ @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());
+ }
+ }
+}
diff --git a/chapter10/src/test/java/com/seaofnodes/simple/Chapter02Test.java b/chapter10/src/test/java/com/seaofnodes/simple/Chapter02Test.java
new file mode 100644
index 00000000..351cdb16
--- /dev/null
+++ b/chapter10/src/test/java/com/seaofnodes/simple/Chapter02Test.java
@@ -0,0 +1,99 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.Ignore;
+
+import static org.junit.Assert.*;
+
+public class Chapter02Test {
+
+ @Test
+ public void testParseGrammar() {
+ 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 testAddPeephole() {
+ Parser parser = new Parser("return 1+2;");
+ StopNode ret = parser.parse();
+ assertEquals("return 3;", ret.print());
+ }
+
+ @Test
+ public void testSubPeephole() {
+ Parser parser = new Parser("return 1-2;");
+ StopNode ret = parser.parse();
+ assertEquals("return -1;", ret.print());
+ }
+
+ @Test
+ public void testMulPeephole() {
+ Parser parser = new Parser("return 2*3;");
+ StopNode ret = parser.parse();
+ assertEquals("return 6;", ret.print());
+ }
+
+ @Test
+ public void testDivPeephole() {
+ Parser parser = new Parser("return 6/3;");
+ StopNode ret = parser.parse();
+ assertEquals("return 2;", ret.print());
+ }
+
+ @Test
+ public void testMinusPeephole() {
+ Parser parser = new Parser("return 6/-3;");
+ StopNode ret = parser.parse();
+ assertEquals("return -2;", ret.print());
+ }
+
+ @Test
+ public void testExample() {
+ 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 testBad1() {
+ try {
+ new Parser("ret").parse();
+ fail();
+ } catch( RuntimeException e ) {
+ assertEquals("Undefined name 'ret'",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());
+ }
+
+}
diff --git a/chapter10/src/test/java/com/seaofnodes/simple/Chapter03Test.java b/chapter10/src/test/java/com/seaofnodes/simple/Chapter03Test.java
new file mode 100644
index 00000000..8a11107d
--- /dev/null
+++ b/chapter10/src/test/java/com/seaofnodes/simple/Chapter03Test.java
@@ -0,0 +1,70 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.Ignore;
+
+import static org.junit.Assert.*;
+
+public class Chapter03Test {
+
+ @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 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());
+ }
+ }
+
+}
diff --git a/chapter10/src/test/java/com/seaofnodes/simple/Chapter04Test.java b/chapter10/src/test/java/com/seaofnodes/simple/Chapter04Test.java
new file mode 100644
index 00000000..ffbbe9a8
--- /dev/null
+++ b/chapter10/src/test/java/com/seaofnodes/simple/Chapter04Test.java
@@ -0,0 +1,130 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.Ignore;
+
+import static org.junit.Assert.*;
+
+public class Chapter04Test {
+
+ @Test
+ public void testPeephole() {
+ Parser parser = new Parser("return 1+arg+2; #showGraph;");
+ StopNode ret = parser.parse();
+ assertEquals("return (arg+3);", ret.print());
+ }
+
+ @Test
+ public void testPeephole2() {
+ Parser parser = new Parser("return (1+arg)+2;");
+ StopNode ret = parser.parse();
+ assertEquals("return (arg+3);", ret.print());
+ }
+
+ @Test
+ public void testAdd0() {
+ Parser parser = new Parser("return 0+arg;");
+ StopNode ret = parser.parse();
+ assertEquals("return arg;", ret.print());
+ }
+
+ @Test
+ public void testAddAddMul() {
+ Parser parser = new Parser("return arg+0+arg;");
+ StopNode ret = parser.parse();
+ assertEquals("return (arg*2);", ret.print());
+ }
+
+ @Test
+ public void testPeephole3() {
+ 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 testMul1() {
+ Parser parser = new Parser("return 1*arg;");
+ StopNode ret = parser.parse();
+ assertEquals("return arg;", ret.print());
+ }
+
+ @Test
+ public void testVarArg() {
+ 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 testConstantArg() {
+ Parser parser = new Parser("return arg; #showGraph;", TypeInteger.constant(2));
+ StopNode ret = parser.parse();
+ assertEquals("return 2;", ret.print());
+ }
+
+ @Test
+ public void testCompEq() {
+ Parser parser = new Parser("return 3==3; #showGraph;");
+ StopNode ret = parser.parse();
+ assertEquals("return 1;", ret.print());
+ }
+
+ @Test
+ public void testCompEq2() {
+ Parser parser = new Parser("return 3==4; #showGraph;");
+ StopNode ret = parser.parse();
+ assertEquals("return 0;", ret.print());
+ }
+
+ @Test
+ public void testCompNEq() {
+ Parser parser = new Parser("return 3!=3; #showGraph;");
+ StopNode ret = parser.parse();
+ assertEquals("return 0;", ret.print());
+ }
+
+ @Test
+ public void testCompNEq2() {
+ Parser parser = new Parser("return 3!=4; #showGraph;");
+ StopNode ret = parser.parse();
+ assertEquals("return 1;", ret.print());
+ }
+
+ @Test
+ public void testBug1() {
+ 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 testBug2() {
+ 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 testBug3() {
+ try {
+ new Parser("inta=1; return a;").parse();
+ fail();
+ } catch( RuntimeException e ) {
+ assertEquals("Undefined name 'inta'",e.getMessage());
+ }
+ }
+
+ @Test
+ public void testBug4() {
+ Parser parser = new Parser("return -arg;");
+ StopNode ret = parser.parse();
+ assertEquals("return (-arg);", ret.print());
+ }
+
+}
diff --git a/chapter10/src/test/java/com/seaofnodes/simple/Chapter05Test.java b/chapter10/src/test/java/com/seaofnodes/simple/Chapter05Test.java
new file mode 100644
index 00000000..e82aa7da
--- /dev/null
+++ b/chapter10/src/test/java/com/seaofnodes/simple/Chapter05Test.java
@@ -0,0 +1,225 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.Ignore;
+
+import static org.junit.Assert.*;
+
+public class Chapter05Test {
+
+ @Test
+ public void testIfStmt() {
+ 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().iterate(true);
+ assertEquals("return Phi(Region18,(arg+2),(arg-3));", ret.toString());
+ }
+
+ @Test
+ public void testTest() {
+ 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().iterate(true);
+ assertEquals("return Phi(Region16,4,3);", ret.toString());
+ }
+
+ @Test
+ public void testReturn2() {
+ 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 testIfMergeB() {
+ 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().iterate(true);
+ assertEquals("return ((arg*2)+Phi(Region21,2,3));", ret.toString());
+ }
+
+ @Test
+ public void testIfMerge2() {
+ 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().iterate(true);
+ assertEquals("return ((Phi(Region32,(arg*2),arg)+arg)+Phi(Region,4,5));", ret.toString());
+ }
+
+ @Test
+ public void testMerge3() {
+ 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().iterate();
+ assertEquals("return Phi(Region36,Phi(Region22,2,3),Phi(Region34,4,5));", stop.toString());
+ }
+
+ @Test
+ public void testMerge4() {
+ 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(Region28,2,0));", stop.toString());
+ }
+
+ @Test
+ public void testMerge5() {
+ Parser parser = new Parser(
+"""
+int a=arg==2;
+if( arg==1 )
+{
+ a=arg==3;
+}
+return a;""");
+ StopNode ret = parser.parse().iterate();
+ assertEquals("return (arg==Phi(Region16,3,2));", ret.toString());
+ }
+
+ @Test
+ public void testTrue() {
+ StopNode stop = new Parser("return true;").parse();
+ assertEquals("return 1;",stop.toString());
+ }
+
+ @Test
+ public void testHalfDef() {
+ 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 testHalfDef2() {
+ 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 testRegress1() {
+ try {
+ new Parser("if(arg==2) int a=1; else int b=2; return a;").parse();
+ fail();
+ } catch( RuntimeException e ) {
+ assertEquals("Cannot define a new name on one arm of an if",e.getMessage());
+ }
+ }
+
+
+ @Test
+ public void testBadNum() {
+ try {
+ new Parser("return 1-;").parse();
+ fail();
+ } catch( RuntimeException e ) {
+ assertEquals("Syntax error, expected an identifier or expression: ;",e.getMessage());
+ }
+ }
+
+ @Test
+ public void testKeyword1() {
+ try {
+ new Parser("int true=0; return true;").parse();
+ fail();
+ } catch( RuntimeException e ) {
+ assertEquals("Expected an identifier, found 'true'",e.getMessage());
+ }
+ }
+
+ @Test
+ public void testKeyword2() {
+ 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 testKeyword3() {
+ try {
+ new Parser("int a=1; ififif(arg)inta=2;return a;").parse();
+ fail();
+ } catch( RuntimeException e ) {
+ assertEquals("Undefined name 'ififif'",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/chapter10/src/test/java/com/seaofnodes/simple/Chapter06Test.java b/chapter10/src/test/java/com/seaofnodes/simple/Chapter06Test.java
new file mode 100644
index 00000000..6c24992c
--- /dev/null
+++ b/chapter10/src/test/java/com/seaofnodes/simple/Chapter06Test.java
@@ -0,0 +1,296 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.Ignore;
+
+import static org.junit.Assert.*;
+
+public class Chapter06Test {
+
+ @Test
+ public void testPeepholeReturn() {
+ Parser parser = new Parser(
+"""
+if( true ) return 2;
+return 1;
+""");
+ StopNode stop = parser.parse().iterate(true);
+ assertEquals("return 2;", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof ProjNode);
+ }
+
+ @Test
+ public void testPeepholeRotate() {
+ Parser parser = new Parser(
+"""
+int a = 1;
+if (arg)
+ a = 2;
+return (arg < a) < 3;
+""");
+ StopNode stop = parser.parse().iterate();
+ assertEquals("return ((arg<Phi(Region12,2,1))<3);", stop.toString());
+ }
+
+ @Test
+ public void testPeepholeCFG() {
+ Parser parser = new Parser(
+"""
+int a=1;
+if( true )
+ a=2;
+else
+ a=3;
+return a;
+""");
+ StopNode stop = parser.parse().iterate(true);
+ assertEquals("return 2;", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof ProjNode);
+ }
+
+ @Test
+ public void testIfIf() {
+ 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().iterate(true);
+ assertEquals("return Phi(Region37,42,5);", stop.toString());
+ }
+
+ @Test
+ public void testIfArgIf() {
+ 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().iterate(true);
+ assertEquals("return Phi(Region30,2,5);", stop.toString());
+ }
+
+ @Test
+ public void testMerge3With2() {
+ 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 testMerge3With1() {
+ 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().iterate(true);
+ assertEquals("return 3;", stop.toString());
+ }
+
+ @Test
+ public void testMerge3Peephole() {
+ 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().iterate(true);
+ assertEquals("return Phi(Region41,3,Phi(Region39,4,5));", stop.toString());
+ }
+
+ @Test
+ public void testMerge3Peephole1() {
+ 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().iterate(true);
+ assertEquals("return 3;", stop.toString());
+ }
+
+ @Test
+ public void testMerge3Peephole3() {
+ 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().iterate(true);
+ assertEquals("return 4;", stop.toString());
+ }
+
+ @Test
+ public void testDemo1NonConst() {
+ 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().iterate(true);
+ assertEquals("return Phi(Region24,4,1);", ret.toString());
+ }
+
+
+ @Test
+ public void testDemo1True() {
+ 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().iterate(true);
+ assertEquals("return 4;", ret.toString());
+ }
+
+ @Test
+ public void testDemo1False() {
+ 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().iterate(true);
+ assertEquals("return 1;", ret.toString());
+ }
+
+ @Test
+ public void testDemo2NonConst() {
+ 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().iterate(true);
+ assertEquals("return (Phi(Region36,Phi(Region23,2,3),0)+Phi(Region,3,1));", ret.toString());
+ }
+
+
+ @Test
+ public void testDemo2True() {
+ 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().iterate(true);
+ assertEquals("return 6;", ret.toString());
+ }
+
+ @Test
+ public void testDemo2arg2() {
+ 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().iterate(true);
+ assertEquals("return 5;", ret.toString());
+ }
+
+}
diff --git a/chapter10/src/test/java/com/seaofnodes/simple/Chapter07Test.java b/chapter10/src/test/java/com/seaofnodes/simple/Chapter07Test.java
new file mode 100644
index 00000000..29d16f32
--- /dev/null
+++ b/chapter10/src/test/java/com/seaofnodes/simple/Chapter07Test.java
@@ -0,0 +1,243 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.Ignore;
+
+import static org.junit.Assert.*;
+
+public class Chapter07Test {
+
+
+ @Test
+ public void testExample() {
+ 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 testRegression() {
+ Parser parser = new Parser(
+"""
+int a = 1;
+if(arg){}else{
+ while(a < 10) {
+ a = a + 1;
+ }
+}
+return a;
+""");
+ StopNode stop = parser.parse().iterate(true);
+ assertEquals("return Phi(Region24,1,Phi(Loop13,1,(Phi_a+1)));", stop.toString());
+ }
+
+ @Test
+ public void testWhileNested() {
+ 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().iterate(true);
+ assertEquals("return Phi(Loop8,0,Phi(Loop20,Phi_sum,(Phi_sum+Phi(Loop,0,(Phi_j+1)))));", stop.toString());
+ System.out.println(IRPrinter.prettyPrint(stop,99));
+ }
+
+ @Test
+ public void testWhileScope() {
+ 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 testWhileNestedIfAndInc() {
+ 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().iterate(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 testWhile() {
+ 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 testWhilePeep() {
+ Parser parser = new Parser(
+"""
+int a = 1;
+while(a < 10) {
+ a = a + 1;
+ a = a + 2;
+}
+return a;
+""");
+ StopNode stop = parser.parse().iterate(true);
+ assertEquals("return Phi(Loop7,1,(Phi_a+3));", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof ProjNode);
+ }
+
+ @Test
+ public void testWhile2() {
+ 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 testWhile2Peep() {
+ Parser parser = new Parser(
+"""
+int a = 1;
+while(arg) a = 2;
+return a;
+""");
+ StopNode stop = parser.parse().iterate();
+ assertEquals("return Phi(Loop7,1,2);", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof ProjNode);
+ System.out.println(IRPrinter.prettyPrint(stop, 99));
+ }
+
+ @Test
+ public void testWhile3() {
+ 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 testWhile3Peep() {
+ Parser parser = new Parser(
+"""
+int a = 1;
+while(a < 10) {
+ int b = a + 1;
+ a = b + 2;
+}
+return a;
+""");
+ StopNode stop = parser.parse().iterate(true);
+ assertEquals("return Phi(Loop7,1,(Phi_a+3));", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof ProjNode);
+ }
+
+ @Test
+ public void testWhile4() {
+ 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 testWhile4Peep() {
+ 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().iterate(true);
+ assertEquals("return Phi(Loop8,1,(Phi_a+3));", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof ProjNode);
+ }
+}
diff --git a/chapter10/src/test/java/com/seaofnodes/simple/Chapter08Test.java b/chapter10/src/test/java/com/seaofnodes/simple/Chapter08Test.java
new file mode 100644
index 00000000..05485d8c
--- /dev/null
+++ b/chapter10/src/test/java/com/seaofnodes/simple/Chapter08Test.java
@@ -0,0 +1,211 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.Ignore;
+
+import static org.junit.Assert.*;
+
+public class Chapter08Test {
+
+ @Test
+ public void testEx6() {
+ Parser parser = new Parser(
+ """
+while(arg < 10) {
+ arg = arg + 1;
+ if (arg == 5)
+ break;
+ if (arg == 6)
+ break;
+}
+return arg;
+ """);
+ StopNode stop = parser.parse().iterate(true);
+ assertEquals("return Phi(Region36,Phi(Region25,Phi(Loop6,arg,(Phi_arg+1)),Add),Add);", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof RegionNode);
+ Assert.assertEquals(5, GraphEvaluator.evaluate(stop, 1));
+ Assert.assertEquals(10, GraphEvaluator.evaluate(stop, 6));
+ }
+
+
+ @Test
+ public void testEx5() {
+ Parser parser = new Parser(
+ """
+int a = 1;
+while(arg < 10) {
+ arg = arg + 1;
+ if (arg == 5)
+ continue;
+ if (arg == 7)
+ continue;
+ a = a + 1;
+}
+return a;
+ """);
+ StopNode stop = parser.parse().iterate(true);
+ assertEquals("return Phi(Loop7,1,Phi(Region42,Phi_a,(Phi_a+1)));", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof ProjNode);
+ System.out.println(IRPrinter.prettyPrint(stop,99));
+ }
+
+
+ @Test
+ public void testEx4() {
+ Parser parser = new Parser(
+ """
+while(arg < 10) {
+ arg = arg + 1;
+ if (arg == 5)
+ continue;
+ if (arg == 6)
+ break;
+}
+return arg;
+ """);
+ StopNode stop = parser.parse().iterate(true);
+ assertEquals("return Phi(Region34,Phi(Loop6,arg,(Phi_arg+1)),Add);", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof RegionNode);
+ }
+
+ @Test
+ public void testEx3() {
+ Parser parser = new Parser(
+ """
+while(arg < 10) {
+ arg = arg + 1;
+ if (arg == 6)
+ break;
+}
+return arg;
+ """);
+ StopNode stop = parser.parse().iterate(true);
+ assertEquals("return Phi(Region25,Phi(Loop6,arg,(Phi_arg+1)),Add);", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof RegionNode);
+ }
+
+
+ @Test
+ public void testEx2() {
+ Parser parser = new Parser(
+ """
+while(arg < 10) {
+ arg = arg + 1;
+ if (arg == 5)
+ continue;
+ if (arg == 6)
+ continue;
+}
+return arg;
+ """);
+ StopNode stop = parser.parse().iterate(true);
+ assertEquals("return Phi(Loop6,arg,(Phi_arg+1));", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof ProjNode);
+ }
+
+
+ @Test
+ public void testEx1() {
+ Parser parser = new Parser(
+ """
+while(arg < 10) {
+ arg = arg + 1;
+ if (arg == 5)
+ continue;
+}
+return arg;
+ """);
+ StopNode stop = parser.parse().iterate(true);
+ assertEquals("return Phi(Loop6,arg,(Phi_arg+1));", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof ProjNode);
+ }
+
+ @Test
+ public void testRegress1() {
+ Parser parser = new Parser(
+ """
+while( arg < 10 ) {
+ int a = arg+2;
+ if( a > 4 )
+ break;
+}
+return arg;
+ """);
+ StopNode stop = parser.parse().iterate(true);
+ assertEquals("return arg;", stop.toString());
+ System.out.println(IRPrinter.prettyPrint(stop,99));
+ }
+
+ @Test
+ public void testRegress2() {
+ Parser parser = new Parser("if(1) return 0; else while(arg>--arg) arg=arg+1; return 0;");
+ StopNode stop = parser.parse().iterate(true);
+ assertEquals("return 0;", stop.toString());
+ }
+
+ @Test
+ public void testBreakOutsideLoop() {
+ try {
+ new Parser("""
+if(arg <= 10) {
+ break;
+ arg = arg + 1;
+}
+return arg;
+""").parse();
+ fail();
+ } catch( RuntimeException e ) {
+ assertEquals("No active loop for a break or continue",e.getMessage());
+ }
+ }
+
+ @Test
+ public void testRegress3() {
+ Parser parser = new Parser("""
+while(arg < 10) {
+ break;
+}
+return arg;
+""");
+ StopNode stop = parser.parse().iterate(true);
+ assertEquals("return arg;", stop.toString());
+ }
+
+ @Test
+ public void testRegress4() {
+ Parser parser = new Parser("""
+int a = 1;
+while(arg < 10) {
+ a = a + 1;
+ if (arg > 2) {
+ int a = 17;
+ break;
+ }
+}
+return a;
+""");
+ StopNode stop = parser.parse().iterate(true);
+ assertEquals("return Phi(Region28,Phi(Loop7,1,(Phi_a+1)),Add);", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof RegionNode);
+ }
+
+ @Test
+ public void testRegress5() {
+ Parser parser = new Parser("""
+int a = 1;
+while(1) {
+ a = a + 1;
+ if (a<10) continue;
+ break;
+}
+return a;
+""");
+ StopNode stop = parser.parse().iterate(true);
+ assertEquals("return (Phi(Loop7,1,Add)+1);", stop.toString());
+ assertTrue(stop.ret().ctrl() instanceof ProjNode prj && prj._idx==1 );
+ }
+
+}
diff --git a/chapter10/src/test/java/com/seaofnodes/simple/Chapter09Test.java b/chapter10/src/test/java/com/seaofnodes/simple/Chapter09Test.java
new file mode 100644
index 00000000..8e198218
--- /dev/null
+++ b/chapter10/src/test/java/com/seaofnodes/simple/Chapter09Test.java
@@ -0,0 +1,300 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.Ignore;
+
+import static org.junit.Assert.*;
+
+public class Chapter09Test {
+
+ @Test
+ @Ignore
+ public void testJig() {
+ Parser parser = new Parser("""
+int v0=0;
+arg=0;
+while(v0) {
+ while(1) if(arg*arg*0==0) {}
+ while(0) {}
+ arg=1;
+}
+return 0;
+ """);
+ StopNode stop = parser.parse().iterate(true);
+ }
+
+ @Test
+ public void testGVN1() {
+ Parser parser = new Parser(
+ """
+int x = arg + arg;
+if(arg < 10) {
+ return arg + arg;
+}
+else {
+ x = x + 1;
+}
+return x;
+ """);
+ StopNode stop = parser.parse(true);
+ assertEquals("Stop[ return (arg*2); return (Mul+1); ]", stop.toString());
+ Assert.assertEquals(2, GraphEvaluator.evaluate(stop, 1));
+ Assert.assertEquals(23, GraphEvaluator.evaluate(stop, 11));
+ }
+
+ @Test
+ public void testGVN2() {
+ Parser parser = new Parser(
+ """
+return arg*arg-arg*arg;
+ """);
+ StopNode stop = parser.parse(true);
+ assertEquals("return 0;", stop.toString());
+ Assert.assertEquals(0, GraphEvaluator.evaluate(stop, 1));
+ }
+
+ @Test
+ public void testWorklist1() {
+ Parser parser = new Parser(
+ """
+int step = 1;
+while (arg < 10) {
+ arg = arg + step + 1;
+}
+return arg;
+ """);
+ StopNode stop = parser.parse(true).iterate(true);
+ assertEquals("return Phi(Loop7,arg,(Phi_arg+2));", stop.toString());
+ Assert.assertEquals(11, GraphEvaluator.evaluate(stop, 1));
+ }
+
+ @Test
+ public void testWorklist2() {
+ Parser parser = new Parser(
+ """
+int cond = 0;
+int one = 1;
+while (arg < 10) {
+ if (cond) one = 2;
+ arg = arg + one*3 + 1;
+}
+return arg;
+ """);
+ StopNode stop = parser.parse(true).iterate(true);
+ assertEquals("return Phi(Loop8,arg,(Phi_arg+4));", stop.toString());
+ Assert.assertEquals(13, GraphEvaluator.evaluate(stop, 1));
+ }
+
+ @Test
+ public void testWorklist3() {
+ Parser parser = new Parser(
+ """
+int v1 = 0;
+int v2 = 0;
+int v3 = 0;
+int v4 = 0;
+int v5 = 0;
+int v6 = 0;
+int v7 = 0;
+int v8 = 0;
+while (arg) {
+ if (v1) v2 = 1;
+ if (v2) v3 = 1;
+ if (v3) v4 = 1;
+ if (v4) v5 = 1;
+ if (v5) v6 = 1;
+ if (v6) v7 = 1;
+ if (v7) v8 = 1;
+ arg = arg + v8 + 1;
+}
+return arg;
+ """);
+ StopNode stop = parser.parse().iterate(true);
+ assertEquals("return Phi(Loop14,arg,(Phi_arg+1));", stop.toString());
+ }
+
+ @Test
+ public void testRegionPeepBug() {
+ Parser parser = new Parser(
+ """
+int v0=0;
+int v1=0;
+while(v1+arg) {
+ arg=0;
+ int v2=v0;
+ while(arg+1) {}
+ v0=1;
+ v1=v2;
+}
+ """);
+ StopNode stop = parser.parse().iterate(true);
+ assertEquals("Stop[ ]", stop.toString());
+ }
+
+ @Test
+ public void testWhile0() {
+ Parser parser = new Parser("while(0) continue; if(0) arg=0;");
+ StopNode stop = parser.parse().iterate(true);
+ assertEquals("Stop[ ]", stop.toString());
+ }
+
+ @Test
+ public void testWhile1() {
+ Parser parser = new Parser("""
+if(0) while(0) {
+ int arg=arg;
+ while(0) {}
+}
+""");
+ StopNode stop = parser.parse().iterate(true);
+ assertEquals("Stop[ ]", stop.toString());
+ }
+
+
+ @Test
+ public void testPrecedence() {
+ Parser parser = new Parser("return 3-1+2;");
+ StopNode stop = parser.parse().iterate(true);
+ assertEquals("return 4;", stop.toString());
+ }
+
+ @Test
+ public void testSwap2() {
+ Parser parser = new Parser("return 1+(1+1);");
+ StopNode stop = parser.parse().iterate(true);
+ assertEquals("return 3;", stop.toString());
+ }
+
+
+ @Test
+ public void testFuzz0() {
+ Parser parser = new Parser("""
+int one = 1;
+int a = 0;
+int zero = 0;
+while(arg) {
+ a = -(one + a + 2);
+ arg = arg + 1;
+ one = one + zero;
+}
+return a;
+""");
+ StopNode stop = parser.parse().iterate(true);
+ assertEquals("return Phi(Loop9,0,(-(Phi_a+3)));", stop.toString());
+ }
+
+ @Test
+ public void testFuzz1() {
+ Parser parser = new Parser("""
+while(1) {}
+while(arg) break;
+while(arg) arg=0;
+arg=0;
+int v0=0!=0<-0;
+return -0+0+0;
+""");
+ StopNode stop = parser.parse().iterate(true);
+ assertEquals("Stop[ ]", stop.toString());
+ }
+
+
+ @Test
+ public void testFuzz2() {
+ Parser parser = new Parser("return 0+-0;");
+ StopNode stop = parser.parse().iterate(true);
+ assertEquals("return 0;", stop.toString());
+ }
+
+ @Test
+ public void testFuzz3() {
+ Parser parser = new Parser("int v0=0; while(0==69) while(v0) return 0;");
+ StopNode stop = parser.parse().iterate(true);
+ assertEquals("Stop[ ]", stop.toString());
+ }
+
+ @Test
+ public void testFuzz4() {
+ Parser parser = new Parser("""
+while(1) {
+ arg=0<=0;
+ if(1<0) while(arg==-0) arg=arg-arg;
+}
+""");
+ StopNode stop = parser.parse().iterate(true);
+ assertEquals("Stop[ ]", stop.toString());
+ }
+
+ @Test
+ public void testFuzz5() {
+ Parser parser = new Parser("""
+{
+ int v0=0;
+ while(1)
+ int v1=0--0;
+ while(v0)
+ break;
+ while(-v0) {
+ while(0+0+v0) continue;
+ break;
+ }
+ if(-0!=-0+0+v0) while(0+0+0+0)
+ break;
+}
+return 0!=0;
+""");
+ StopNode stop = parser.parse().iterate(true);
+ assertEquals("Stop[ ]", stop.toString());
+ }
+
+ @Test
+ public void testFuzz6() {
+ Parser parser = new Parser("""
+int v0=0;
+while(0==1) while(v0)
+ v0=1+v0;
+ """);
+ StopNode stop = parser.parse().iterate(true);
+ assertEquals("Stop[ ]", stop.toString());
+ }
+
+ @Test
+ public void testFuzz7() {
+ Parser parser = new Parser("""
+while(1) {}
+int v0=0;
+while(v0)
+ {}
+int v1=0;
+while(1)
+ v1=1;
+return v1+v0;
+ """);
+ StopNode stop = parser.parse().iterate(true);
+ assertEquals("Stop[ ]", stop.toString());
+ }
+
+ @Test
+ public void testFuzz8() {
+ Parser parser = new Parser("while(arg) arg = arg - 1; #showGraph; return arg;");
+ StopNode stop = parser.parse().iterate(true);
+ assertEquals("return Phi(Loop6,arg,(Phi_arg-1));", stop.toString());
+ }
+
+ @Test
+ public void testMeet() {
+ Type t1 = Type.TOP;
+ Type t2 = TypeInteger.TOP;
+ Assert.assertEquals(TypeInteger.TOP, t1.meet(t2));
+ Assert.assertEquals(TypeInteger.TOP, t2.meet(t1));
+ t1 = Type.BOTTOM;
+ t2 = TypeInteger.BOT;
+ Assert.assertEquals(Type.BOTTOM, t1.meet(t2));
+ Assert.assertEquals(Type.BOTTOM, t2.meet(t1));
+ }
+
+}
diff --git a/chapter10/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter10/src/test/java/com/seaofnodes/simple/Chapter10Test.java
new file mode 100644
index 00000000..6bafe72a
--- /dev/null
+++ b/chapter10/src/test/java/com/seaofnodes/simple/Chapter10Test.java
@@ -0,0 +1,363 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.node.StopNode;
+import com.seaofnodes.simple.type.*;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.Arrays;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+public class Chapter10Test {
+
+
+ @Test
+ public void testStruct() {
+ Parser parser = new Parser("""
+struct Bar {
+ int a;
+ int b;
+}
+struct Foo {
+ int x;
+}
+Foo? foo = null;
+Bar bar = new Bar;
+bar.a = 1;
+bar.a = 2;
+return bar.a;
+""");
+ StopNode stop = parser.parse(true).iterate(true);
+ System.out.println(IRPrinter.prettyPrint(stop, 99, true));
+ assertEquals("return .a;", stop.toString());
+ }
+
+ @Test
+ public void testExample() {
+ Parser parser = new Parser("""
+struct Vector2D { int x; int y; }
+Vector2D v = new Vector2D;
+v.x = 1;
+if (arg)
+ v.y = 2;
+else
+ v.y = 3;
+return v;
+""");
+ StopNode stop = parser.parse(true).iterate(true);
+ System.out.println(IRPrinter.prettyPrint(stop, 99, true));
+ assertEquals("return new Vector2D;", stop.toString());
+ }
+
+ @Test
+ public void testBug() {
+ Parser parser = new Parser("""
+struct s0 {
+ int v0;
+}
+s0? v1=null;
+int v3=v1.zAicm;
+""");
+ try { parser.parse(true); fail(); }
+ catch( Exception e ) { assertEquals("Accessing unknown field 'zAicm' from 'null'",e.getMessage()); }
+ }
+
+ @Test
+ public void testBug2() {
+ Parser parser = new Parser("""
+struct s0 { int v0; }
+arg=0+new s0.0;
+""");
+ try { parser.parse(true); fail(); }
+ catch( Exception e ) { assertEquals("Expected an identifier, found 'null'",e.getMessage()); }
+ }
+
+ @Test
+ public void testLoop() {
+ Parser parser = new Parser("""
+struct Bar { int a; }
+Bar bar = new Bar;
+while (arg) {
+ bar.a = bar.a + 2;
+ arg = arg + 1;
+}
+return bar.a;
+""");
+ StopNode stop = parser.parse(true).iterate(true);
+ assertEquals("return .a;", stop.toString());
+ }
+
+ @Test
+ public void testIf() {
+ Parser parser = new Parser("""
+struct Bar { int a; }
+Bar bar = new Bar;
+if (arg) bar = null;
+bar.a = 1;
+return bar.a;
+""");
+ try { parser.parse(true).iterate(true); fail(); }
+ catch( Exception e ) { assertEquals("Type null is not of declared type *Bar",e.getMessage()); }
+ }
+
+ @Test
+ public void testIf2() {
+ Parser parser = new Parser("""
+struct Bar { int a; }
+Bar? bar = null;
+if (arg) bar = new Bar;
+bar.a = 1;
+return bar.a;
+""");
+ try { parser.parse(true).iterate(true); fail(); }
+ catch( Exception e ) { assertEquals("Might be null accessing 'a'",e.getMessage()); }
+ }
+
+ @Test
+ public void testIf3() {
+ Parser parser = new Parser("""
+struct Bar { int a; }
+Bar bar = null;
+if (arg) bar = null;
+bar.a = 1;
+return bar.a;
+""");
+ try { parser.parse(true); fail(); }
+ catch( Exception e ) { assertEquals("Type null is not of declared type *Bar", e.getMessage()); }
+ }
+
+ @Test
+ public void testIfOrNull() {
+ Parser parser = new Parser("""
+struct Bar { int a; }
+Bar? bar = new Bar;
+if (arg) bar = null;
+if( bar ) bar.a = 1;
+return bar;
+""");
+ StopNode stop = parser.parse(false).iterate(true);
+ assertEquals("return Phi(Region15,null,new Bar);", stop.toString());
+ }
+
+ @Test
+ public void testIfOrNull2() {
+ Parser parser = new Parser(
+"""
+struct Bar { int a; }
+Bar? bar = new Bar;
+if (arg) bar = null;
+int rez = 3;
+if( !bar ) rez=4;
+else bar.a = 1;
+return rez;
+""");
+ StopNode stop = parser.parse(false).iterate(true);
+ assertEquals("return Phi(Region32,4,3);", stop.toString());
+ }
+
+ @Test
+ public void testWhileWithNullInside() {
+ Parser parser = new Parser("""
+struct s0 {int v0;}
+s0? v0 = new s0;
+int ret = 0;
+while(arg) {
+ ret = v0.v0;
+ v0 = null;
+ arg = arg - 1;
+}
+return ret;
+""");
+ try { parser.parse(true).iterate(true); fail(); }
+ catch( Exception e ) { assertEquals("Might be null accessing 'v0'", e.getMessage()); }
+ }
+
+ @Test
+ public void testRedeclareStruct() {
+ Parser parser = new Parser("""
+struct s0 {
+ int v0;
+}
+s0? v1=new s0;
+s0? v1;
+v1=new s0;
+""");
+ try { parser.parse(true); fail(); }
+ catch( Exception e ) { assertEquals("Redefining name 'v1'", e.getMessage()); }
+ }
+
+ @Test
+ public void test1() {
+ Parser parser = new Parser("""
+struct s0 {int v0;}
+s0 ret = new s0;
+while(arg) {
+ s0 v0 = new s0;
+ v0.v0 = arg;
+ arg = arg-1;
+ if (arg==5) ret=v0;
+ #showGraph;
+}
+return ret;
+""");
+ StopNode stop = parser.parse(true).iterate(true);
+ System.out.println(IRPrinter.prettyPrint(stop, 99, true));
+ assertEquals("return Phi(Loop10,new s0,Phi(Region31,new s0,Phi_ret));", stop.toString());
+ }
+
+ @Test
+ public void test2() {
+ Parser parser = new Parser("""
+struct s0 {int v0;}
+s0 ret = new s0;
+s0 v0 = new s0;
+while(arg) {
+ v0.v0 = arg;
+ arg = arg-1;
+ if (arg==5) ret=v0;
+ #showGraph;
+}
+return ret;
+""");
+ StopNode stop = parser.parse(true).iterate(true);
+ System.out.println(IRPrinter.prettyPrint(stop, 99, true));
+ assertEquals("return Phi(Loop13,new s0,Phi(Region32,new s0,Phi_ret));", stop.toString());
+ }
+
+
+ @Test
+ public void test3() {
+ Parser parser = new Parser("""
+struct s0 {int v0;}
+s0 ret = new s0;
+while(arg < 10) {
+ s0 v0 = new s0;
+ if (arg == 5) ret=v0;
+ arg = arg + 1;
+}
+return ret;
+""");
+ StopNode stop = parser.parse(true).iterate(true);
+ System.out.println(IRPrinter.prettyPrint(stop, 99, true));
+ assertEquals("return Phi(Loop10,new s0,Phi(Region30,new s0,Phi_ret));", stop.toString());
+ }
+
+ @Test
+ public void testBug3() {
+ Parser parser = new Parser("""
+struct s0 {
+ int f0;
+}
+if(0>=0) return new s0;
+return new s0;
+int v0=null.f0;
+""");
+ try { parser.parse(true); fail(); }
+ catch( Exception e ) { assertEquals("Accessing unknown field 'f0' from 'null'", e.getMessage()); }
+ }
+
+ @Test
+ public void testBug4() {
+ Parser parser = new Parser("""
+if(0) {
+ while(0) if(arg) continue;
+ int v0=0;
+ while(1) {
+ int arg=-arg;
+ v0=arg;
+ }
+}
+ """);
+ StopNode stop = parser.parse(true).iterate(true);
+ assertEquals("Stop[ ]", stop.toString());
+ }
+
+ @Test
+ public void testBug5() {
+ Parser parser = new Parser("""
+struct s0 {
+ int f0;
+}
+if(0) return 0;
+else return new s0;
+if(new s0.f0) return 0;
+ """);
+ StopNode stop = parser.parse(true).iterate(true);
+ assertEquals("return new s0;", stop.toString());
+ }
+
+ @Test
+ public void testBug6MissedWorklist() {
+ Parser parser = new Parser("""
+while(0) {}
+int v4=0;
+while(0<arg) {
+ v4=v4+1;
+ while(1) v4=-v4;
+ while(0) arg=-1;
+}
+return 0;
+ """);
+ StopNode stop = parser.parse().iterate(true);
+ }
+
+ @Test
+ public void testBug7() {
+ Parser parser = new Parser("""
+struct s0 { int f0; }
+s0 v0 = new s0;
+while(v0.f0) {}
+s0 v1 = v0;
+return v1;
+ """);
+ StopNode stop = parser.parse().iterate(true);
+ assertEquals("return new s0;", stop.toString());
+ }
+
+
+ @Test
+ public void testBug8() {
+ Parser parser = new Parser("""
+int v2=0;
+while(0)
+while(0) {}
+{
+ {
+ {
+ int v36=0;
+ {
+ while(0) {
+ {
+ while(-v2) {
+ {
+ while(v36) {
+ while(v2) return 0;
+ break;
+ } }
+ if(-v2) break;
+ }
+ }
+ }
+ }
+ } }
+}
+""");
+ StopNode stop = parser.parse().iterate(true);
+ assertEquals("Stop[ ]", stop.toString());
+ }
+
+ @Test
+ public void testBug9() {
+ Parser parser = new Parser("""
+int v0=arg==0;
+while(v0) continue;
+return 0;
+""");
+ StopNode stop = parser.parse().iterate(true);
+ assertEquals("return 0;", stop.toString());
+ }
+
+}
diff --git a/chapter10/src/test/java/com/seaofnodes/simple/FuzzerWrap.java b/chapter10/src/test/java/com/seaofnodes/simple/FuzzerWrap.java
new file mode 100644
index 00000000..699711fc
--- /dev/null
+++ b/chapter10/src/test/java/com/seaofnodes/simple/FuzzerWrap.java
@@ -0,0 +1,35 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.fuzzer.Fuzzer;
+import org.junit.Test;
+import org.junit.Ignore;
+
+
+import static org.junit.Assert.assertTrue;
+
+/**
+ * To use the fuzzer run the <code>fuzzPeeps</code> methods.
+ * This can be done from IntelliJ Gui or remove the `@Ignore` annotation and start the test case from the command line
+ * via <code>mvn clean test -Dtest=com.seaofnodes.simple.Fuzzer08Test#fuzzPeeps</code>
+ */
+public class FuzzerWrap {
+
+ @Ignore
+ @Test
+ public void fuzzPeeps() {
+ var fuzzer = new Fuzzer();
+ for (int i=0; i<1000000; i++)
+ fuzzer.fuzzPeeps(i);
+ assertTrue(fuzzer.noExceptions());
+ }
+
+
+ @Test
+ @Ignore
+ public void fuzzPeepTiming() {
+ var fuzzer = new Fuzzer();
+ int max_nid=0;
+ for (int i=0; i<1000000; i++)
+ max_nid = fuzzer.fuzzPeepTiming(i, max_nid);
+ }
+}
diff --git a/chapter10/src/test/java/com/seaofnodes/simple/TypeTest.java b/chapter10/src/test/java/com/seaofnodes/simple/TypeTest.java
new file mode 100644
index 00000000..f18fb306
--- /dev/null
+++ b/chapter10/src/test/java/com/seaofnodes/simple/TypeTest.java
@@ -0,0 +1,145 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.type.*;
+import org.junit.Assert;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+public class TypeTest {
+
+ // Test basic properties and GLB
+ @Test
+ public void testTypeAdHoc() {
+ TypeStruct s1 = TypeStruct.make("s1", new Field[]{
+ Field.make("s1", TypeInteger.BOT, "a"),
+ Field.make("s1", TypeInteger.BOT, "b") });
+ TypeStruct s2 = TypeStruct.make("s2", new Field[]{
+ Field.make("s2", TypeInteger.BOT, "a"),
+ Field.make("s2", TypeInteger.BOT, "b") });
+ Assert.assertEquals(s1, s1.glb());
+ Assert.assertNotEquals(s1, s1.dual());
+ Assert.assertEquals(s1, s1.dual().glb());
+
+ TypeMem m1 = TypeMem.make(s1.get("a")._alias);
+ TypeMem m2 = TypeMem.make(s1.get("b")._alias);
+ TypeMem m3 = TypeMem.make(s2.get("a")._alias);
+ TypeMem m4 = TypeMem.make(s2.get("b")._alias);
+
+ Assert.assertNotEquals(m1, m2);
+ Assert.assertNotEquals(m2, m3);
+ Assert.assertNotEquals(m3, m4);
+
+ Assert.assertEquals(TypeStruct.BOT, s1.meet(s2));
+ Assert.assertEquals(TypeMem .BOT, m1.meet(m2));
+ Assert.assertEquals(TypeMem .BOT, m2.meet(m3));
+ Assert.assertEquals(TypeMem .BOT, m3.meet(m4));
+
+ Assert.assertEquals(TypeMem.BOT, m1.glb());
+ Assert.assertEquals(m1, m1.dual());
+ Assert.assertEquals(TypeMem.TOP, m1.glb().dual());
+
+ TypeMemPtr ptr1 = TypeMemPtr.make(s1);
+ Assert.assertEquals(s1, ptr1._obj);
+ TypeMemPtr ptr2 = TypeMemPtr.make(s2);
+ Assert.assertEquals(s2, ptr2._obj);
+
+ TypeMemPtr ptr1nil = TypeMemPtr.make(s1, true);
+ Assert.assertEquals(s1, ptr1nil._obj);
+ Assert.assertTrue(ptr1nil._nil);
+ Assert.assertFalse(ptr1nil._obj==null);
+ TypeMemPtr ptr2nil = TypeMemPtr.make(s2, true);
+ Assert.assertEquals(s2, ptr2nil._obj);
+
+ Assert.assertNotEquals(ptr1, ptr2);
+ Assert.assertNotEquals(ptr1, ptr1.glb());
+ Assert.assertEquals(ptr1nil, ptr1.glb());
+
+ Assert.assertEquals(ptr1, ptr1.dual().dual());
+ Assert.assertEquals(ptr1.glb(), ptr1.dual().glb());
+ Assert.assertEquals(TypeMemPtr.BOT, ptr1.meet(ptr2nil));
+ Assert.assertEquals(ptr1.glb(), ptr1.meet(TypeMemPtr.NULLPTR));
+
+ TypeMemPtr TOP = TypeMemPtr.TOP;
+ TypeMemPtr BOT = TypeMemPtr.BOT;
+ TypeMemPtr PTR = TypeMemPtr.VOIDPTR;
+ TypeMemPtr NULL = TypeMemPtr.NULLPTR;
+ Type PTR_meet_NULL = NULL.meet(PTR);
+ Assert.assertEquals(BOT, PTR_meet_NULL);
+ Type ptr1_meet_ptr2 = ptr1.meet(ptr2);
+ Assert.assertEquals(PTR, ptr1_meet_ptr2);
+ Type NULL_join_ptr1 = NULL.join(ptr1);
+ Assert.assertEquals(TOP, NULL_join_ptr1);
+ Type NULL_join_PTR = PTR.join(NULL);
+ Assert.assertEquals(TOP, NULL_join_PTR);
+ Type ptr1_dual = ptr1.dual();
+ Type nullableptr1_dual = ptr1nil.dual();
+ }
+
+ // Test theoretical properties.
+ // This is a symmetric complete bounded (ranked) lattice.
+ // Also the meet is commutative and associative.
+ // The lattice has a dual (symmetric), and join is ~(~x meet ~y).
+ // See https://en.wikipedia.org/wiki/Lattice_(order).
+ @Test
+ public void testLatticeTheory() {
+ Type[] ts = Type.gather();
+
+ // Confirm commutative & complete
+ for( Type t0 : ts )
+ for( Type t1 : ts ) {
+ check_commute (t0,t1);
+ check_symmetric(t0,t1);
+ }
+
+ // Confirm associative
+ for( Type t0 : ts )
+ for( Type t1 : ts )
+ for( Type t2 : ts )
+ assoc(t0,t1,t2);
+
+ // Confirm symmetry. If A isa B, then A.join(C) isa B.join(C)
+ for( Type t0 : ts )
+ for( Type t1 : ts )
+ if( t0.isa(t1) )
+ for( Type t2 : ts ) {
+ Type t02 = t0.join(t2);
+ Type t12 = t1.join(t2);
+ Type mt = t02.meet(t12);
+ assertSame(mt,t12);
+ }
+ }
+
+ // By design in meet, args are already flipped to order _type, which forces
+ // symmetry for things with badly ordered _type fields. The question is
+ // still interesting for other orders.
+ private static void check_commute( Type t0, Type t1 ) {
+ if( t0==t1 ) return;
+ if( t0.is_simple() && !t1.is_simple() ) return; // By design, flipped the only allowed order
+ Type mta = t0.meet(t1);
+ Type mtb = t1.meet(t0); // Reverse args and try again
+ assertSame(mta,mtb);
+ }
+
+ // A & B = MT
+ // Expect: ~A & ~MT == ~A
+ // Expect: ~B & ~MT == ~B
+ private static void check_symmetric( Type t0, Type t1 ) {
+ if( t1==t0 ) return;
+ Type mt = t0.meet(t1);
+ Type ta = mt.dual().meet(t1.dual());
+ Type tb = mt.dual().meet(t0.dual());
+ assertSame(ta,t1.dual());
+ assertSame(tb,t0.dual());
+ }
+
+ private static void assoc( Type t0, Type t1, Type t2 ) {
+ Type t01 = t0 .meet(t1 );
+ Type t12 = t1 .meet(t2 );
+ Type t01_2 = t01.meet(t2 );
+ Type t0_12 = t0 .meet(t12);
+ assertSame(t01_2,t0_12);
+ }
+
+}
diff --git a/chapter10/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java b/chapter10/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java
new file mode 100644
index 00000000..56933248
--- /dev/null
+++ b/chapter10/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java
@@ -0,0 +1,173 @@
+package com.seaofnodes.simple.fuzzer;
+
+import com.seaofnodes.simple.GraphEvaluator;
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.node.StopNode;
+
+import java.util.ArrayList;
+import java.util.Random;
+import java.util.function.Consumer;
+
+/**
+ * Implementation of the fuzzer.
+ * Random testcases are generated using the ScriptGenerator.
+ * This will generate a script similar to how the parser parses it but instead of parsing it will
+ * randomly choose a variation the parser would parse and generate it.
+ * These scripts are the parsed by the parser and all exceptions are caught and filtered
+ * to only show one occurrence of one problem. This includes script which generate different
+ * results when the compiled graph with and without peeps is executed.
+ * To aid debugging scripts that cause errors are then reduced by applying rules and checking
+ * that the same issue persists.
+ */
+public class Fuzzer {
+
+ private static final int EVAL_TIMEOUT = 1000;
+
+ /**
+ * List of exceptions already encountered.
+ * This is used to filter new exceptions and discard them if they are already found.
+ */
+ private final ArrayList<Throwable> exceptions = new ArrayList<>();
+
+ /**
+ * Filter and record an exception in a script.
+ * @param e The exception caused by the script
+ * @param script The script causing the exception
+ * @param reproducer Reproducer which should be used to reproduce the test case.
+ */
+ private void recordException(Throwable e, String script, Consumer<String> reproducer, long seed) {
+ for (var ex : exceptions) {
+ if (FuzzerUtils.isExceptionFromSameCause(ex, e)) return;
+ }
+ exceptions.add(e);
+ System.out.println("========== Stack ==========");
+ e.printStackTrace(System.out);
+ System.out.println("========== Seed ==========");
+ System.out.println(seed);
+ System.out.println("========== Code ===========");
+ System.out.println(script);
+ System.out.println("========= Reduced =========");
+ System.out.println(Reducer.reduce(script, e, reproducer));
+ System.out.println("===========================");
+ System.out.flush();
+ }
+
+ /**
+ * Check that two graphs result in the same output when supplied with a value
+ * @param stop1 Stop node of the first graph
+ * @param stop2 Stop node of the second graph
+ * @param in The input value to both graphs to test for an equal output
+ */
+ private static void checkGraphs(StopNode stop1, StopNode stop2, long in) {
+ if (true) return;
+ var e1 = GraphEvaluator.evaluateWithResult(stop1, in, EVAL_TIMEOUT);
+ var e2 = GraphEvaluator.evaluateWithResult(stop2, in, EVAL_TIMEOUT);
+ if (e1.type() == GraphEvaluator.ResultType.TIMEOUT || e2.type() == GraphEvaluator.ResultType.TIMEOUT) return;
+ if (e1.type() != e2.type())
+ throw new RuntimeException("Different calculations types " + e1.type() + " vs " + e2.type());
+ if (e1.value() != e2.value())
+ throw new RuntimeException("Different calculations values " + e1.value() + " vs " + e2.value());
+ }
+
+ /**
+ * Run checks for script. Compile the script with peeps enabled and disabled.
+ * Check that exceptions raised in the parser by both methods are the same and only happens if the script may be invalid.
+ * If the script was successfully parsed check that both version behave the same.
+ * @param script The script to test
+ * @param valid If the script is definitely valid. If not some exceptions may be suppressed.
+ */
+ private static void runCheck(String script, boolean valid) {
+ StopNode stop1;
+ try {
+ stop1 = FuzzerUtils.parse(script, false);
+ } catch (RuntimeException e1) {
+ try {
+ FuzzerUtils.parse(script, true);
+ } catch (RuntimeException e2) {
+ if (FuzzerUtils.isExceptionFromSameCause(e1, e2)) {
+ if (!valid || e1.getClass() == RuntimeException.class) return;
+ } else {
+ e1.addSuppressed(e2);
+ }
+ }
+ throw e1;
+ }
+ var stop2 = FuzzerUtils.parse(script, true);
+ checkGraphs(stop1, stop2, 0);
+ checkGraphs(stop1, stop2, 1);
+ checkGraphs(stop1, stop2, 10);
+ }
+
+ /**
+ * Check that the script does not result in exceptions.
+ * @param script The script to test.
+ * @param valid If the script is valid or if it might contain syntax errors.
+ */
+ private void check(String script, boolean valid, long seed) {
+ try {
+ runCheck(script, valid);
+ } catch (Throwable e) {
+ recordException(e, script, s->runCheck(s, valid), seed);
+ }
+ }
+
+ /**
+ * Run one test with the given seed, and check for peephole counts.
+ * @param seed The seed to use for generating this test case
+ */
+ public int fuzzPeepTiming(long seed, int max_nids) {
+ var rand = new Random(seed);
+ var sb = new StringBuilder();
+ new ScriptGenerator(rand, sb, true).genProgram();
+ try {
+ var parser = new Parser(sb.toString());
+
+ var stop = parser.parse();
+ int parse_peeps= Node.ITER_CNT;
+ int parse_nops = Node.ITER_NOP_CNT;
+ double parse_nop_ratio = (double)parse_nops/parse_peeps;
+ int nids = Node.UID();
+ if( nids <= max_nids ) return max_nids;
+
+ double parse_peeps_per_node = (double)parse_peeps/nids;
+
+ stop.iterate();
+ int iter_peeps= Node.ITER_CNT;
+ int iter_nops = Node.ITER_NOP_CNT;
+ double iter_nop_ratio = (double)iter_nops/iter_peeps;
+ double iter_peeps_per_node = (double)iter_peeps/nids;
+
+ System.out.printf("%6d | Parsing: peeps: %5d, nops: %5d, ratio: %5.3f, last UID: %5d ps/node: %5.3f | Iter: peeps: %6d, nops: %6d, ratio: %5.3f, ps/node: %5.3f\n",
+ seed,
+ parse_peeps,parse_nops,parse_nop_ratio,nids,parse_peeps_per_node,
+ iter_peeps , iter_nops, iter_nop_ratio, iter_peeps_per_node);
+ return nids;
+ } catch (Throwable e) {
+ // ignore exceptions, let the normal fullPeeps catch and reduce,
+ // we're interested in valid program optimizations
+ }
+ return max_nids;
+ }
+
+
+ /**
+ * Run one test with the given seed.
+ * @param seed The seed to use for generating this test case
+ */
+ public void fuzzPeeps(long seed) {
+ var rand = new Random(seed);
+ var sb = new StringBuilder();
+ var valid = new ScriptGenerator(rand, sb, true).genProgram();
+ check(sb.toString(), valid, seed);
+ }
+
+
+ /**
+ * Check that no exceptions happened.
+ * @return true if no exception happened
+ */
+ public boolean noExceptions() {
+ return exceptions.isEmpty();
+ }
+}
diff --git a/chapter10/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java b/chapter10/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java
new file mode 100644
index 00000000..18ef069a
--- /dev/null
+++ b/chapter10/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java
@@ -0,0 +1,140 @@
+package com.seaofnodes.simple.fuzzer;
+
+import com.seaofnodes.simple.IterPeeps;
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.node.StopNode;
+
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Field;
+import java.util.BitSet;
+
+/**
+ * Some utilities for the fuzzer.
+ */
+class FuzzerUtils {
+
+ /**
+ * Get the val of a private field
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> T getFieldValue(Object obj, String name) throws NoSuchFieldException, IllegalAccessException {
+ return (T) (obj instanceof Class<?> clz ? getField(clz, name).get(null) : getField(obj.getClass(), name).get(obj));
+ }
+
+ /**
+ * Get access to a private field
+ */
+ public static Field getField(Class<?> clz, String name) throws NoSuchFieldException {
+ var field = clz.getDeclaredField(name);
+ try {
+ field.setAccessible(true);
+ } catch (Throwable e) {
+ // Intentionally left empty
+ }
+ return field;
+ }
+
+ /**
+ * Try to rethrow an exception or create a runtime exception and throw it.
+ */
+ public static RuntimeException rethrow(Throwable e) {
+ if (e instanceof RuntimeException re) return re;
+ if (e instanceof Error er) throw er;
+ return new RuntimeException(e);
+ }
+
+ /**
+ * Copy of Node.WVISIST
+ * Used to clear as exceptions might happen in the walk and left this bitset not cleared
+ */
+ private static final BitSet NodeWalkVisit;
+
+ /**
+ * Write access to Iterate.MID_ASSERT
+ * Exception could happen mid assert and left this at true. Used to reset to false prior to parsing.
+ */
+ private static final MethodHandle set_MID_ASSERT;
+
+ /**
+ * Some problems might trigger debug output. Suppress this output with a null print stream during fuzzing.
+ */
+ public static final PrintStream NULL_PRINT_STREAM = new PrintStream(OutputStream.nullOutputStream());
+
+ static {
+ try {
+ NodeWalkVisit = getFieldValue(Node.class, "WVISIT");
+ set_MID_ASSERT = MethodHandles.lookup().unreflectSetter(getField( IterPeeps.class, "MID_ASSERT"));
+ } catch (Exception e) {
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+
+ /**
+ * Check that the messages are similar.
+ */
+ private static boolean similarMessages(String a, String b) {
+ if (a == null || b == null) return true;
+ a = a.replaceAll("'\\w+'", "'normalized'").replaceAll("variable \\w+", "variable normalized");
+ b = b.replaceAll("'\\w+'", "'normalized'").replaceAll("variable \\w+", "variable normalized");
+ return a.equals(b);
+ }
+
+ /**
+ * Check if both stack traces have the same first entry. Empty stack traces can happen with NPE optimization
+ * and allow the other to be any stack trace.
+ */
+ private static boolean sameStackTraceOrigin(StackTraceElement[] a, StackTraceElement[] b) {
+ if (a == null || a.length == 0 || b == null || b.length == 0) return true; // One or both stack traces are missing.
+ return a[0].equals(b[0]);
+ }
+
+ /**
+ * Checks if two exceptions are caused by the same problem.
+ */
+ public static boolean isExceptionFromSameCause(Throwable a, Throwable b) {
+ if (a == null || b == null) return a == b; // Handle the null case
+ if (a.getClass() != b.getClass()) return false; // Different classes
+ if (!similarMessages(a.getMessage(), b.getMessage())) return false; // Different messages. Null is allowed as wildcard
+ var s1 = a.getStackTrace();
+ var s2 = b.getStackTrace();
+ if (!sameStackTraceOrigin(s1, s2)) return false; // Different stack trace origins
+ if (!isExceptionFromSameCause(a.getCause(), b.getCause())) return false; // Different causes
+ var o1 = a.getSuppressed();
+ var o2 = b.getSuppressed();
+ if (o1 == null || o2 == null || o1.length != o2.length) return o1 == o2; // Different suppression
+ for (int i=0; i<o1.length; i++) {
+ if (!isExceptionFromSameCause(o1[i], o2[i])) return false; // Different suppression
+ }
+ return true;
+ }
+
+ /**
+ * Parse script with peepholes enabled or disabled
+ */
+ public static StopNode parse(String script, boolean runPeeps) {
+ var err = System.err;
+ var out = System.out;
+ try {
+ System.setErr(NULL_PRINT_STREAM);
+ System.setOut(NULL_PRINT_STREAM);
+ try {
+ set_MID_ASSERT.invokeExact(false);
+ } catch (Throwable e) {
+ throw rethrow(e);
+ }
+ var parser = new Parser(script);
+ Node._disablePeephole = !runPeeps;
+ var stop = parser.parse();
+ return runPeeps ? stop.iterate() : stop;
+ } finally {
+ NodeWalkVisit.clear();
+ System.setErr(err);
+ System.setOut(out);
+ }
+ }
+
+}
diff --git a/chapter10/src/test/java/com/seaofnodes/simple/fuzzer/Reducer.java b/chapter10/src/test/java/com/seaofnodes/simple/fuzzer/Reducer.java
new file mode 100644
index 00000000..3731b40c
--- /dev/null
+++ b/chapter10/src/test/java/com/seaofnodes/simple/fuzzer/Reducer.java
@@ -0,0 +1,176 @@
+package com.seaofnodes.simple.fuzzer;
+
+import java.util.Arrays;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * This class is used to reduce test cases created by the fuzzer.
+ * It applies rewrite rules to the test case and checks that the
+ * test still produces the same exception.
+ * This is done in a fixpoint iteration until every rewrite produces
+ * no or a different exception.
+ */
+public class Reducer {
+
+ private interface Replacer extends BiConsumer<StringBuilder, Matcher> {}
+
+ /**
+ * Holder for the rewrite rules
+ */
+ private record Replace(Pattern pattern, boolean iterate, Replacer... replacements) {
+ Replace(Pattern p, Object... replacements) {
+ this(p, replacements[0] == Boolean.TRUE, Arrays.stream(replacements).filter(o->!(o instanceof Boolean)).map(Replace::from).toArray(Replacer[]::new));
+ }
+ private static Replacer from(Object o) {
+ if (o instanceof Replacer r) return r;
+ if (o instanceof Integer i) return (s, m)->s.append(m.group(i));
+ if (o instanceof String r) return (s, m)->s.append(r);
+ throw new IllegalArgumentException();
+ }
+ }
+
+ /**
+ * List of rewrite rules.
+ */
+ private static final Replace[] REPLACEMENTS = {
+ new Replace(Pattern.compile("\\b(?:[^\\W0]|\\w\\w+)\\b"), "0", "1"),
+ new Replace(Pattern.compile("(?<=\\n|^)[^\\n]+(?=\\n|$)"), true, ""),
+ new Replace(Pattern.compile("(?<=[+\\-*/<>=(])--"), ""),
+ new Replace(Pattern.compile("\\b--"), "+"),
+ new Replace(Pattern.compile("-"), ""),
+ new Replace(Pattern.compile("\\b\\+-"), "-"),
+ new Replace(Pattern.compile("(?<![+\\-*/<>=])(?:-|\\*|/|>=|<=|>|<|!=|==)(?![+\\-*/<>=])"), "+"),
+ new Replace(Pattern.compile("\\{((?:[^{}]|\\{})+)(?:\\n[ \\t]*)?}"), "", "{}", 1),
+ new Replace(Pattern.compile("(?<=\\n|^)([ \\t]*)\\{\\n(.*?)\\n\\1}", Pattern.DOTALL), 2),
+ new Replace(Pattern.compile("\\(([^()]+)\\)"), 1),
+ new Replace(Pattern.compile("\\d+\\+"), ""),
+ new Replace(Pattern.compile("\\+\\d+"), ""),
+ new Replace(Pattern.compile("\\n\\n+"), "\n"),
+ new Replace(Pattern.compile("^\\n+|\\n+$"), ""),
+ new Replace(Pattern.compile("else *"), ""),
+ new Replace(Pattern.compile("\\bif\\([^()]+\\) *"), ""),
+ new Replace(Pattern.compile("\\bwhile\\([^()]+\\) *"), ""),
+ new Replace(Pattern.compile("\\bstruct \\w+ \\{[^{}]*}"), "")
+ };
+
+ /**
+ * Predicate to test if the reduced script generates the same error.
+ */
+ private final Predicate<String> tester;
+
+ /**
+ * Create a reducer with a test function.
+ * @param tester Test function which returns true when the same issue is found in the script.
+ */
+ private Reducer(Predicate<String> tester) {
+ this.tester = tester;
+ }
+
+ /**
+ * Rewrites the script with one rewrite rule.
+ */
+ private String doReplacement(String script, Pattern pattern, Replacer[] replacements) {
+ var matcher = pattern.matcher(script);
+ var sb = new StringBuilder();
+ var last = 0;
+ var skip = 0;
+ outer:
+ while (matcher.find(last + skip)) {
+ sb.append(script, last, matcher.start());
+ last = matcher.end();
+ skip = last == matcher.start() ? 1 : 0;
+ var pos = sb.length();
+ for (var replacement : replacements) {
+ replacement.accept(sb, matcher);
+ var tail = sb.length();
+ sb.append(script, last, script.length());
+ if (tester.test(sb.toString())) {
+ sb.setLength(tail);
+ continue outer;
+ }
+ sb.setLength(pos);
+ }
+ last = matcher.start();
+ skip = 1;
+ }
+ sb.append(script, last, script.length());
+ return sb.toString();
+ }
+
+ /**
+ * Clean up variable names from the random ones generated by the fuzzer.
+ */
+ private String cleanVariables(String script) {
+ var matcher = Pattern.compile("\\b([0-9a-zA-Z_]+) ([0-9a-zA-Z_]+)").matcher(script);
+ int[] num = {0, 0};
+ while (matcher.find()) {
+ var type = matcher.group(1);
+ var var = matcher.group(2);
+ if (type.equals("new")) continue;
+ String prefix = "v";
+ int t = 0;
+ if (type.equals("struct")) {
+ t = 1;
+ prefix = "s";
+ }
+ if (var.matches(prefix+"\\d+")) continue;
+ while (script.contains(prefix+num[t])) num[t]++;
+ var n = script.replaceAll("\\b"+var+"\\b", prefix + num[t]);
+ if (tester.test(n)) {
+ script = n;
+ }
+ }
+ return script;
+ }
+
+ /**
+ * Run all rewrite rules once.
+ */
+ private String doAllReplacements(String script) {
+ for (var replace: REPLACEMENTS) {
+ String old;
+ do {
+ old = script;
+ script = doReplacement(script, replace.pattern, replace.replacements);
+ } while (replace.iterate && !old.equals(script));
+ }
+ return script;
+ }
+
+ /**
+ * Reduce the script to a minimal version which produces the same problem with a test function.
+ * @param tester Test function which returns true when the same issue is found in the script.
+ */
+ public static String reduce(String script, Predicate<String> tester) {
+ var reducer = new Reducer(tester);
+ String old;
+ do {
+ old = script;
+ script = reducer.doAllReplacements(script);
+ script = reducer.cleanVariables(script);
+ } while (!script.equals(old));
+ return script;
+ }
+
+ /**
+ * Reduce the script to a minimal version which produces the same problem with a reproducer.
+ * @param ex The exception originally caused by the script
+ * @param reproducer Test function which throws an exception which will be checked against ex.
+ */
+ public static String reduce(String script, Throwable ex, Consumer<String> reproducer) {
+ return reduce(script, s->{
+ Throwable nex = null;
+ try {
+ reproducer.accept(s);
+ } catch (Throwable e) {
+ nex = e;
+ }
+ return FuzzerUtils.isExceptionFromSameCause(nex, ex);
+ });
+ }
+
+}
diff --git a/chapter10/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java b/chapter10/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java
new file mode 100644
index 00000000..155063ab
--- /dev/null
+++ b/chapter10/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java
@@ -0,0 +1,792 @@
+package com.seaofnodes.simple.fuzzer;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.node.ScopeNode;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Random;
+import java.util.function.Predicate;
+
+/**
+ * Generate a pseudo random script.
+ * This generator will generate the same script for two random number generators initialized with the same seed.
+ * To generate valid code it implements the parser but instead of parsing it emits the statements and expressions.
+ * It is guaranteed to terminate.
+ */
+public class ScriptGenerator {
+
+ private static final int MAX_STATEMENTS_PER_BLOCK = 5;
+ private static final int MAX_BINARY_EXPRESSIONS_PER_EXPRESSION = 10;
+ private static final int MAX_UNARY_EXPRESSIONS_PER_EXPRESSION = 6;
+ private static final int MAX_NAME_LENGTH = 10;
+ private static final int MAX_BLOCK_DEPTH = 4;
+ private static final int MAX_EXPRESSION_DEPTH = 10;
+
+ /**
+ * Number of spaces per indentation
+ */
+ private static final int INDENTATION = 4;
+ /**
+ * Valid characters for identifiers. The last 10 are not valid for the first character.
+ */
+ private static final String VAR_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789";
+ /**
+ * List of keywords not valid for identifiers.
+ */
+ private static final HashSet<String> KEYWORDS;
+
+ static {
+ try {
+ KEYWORDS = FuzzerUtils.getFieldValue(new Parser(""), "KEYWORDS");
+ } catch (Throwable e) {
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+
+ /**
+ * Flag for a statement that it does not pass on control flow to the next statement.
+ */
+ private static final int FLAG_STOP = 0x01;
+ /**
+ * Flag for a statement that it contains a final if statement without an else branch.
+ */
+ private static final int FLAG_IF_WITHOUT_ELSE = 0x02;
+ /**
+ * Binary operators.
+ */
+ private static final String[] BINARY_OP = {"==", "!=", "<", "<=", ">", ">=", "+", "-", "*", "/"};
+ /**
+ * Unary operators.
+ */
+ private static final String[] UNARY_OP = {"-"};
+
+ private static class Type {
+ final String name;
+ Type(String name) {this.name=name;}
+ boolean isa(Type other) { return this == other; }
+ }
+
+ private static class TypeStruct extends Type {
+ record Field (String name, Type type) {}
+ final Field[] fields;
+ final TypeNullable nullable = new TypeNullable(this);
+ TypeStruct(String name, Field[] fields) {
+ super(name);
+ this.fields = fields;
+ }
+ boolean isa(Type other) { return this == other || other == nullable; }
+ }
+
+ private static class TypeNullable extends Type {
+
+ final Type base;
+
+ TypeNullable(Type base) {
+ super(base.name+"?");
+ this.base = base;
+ }
+
+ }
+
+ private static final Type TYPE_INT = new Type("int");
+
+
+ private static class Variable {
+ final String name;
+ final Type declared;
+ final Variable shadowing;
+ Type type;
+ boolean shadowed = false;
+
+ Variable(String name, Type type, Variable var) {
+ this.name = name;
+ this.declared = type;
+ this.type = type;
+ this.shadowing = var;
+ }
+ }
+
+ /**
+ * The random number generator used for random decisions.
+ */
+ private final Random random;
+ /**
+ * Indentation depth.
+ */
+ private int indentation = 0;
+ /**
+ * Output string builder
+ */
+ private final StringBuilder sb;
+ /**
+ * Number of nested loops
+ */
+ private int loopDepth = 0;
+ /**
+ * Current scope start in the variables list.
+ */
+ private int currScopeStart = 0;
+ /**
+ * The depth of blocks allowed.
+ */
+ private int depth = MAX_BLOCK_DEPTH;
+ /**
+ * The depth of expressions allowed.
+ */
+ private int exprDepth = MAX_EXPRESSION_DEPTH;
+ /**
+ * Current variables in scope.
+ */
+ private final ArrayList<Variable> variables = new ArrayList<>();
+ /**
+ * All defined structs.
+ */
+ private final ArrayList<TypeStruct> structs = new ArrayList<>();
+ /**
+ * Allow to declare structs.
+ */
+ private boolean allowStructs = true;
+ /**
+ * If this needs to generate valid scripts or is also allowed to generate invalid ones.
+ */
+ private boolean generateValid;
+ /**
+ * If this script might be invalid.
+ */
+ private boolean maybeInvalid = false;
+
+
+ /**
+ *
+ * @param random the random number generator.
+ * @param sb the script will be written to this string builder.
+ * @param generateValid if the script should be guaranteed valid or if it is allowed to contain bugs.
+ */
+ public ScriptGenerator(Random random, StringBuilder sb, boolean generateValid) {
+ this.random = random;
+ this.sb = sb;
+ this.generateValid = generateValid;
+ }
+
+ /**
+ * Random number with log distribution
+ * @param max The maximum value
+ * @return A random number in the range [0, max) with higher numbers being more uncommon.
+ */
+ private int randLog(int max) {
+ max--;
+ int n = 1<<max;
+ var num = random.nextInt(n);
+ for (int i=max-1; i>=0; i--) {
+ if ((num & (1<<i))!=0) return max-i-1;
+ }
+ return max;
+ }
+
+ /**
+ * Helper function to print indentation.
+ * @return sb for chaining
+ */
+ private StringBuilder printIndentation() {
+ return sb.repeat(' ', indentation);
+ }
+
+ /**
+ * Generate a random name for variables. This might be invalid when it is a keyword.
+ * @return The random name generated
+ */
+ private StringBuilder getRandomName() {
+ int len = random.nextInt(MAX_NAME_LENGTH) + 1;
+ StringBuilder sb = new StringBuilder(len);
+ sb.append(VAR_CHARS.charAt(random.nextInt(VAR_CHARS.length()-10)));
+ for (int i=1; i<len; i++)
+ sb.append(VAR_CHARS.charAt(random.nextInt(VAR_CHARS.length())));
+ return sb;
+ }
+
+ /**
+ * Should invalid code be generated?
+ * @return True if invalid code should be generated
+ */
+ private boolean generateInvalid() {
+ if (generateValid || random.nextInt(1000)>2) return false;
+ maybeInvalid = true;
+ return true;
+ }
+
+ /**
+ * Lookup a variable by name
+ * @param name name of the variable
+ * @return The index of the variable in variables or -1
+ */
+ private int lookup(String name) {
+ for (int i=variables.size()-1; i>=0; i--) {
+ if (variables.get(i).name.equals(name)) return i;
+ }
+ return -1;
+ }
+
+ /**
+ * Lookup a variable by name
+ * @param name name of the variable
+ * @return THe variable of null if not found
+ */
+ private Variable lookupVar(String name) {
+ int idx = lookup(name);
+ return idx == -1 ? null : variables.get(idx);
+ }
+
+ /**
+ * Add a variable to the current scope
+ * @param name The name of the variable
+ * @param type The declared type of the variable
+ */
+ private void addVariable(String name, Type type) {
+ var old = lookupVar(name);
+ variables.add(new Variable(name, type, old));
+ if (old != null) old.shadowed = true;
+ }
+
+ /**
+ * Pop variables to level to
+ * @param to Level until which variables should be popped
+ */
+ private void popVariables(int to) {
+ while (variables.size() > to) {
+ var v = variables.removeLast();
+ if (v.shadowing != null) v.shadowing.shadowed = false;
+ }
+ }
+
+ /**
+ * Get a random visible variable patching pred
+ * @param pred The predicate the variable needs to match
+ * @return A random visible variable matching pred or null if no variable mached pred.
+ */
+ private Variable findVisibleVariablesMatching(Predicate<Variable> pred) {
+ int num=0;
+ for (var v:variables) {
+ if (!v.shadowed && pred.test(v)) num++;
+ }
+ if (num == 0) return null;
+ num = random.nextInt(num);
+ for (var v:variables) {
+ if (!v.shadowed && pred.test(v) && num-- == 0) return v;
+ }
+ throw new AssertionError();
+ }
+
+ /**
+ * Get a structure by name
+ * @param name Name of the structure
+ * @return The structure of null
+ */
+ private TypeStruct getStruct(String name) {
+ for (var s:structs) {
+ if (s.name.equals(name)) return s;
+ }
+ return null;
+ }
+
+ /**
+ * Generate a variable name. This might be new random name or a name from an outer scope.
+ * If the program is allowed to be invalid this can return keywords and already used variable names in the current scope.
+ * @return The variable name.
+ */
+ private String getVarName() {
+ if (currScopeStart > 0 && random.nextInt(10) > 7) {
+ // Use a variable outside the current scope.
+ var v = variables.get(random.nextInt(currScopeStart));
+ if (lookup(v.name) < currScopeStart) return v.name;
+ if (!generateValid) {
+ maybeInvalid = true;
+ return v.name;
+ }
+ }
+ // Generate a new random variable name
+ StringBuilder sb = getRandomName();
+ var v = sb.toString();
+ if (!generateValid && (KEYWORDS.contains(v) || lookup(v) >= currScopeStart)) {
+ maybeInvalid = true;
+ return v;
+ }
+ while (KEYWORDS.contains(v) || lookup(v) >= currScopeStart) {
+ sb.append(VAR_CHARS.charAt(random.nextInt(VAR_CHARS.length())));
+ v = sb.toString();
+ }
+ return v;
+ }
+
+ /**
+ * Get a random structure name
+ * @return A random structure name
+ */
+ private String getStructName() {
+ if (!structs.isEmpty() && generateInvalid()) return structs.get(random.nextInt(structs.size())).name;
+ // Generate a new random struct name
+ StringBuilder sb = getRandomName();
+ var v = sb.toString();
+ if (!generateValid && (KEYWORDS.contains(v) || getStruct(v) != null)) {
+ maybeInvalid = true;
+ return v;
+ }
+ while (KEYWORDS.contains(v) || getStruct(v) != null) {
+ sb.append(VAR_CHARS.charAt(random.nextInt(VAR_CHARS.length())));
+ v = sb.toString();
+ }
+ return v;
+ }
+
+ /**
+ * Get a random type
+ * @return A random type
+ */
+ private Type getType() {
+ if (structs.isEmpty() || random.nextBoolean()) return TYPE_INT;
+ var idx = random.nextInt(structs.size());
+ TypeStruct struct = structs.get(idx);
+ if (random.nextBoolean()) return struct.nullable;
+ return struct;
+ }
+
+ /**
+ * Generates a program and writes it into the string builder supplied in the constructor.
+ * @return If the program is allowed to be invalid returns if something potentially invalid was generated.
+ */
+ public boolean genProgram() {
+ var oldGenerateValid = generateValid;
+ if (!generateValid && random.nextBoolean()) generateValid = true;
+ this.maybeInvalid = false;
+ addVariable(ScopeNode.ARG0, TYPE_INT);
+ currScopeStart = variables.size();
+ if ((genStatements() & FLAG_STOP) == 0) {
+ if (generateValid || random.nextInt(10)<7) {
+ genReturn();
+ } else {
+ this.maybeInvalid = true;
+ }
+ }
+ generateValid = oldGenerateValid;
+ return !this.maybeInvalid;
+ }
+
+ /**
+ * Generate a list of statements.
+ * @return flags FLAG_STOP and FLAG_IF_WITHOUT_ELSE for the last statement generated
+ */
+ public int genStatements() {
+ var num = random.nextInt(MAX_STATEMENTS_PER_BLOCK * (depth+1));
+ for (int i=0; i<num; i++) {
+ printIndentation();
+ var stop = genStatement();
+ sb.append("\n");
+ if ((stop & FLAG_STOP) != 0) return stop;
+ }
+ return 0;
+ }
+
+ /**
+ * Generate a single statement
+ * @return flags FLAG_STOP and FLAG_IF_WITHOUT_ELSE for the generated statement
+ */
+ public int genStatement() {
+ return switch (random.nextInt(12)) {
+ case 0 -> allowStructs || generateInvalid() ? genStruct() : genAssignment();
+ case 1 -> genBlock();
+ case 2 -> genIf();
+ case 3 -> genCountedLoop();
+ case 4 -> genWhile();
+ case 5, 6 -> genDecl();
+ case 7, 8, 9 -> genAssignment();
+ case 10 -> genNullCheck();
+ default -> genExit();
+ };
+ }
+
+ /**
+ * Generate a new structure
+ * @return 0
+ */
+ public int genStruct() {
+ var name = getStructName();
+ var fields = new TypeStruct.Field[generateInvalid() ? 0 : random.nextInt(10)];
+ sb.append("struct ").append(name).append(" {\n");
+ indentation += INDENTATION;
+ for (int i=0; i<fields.length; i++) {
+ var fieldName = getRandomName();
+ var type = generateInvalid() ? getType() : TYPE_INT;
+ printIndentation().append(type.name).append(" ").append(fieldName).append(";\n");
+ fields[i] = new TypeStruct.Field(fieldName.toString(), type);
+ }
+ indentation -= INDENTATION;
+ printIndentation().append("}");
+ structs.add(new TypeStruct(name, fields));
+ return 0;
+ }
+
+ /**
+ * Generate a statement for if, else and while blocks.
+ * This is special since it disallows declarations and prefers to generate blocks.
+ * @return flags FLAG_STOP and FLAG_IF_WITHOUT_ELSE for the generated statement
+ */
+ public int genStatementBlock() {
+ var oldAllowStructs = allowStructs;
+ allowStructs = false;
+ indentation += INDENTATION;
+ int res;
+ if (depth == 0) {
+ res = random.nextBoolean() ? genAssignment() : genExit();
+ } else {
+ depth--;
+ res = switch (random.nextInt(12)) {
+ case 1, 2, 3, 4, 5 -> {
+ depth++;
+ indentation -= INDENTATION;
+ var ret = genBlock();
+ indentation += INDENTATION;
+ depth--;
+ yield ret;
+ }
+ case 6 -> genIf();
+ case 7 -> genCountedLoop();
+ case 8 -> genWhile();
+ case 9 -> genAssignment();
+ case 10 -> genNullCheck();
+ default -> genExit();
+ };
+ depth++;
+ }
+ indentation -= INDENTATION;
+ allowStructs = oldAllowStructs;
+ return res;
+ }
+
+ /**
+ * Generate an exit. This can be return or break and continue when in a loop.
+ * @return FLAG_STOP
+ */
+ public int genExit() {
+ if (loopDepth == 0 && generateValid) return genReturn();
+ return switch (random.nextInt(7)) {
+ case 0,1,2 -> {
+ if (loopDepth == 0) maybeInvalid = true;
+ sb.append("continue;");
+ yield FLAG_STOP;
+ }
+ case 3,4,5 -> {
+ if (loopDepth == 0) maybeInvalid = true;
+ sb.append("break;");
+ yield FLAG_STOP;
+ }
+ default -> genReturn();
+ };
+ }
+
+ /**
+ * Generate a block statement. After a certain depth only empty blocks are generated to ensure termination of the generator.
+ * @return flag FLAG_STOP for the generated statement
+ */
+ public int genBlock() {
+ if (depth == 0) {
+ sb.append("{}");
+ return 0;
+ }
+ var oldAllowStructs = allowStructs;
+ var oldCSS = currScopeStart;
+ allowStructs = false;
+ currScopeStart = variables.size();
+ depth--;
+ sb.append("{\n");
+ indentation += INDENTATION;
+ var stop = genStatements();
+ indentation -= INDENTATION;
+ printIndentation().append("}");
+ popVariables(currScopeStart);
+ depth++;
+ currScopeStart = oldCSS;
+ allowStructs = oldAllowStructs;
+ return stop & ~FLAG_IF_WITHOUT_ELSE;
+ }
+
+ /**
+ * Generate an if statement with an optional else block.
+ * @return flags FLAG_STOP and FLAG_IF_WITHOUT_ELSE for the generated statement
+ */
+ public int genIf() {
+ sb.append("if(");
+ genExpression(TYPE_INT);
+ sb.append(") ");
+ var stop = genStatementBlock();
+ if ((stop & FLAG_IF_WITHOUT_ELSE) == 0 && random.nextInt(10) > 3) {
+ sb.append("\n");
+ printIndentation().append("else ");
+ stop &= genStatementBlock();
+ } else {
+ stop = FLAG_IF_WITHOUT_ELSE;
+ }
+ return stop;
+ }
+
+ /**
+ * Generate a block with a null check
+ * if (nullable) { handle_nullable_as_non_nullable; }
+ */
+ public int genNullCheck() {
+ var v = findVisibleVariablesMatching(va->va.declared instanceof TypeNullable);
+ if (v == null) return genIf();
+ var n = (TypeNullable)v.declared;
+ boolean negate = random.nextBoolean();
+ sb.append("if(");
+ if (negate) sb.append("!");
+ sb.append(v.name).append(")");
+ if (!negate) v.type = n.base;
+ var stop = genStatementBlock();
+ v.type = n;
+ if ((stop & FLAG_IF_WITHOUT_ELSE) == 0 && random.nextInt(10) > 3) {
+ sb.append("\n");
+ printIndentation().append("else ");
+ if (negate) v.type = n.base;
+ stop &= genStatementBlock();
+ v.type = n;
+ } else {
+ stop = FLAG_IF_WITHOUT_ELSE;
+ }
+ return stop;
+ }
+
+ /**
+ * Generate a while loop.
+ * @return 0
+ */
+ public int genWhile() {
+ sb.append("while(");
+ genExpression(TYPE_INT);
+ sb.append(") ");
+ loopDepth++;
+ genStatementBlock();
+ loopDepth--;
+ return 0;
+ }
+
+ /**
+ * Generate a counted loop of the form
+ * <code>
+ * {
+ * int varname = start_expr;
+ * while (varname < end_expr) loop_body;
+ * }
+ * </code>
+ * @return 0
+ */
+ public int genCountedLoop() {
+ if (depth == 0) return genWhile();
+ var oldCSS = currScopeStart;
+ currScopeStart = variables.size();
+ sb.append("{\n");
+ indentation += INDENTATION;
+ String name = getVarName();
+ printIndentation().append("int ").append(name).append("=");
+ genExpression(TYPE_INT);
+ sb.append(";\n");
+ addVariable(name, TYPE_INT);
+ printIndentation().append("while(").append(name).append("<");
+ genExpression(TYPE_INT);
+ sb.append(") {\n");
+ indentation += INDENTATION;
+ depth--;
+ loopDepth++;
+ printIndentation().append(name).append("=").append(name).append("+");
+ genExpression(TYPE_INT);
+ sb.append(";\n");
+ genStatements();
+ loopDepth--;
+ depth++;
+ indentation -= INDENTATION;
+ printIndentation().append("}\n");
+ indentation -= INDENTATION;
+ printIndentation().append("}");
+ popVariables(currScopeStart);
+ currScopeStart = oldCSS;
+ return 0;
+ }
+
+ /**
+ * Generate a declaration statement.
+ * @return 0
+ */
+ public int genDecl() {
+ var type = getType();
+ var name = getVarName();
+ sb.append(generateInvalid() ? getRandomName() : type.name).append(" ").append(name);
+ if (!(type instanceof TypeNullable) || random.nextBoolean()) {
+ sb.append("=");
+ genExpression(type);
+ }
+ sb.append(";");
+ addVariable(name, type);
+ return 0;
+ }
+
+ /**
+ * Generate an assignment statement.
+ * @return 0
+ */
+ public int genAssignment() {
+ Type type;
+ Type declared;
+ if (generateInvalid()) {
+ sb.append(getRandomName());
+ type = getType();
+ declared = type;
+ } else {
+ var v = findVisibleVariablesMatching(va->true);
+ if (v == null) return genDecl();
+ sb.append(v.name);
+ type = v.type;
+ declared = v.declared;
+ }
+ if (generateInvalid()) {
+ var name = getRandomName();
+ sb.append(".").append(name);
+ declared = getType();
+ } else if (type instanceof TypeStruct s && s.fields.length > 0 && random.nextBoolean()) {
+ var field = s.fields[random.nextInt(s.fields.length)];
+ sb.append(".").append(field.name);
+ declared = field.type;
+ }
+ sb.append("=");
+ genExpression(declared);
+ sb.append(";");
+ return 0;
+ }
+
+ /**
+ * Generate a return statement.
+ * @return FLAG_STOP
+ */
+ public int genReturn() {
+ var type = getType();
+ sb.append("return ");
+ genExpression(type);
+ sb.append(";");
+ return FLAG_STOP;
+ }
+
+ /**
+ * Generate a binary expression.
+ * This method does not care about operator precedence.
+ */
+ public void genExpression(Type type) {
+ if (generateInvalid()) type = getType();
+ if (type != TYPE_INT) {
+ genUnary(type);
+ return;
+ }
+ var num = randLog(MAX_BINARY_EXPRESSIONS_PER_EXPRESSION);
+ while(num-->0) {
+ genUnary(TYPE_INT);
+ sb.append(BINARY_OP[random.nextInt(BINARY_OP.length)]);
+ }
+ genUnary(TYPE_INT);
+ }
+
+ /**
+ * Generate a unary expression.
+ */
+ public void genUnary(Type type) {
+ if (generateInvalid()) type = getType();
+ if (type != TYPE_INT) {
+ genSuffix(type);
+ return;
+ }
+ var num = randLog(MAX_UNARY_EXPRESSIONS_PER_EXPRESSION);
+ while(num-->0) {
+ sb.append(UNARY_OP[random.nextInt(UNARY_OP.length)]);
+ }
+ genSuffix(TYPE_INT);
+ }
+
+ /**
+ * Generate
+ */
+ public void genSuffix(Type type) {
+ if (generateInvalid()) type = getType();
+ if (type == TYPE_INT && random.nextBoolean()) {
+ var t = getType();
+ if (generateInvalid()) {
+ var field = getRandomName();
+ genPrimary(t);
+ sb.append(".").append(field);
+ return;
+ } else if (t instanceof TypeStruct s && s.fields.length > 0) {
+ var field = s.fields[random.nextInt(s.fields.length)];
+ if (field.type == TYPE_INT) {
+ genPrimary(t);
+ sb.append(".").append(field.name);
+ return;
+ }
+ }
+ }
+ genPrimary(type);
+ }
+
+ /**
+ * Generate a primary expression.
+ */
+ public void genPrimary(Type type) {
+ if (generateInvalid()) type = getType();
+ var rand = random.nextInt(10);
+ if (rand == 0 && exprDepth != 0) {
+ sb.append("(");
+ exprDepth--;
+ genExpression(type);
+ exprDepth++;
+ sb.append(")");
+ } else if (rand < 6) {
+ genVariable(type);
+ } else {
+ genConst(type);
+ }
+ }
+
+ /**
+ * Generate a constant.
+ */
+ public void genConst(Type type) {
+ if (generateInvalid()) type = getType();
+ if (type == TYPE_INT) {
+ var rand = random.nextInt(10);
+ switch (rand) {
+ case 0 -> sb.append("true");
+ case 1 -> sb.append("false");
+ default -> sb.append(random.nextInt(1<<(rand-2)));
+ }
+ } else if (type instanceof TypeNullable n) {
+ if (random.nextBoolean()) {
+ sb.append("null");
+ } else {
+ sb.append("new ").append(generateInvalid() ? getRandomName() : n.base.name);
+ }
+ } else {
+ sb.append("new ").append(generateInvalid() ? getRandomName() : type.name);
+ }
+ }
+
+ /**
+ * Generate a variable. If there are none generate a number instead.
+ * If the program is allowed to be invalid a random name can be generated in some cases.
+ */
+ public void genVariable(Type type) {
+ if (generateInvalid()) {
+ sb.append(getRandomName());
+ return;
+ }
+ if (generateInvalid()) type = getType();
+ var t = type;
+ var v = findVisibleVariablesMatching(va->va.type.isa(t));
+ if (v == null) genConst(type);
+ else sb.append(v.name);
+ }
+
+}