diff options
| author | Cliff Click <cliffc@acm.org> | 2026-09-29 12:34:58 -0700 |
|---|---|---|
| committer | Cliff Click <cliffc@acm.org> | 2026-09-29 12:34:58 -0700 |
| commit | 6347fa6dc2ee8d3060f2f0db21f03f11d374b0bc (patch) | |
| tree | 6da4371d0ac6a0e17cfc31dad2275f1331ab3edb | |
| parent | 41be002e89358649da46c33c8245aa786afdad6a (diff) | |
| download | Simple-6347fa6dc2ee8d3060f2f0db21f03f11d374b0bc.tar.gz Simple-6347fa6dc2ee8d3060f2f0db21f03f11d374b0bc.zip | |
Squashed commit of the following:
commit 3ba6686fc991998a0a420709dfb0012bbddf77ab
Author: Cliff Click <cliffc@acm.org>
Date: Tue Sep 29 12:21:46 2026 -0700
Backport lazy memory splits to ch10, split into ch10a and b
commit fa7d56f72455828826d0ab7e1c142ca814e6e2aa
Author: Cliff Click <cliffc@acm.org>
Date: Tue Sep 29 10:10:53 2026 -0700
Reverse edges
commit e1bba17ff54400a34ede0eaacad863420d021119
Author: Cliff Click <cliffc@acm.org>
Date: Tue Sep 29 09:44:41 2026 -0700
Split ch10
Basic memory introduction in 10a, then precise aliases in 10b.
183 files changed, 12611 insertions, 2855 deletions
diff --git a/.github/workflows/linearize.sh b/.github/workflows/linearize.sh index ea3499b5..e8785367 100755 --- a/.github/workflows/linearize.sh +++ b/.github/workflows/linearize.sh @@ -84,7 +84,7 @@ while read -r chapter; do cp "$repo"/{README.md,pom.xml} . mkdir docs # Remove links to chapters. - sed -Ei 's,\[Chapter ([0-9]+)\]\(chapter0?\1/README\.md\),Chapter \1,' README.md + sed -Ei 's,\[Chapter ([0-9]+[a-z]?)\]\(chapter0?\1/README\.md\),Chapter \1,' README.md # Change to a JAR and delete the modules, for a single-project structure. sed -i -e 's,<packaging>pom</packaging>,<packaging>jar</packaging>,' \ -e '/<modules>/,/^$/d' pom.xml @@ -23,3 +23,8 @@ TAGS # Libs */lib/*jar + +# Portable IDEA descriptors for the split memory chapters +!chapter10a/chapter10a.iml +!chapter10b/chapter10b.iml +!graph/graph.iml @@ -2,7 +2,7 @@ # make tests CHAPTERS="chapter21 chapter22" # make -k tests # continue through failures, but return a failing status .DEFAULT_GOAL := tests -CHAPTERS ?= $(sort $(patsubst %/pom.xml,%,$(wildcard chapter[0-9][0-9]/pom.xml))) +CHAPTERS ?= $(sort $(patsubst %/pom.xml,%,$(wildcard chapter[0-9][0-9]/pom.xml chapter[0-9][0-9][a-z]/pom.xml))) ACTIONS := tests tags release lib CHAPTER_TARGETS := $(foreach action,$(ACTIONS),$(addsuffix /$(action),$(CHAPTERS))) @@ -21,7 +21,7 @@ ahead-of-time compilation - but with an eye towards JIT compilation. This repo also is not intended to be a complete language in any sense, and so the backend starts with levering Java: the Evaluator (first appears in Chapter -10) directly slowly interprets the SoN IR. Code-gen first appears in Chapter +10a) directly slowly interprets the SoN IR. Code-gen first appears in Chapter 19. @@ -51,9 +51,10 @@ The Simple language is styled after a subset of C or Java. * [Chapter 7](chapter07/README.md): `while` statement; looping constructs - eager phi approach. * [Chapter 8](chapter08/README.md): Looping constructs continued, lazy phi creation, `break` and `continue` statements. * [Chapter 9](chapter09/README.md): Global Value Numbering. Iterative peepholes to fixpoint. Worklists. -* [Chapter 10](chapter10/README.md): User defined Struct types. Memory effects: - general memory edges in SSA. Equivalence class aliasing. Null pointer - analysis. Peephole optimization around load-after-store/store-after-store. +* [Chapter 10a](chapter10a/README.md): User defined structs, pointers and null + analysis. One memory value in SSA. Loads, stores, and an executable evaluator. +* [Chapter 10b](chapter10b/README.md): Equivalence class aliasing. Lazy memory + partitioning with `MemMerge`, `MemPhi`, and `BulkMemPhi`. * [Chapter 11](chapter11/README.md): Global Code Motion - Scheduling. * [Chapter 12](chapter12/README.md): Float type. * [Chapter 13](chapter13/README.md): Nested references in Structs. @@ -73,7 +74,7 @@ The Simple language is styled after a subset of C or Java. ## Building across chapters The optional [interactive graph viewer](graph/README.md) is shared in `graph/`. -Chapters 18–25 launch it with `make view` from the chapter directory. +Chapters 10a, 10b, and 18–25 launch it with `make view` from the chapter directory. The top-level Makefile runs each chapter's `tests`, `tags` (also `tag`), `release`, or `lib` target. Start with `make lib` on a fresh checkout, then @@ -81,4 +82,17 @@ The top-level Makefile runs each chapter's `tests`, `tags` (also `tag`), The [chapter backport queue](docs/chapter-backports.md) records proposed small corrections and the per-chapter test/review workflow. Larger architectural moves -remain deferred while that workflow is established. +are tracked separately there. The 10a/10b split is complete; forwarding 10b's +memory representation through Chapters 11-24 remains pending. Those snapshots +still use their earlier parser-managed alias chains. + +To build and test just the memory chapters: + +```sh +make lib tests release CHAPTERS="chapter10a chapter10b" +``` + +The root Maven reactor also includes both modules. Each chapter has its own +IDEA module descriptor and depends on the shared `graph` module. Chapter +directories with letter suffixes participate in the normal Make chapter discovery +and in the linear-history workflow. diff --git a/TypeAnalysis.md b/TypeAnalysis.md index ea51f41d..425f6dbc 100644 --- a/TypeAnalysis.md +++ b/TypeAnalysis.md @@ -305,7 +305,7 @@ but we will shortly use them in building structs and functions. [Lattice6](./docs/lattice_iffct.svg) <img src = ./docs/lattice_iffct.svg> -First appears in [Simple chapter10](https://github.com/SeaOfNodes/Simple/tree/main/chapter10) +First appears in [Simple chapter10a](https://github.com/SeaOfNodes/Simple/tree/main/chapter10a) along with structs and pointers. @@ -337,7 +337,7 @@ desired semantics, as well as the normal *meet* on the field type. I am not covering here how to initialize struct members, or the syntax for declaring structs or traits - just the resulting types. -First appears in [Simple chapter10](https://github.com/SeaOfNodes/Simple/tree/main/chapter10) +First appears in [Simple chapter10a](https://github.com/SeaOfNodes/Simple/tree/main/chapter10a) along with tuples and pointers. @@ -377,7 +377,7 @@ Here the load to fetch the `makeSound` method fails during error reporting; in Simple this is just a `LoadNode` which has an error check against its pointer input being possibly `null`. -First appears in [Simple chapter10](https://github.com/SeaOfNodes/Simple/tree/main/chapter10), +First appears in [Simple chapter10a](https://github.com/SeaOfNodes/Simple/tree/main/chapter10a), along with structs and tuples. diff --git a/chapter02/README.md b/chapter02/README.md index c670ea36..eda25269 100644 --- a/chapter02/README.md +++ b/chapter02/README.md @@ -93,7 +93,7 @@ optimizations such as: * The compiler may be able to identify regions of code that are dead and no longer needed, such as when a conditional branch always take one of the branches ([Chapter 6](../chapter06/README.md)). -* Pointers may be known to be not-null, and a null check can be skipped ([Chapter 10](../chapter10/README.md)). +* Pointers may be known to be not-null, and a null check can be skipped ([Chapter 10a](../chapter10a/README.md)). * Array indices may be known to be in-range, and a range check can be skipped. * Many additional optimizations are possible when the compiler learns more about the possible set of Node values. @@ -151,7 +151,7 @@ starting in [Chapter 4](../chapter04/README.md) and [Chapter There are other important properties of the Lattice that we discuss in [Chapter -4](../chapter04/README.md) and [Chapter 10](../chapter10/README.md), such as the "meet" and "join" operators and their rules. +4](../chapter04/README.md) and [Chapter 10a](../chapter10a/README.md), such as the "meet" and "join" operators and their rules. ## Nodes Pre Peephole Optimization diff --git a/chapter03/README.md b/chapter03/README.md index 39baf903..e8944e12 100644 --- a/chapter03/README.md +++ b/chapter03/README.md @@ -72,9 +72,9 @@ A ScopeNode encapsulates the stack of symbol tables that are used to implement l * In [Chapter 5](../chapter05/README.md) when we implement branches, the ScopeNode gets duplicated at a branch point and then gets merged when the branch joins. This allows the names to evolve in each branch independently. At the merge point names that have diverged require a Phi node. * In [Chapter 7](../chapter07/README.md) additional logic is deployed for handling Phis from back edges. -* The ScopeNode is used to track some other values such as the Control token starting from [Chapter 4](../chapter04/README.md), and Aliases starting from [Chapter 10](../chapter10/README.md). Special name bindings are used +* The ScopeNode is used to track some other values such as the Control token starting from [Chapter 4](../chapter04/README.md), and whole memory starting from [Chapter 10a](../chapter10a/README.md). Special name bindings are used for these, and each name is tracked across branches and then merged at merge points. -* In [Chapter 10](../chapter10/README.md) the ScopeNode is enhanced to also maintain the declared type for each name. +* In [Chapter 10a](../chapter10a/README.md) the ScopeNode is enhanced to also maintain the declared type for each name. From an implementation standpoint, each symbol table is represented as a `HashMap<String,Integer>`, where the `String` is the variable name, and the `Integer` is an index into the inputs of the ScopeNode. To access the Node associated with a name, first the symbol table is consulted, and then the ScopeNode's inputs to find the required Node. diff --git a/chapter10/Makefile b/chapter10/Makefile deleted file mode 100644 index 77df7182..00000000 --- a/chapter10/Makefile +++ /dev/null @@ -1,105 +0,0 @@ -SHELL := /bin/bash -# Shared packaging target; included after chapter source variables below. -.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)) -include $(firstword $(wildcard ../graph/graph.mk graph/graph.mk)) -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 $(graph_javas) - @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) com.seaofnodes.simple.FuzzerWrap - -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) - -include $(firstword $(wildcard ../build-support/chapter-release.mk build-support/chapter-release.mk)) - -.PHONY: build -build: $(main_classes) diff --git a/chapter10/README.md b/chapter10/README.md deleted file mode 100644 index 88f0016a..00000000 --- a/chapter10/README.md +++ /dev/null @@ -1,359 +0,0 @@ -# Chapter 10: Structs and Memory - - -# Table of Contents - -1. [Memory](#memory) -2. [Alias Classes](#alias-classes) -3. [Implementation of Alias Classes](#implementation-of-alias-classes) -4. [Extensions to Intermediate Representation ](#extensions-to-intermediate-representation) -5. [Enhanced Type Lattice](#enhanced-type-lattice) -6. [Parser Enhancements](#parser-enhancements) -7. [Null Pointer Analysis](#null-pointer-analysis) -8. [Memory Operations Example](#memory-operations-example) -9. [Additional Peephole Optimizations](#additional-peephole-optimizations) - -You can also read [this chapter](https://github.com/SeaOfNodes/Simple/tree/linear-chapter10) in a linear Git revision history on the [linear](https://github.com/SeaOfNodes/Simple/tree/linear) branch and [compare](https://github.com/SeaOfNodes/Simple/compare/linear-chapter09...linear-chapter10) it to the previous chapter. - -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. - - - -Within the Type Lattice, we now have the following type domains: - -* <font style="background-color:yellow">Control type</font> - represents control flow -* <font style="background-color:lightblue">Integer type</font> - Integer values, with a Top, Bottom and constants -* <font style="background-color:green" color="white">Pointer type</font> (new) - Represents a pointer to a struct type - * We use the prefix `*` to mean pointer-to. Thus `*S1` means pointer to - `S1`, `*$TOP` means pointer to `$TOP`. - * `?` suffix represents the union of a pointer to some type and `null`. - * The pointer lattice has only 2 entries: null or not-null. In both cases a struct lattice element is - included, and can be any member of the struct lattice. - * `null` is a pointer to non-existent memory object, i.e. `*$TOP?`, and is a - nullable. Notice that the null part of the type is low in the lattice, - while the struct part is the highest struct. - * `*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. It is - the inverse or *dual* of `null`, and the null part is high in the pointer - lattice, while the struct part is the lowest struct. - * `null` pointer evaluates to False and non-null pointers evaluate to True, as in C. - -* <font style="background-color:lightgreen">Struct type</font> (new) - - Represents user defined struct types, a struct type is allowed to have - members of Integer type only in this chapter, later chapters will expand - this. - * `⊤:struct` 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. In the code, we use a struct with no fields named `$TOP`. - * `⊥:struct` represents local Bottom for struct type; we definitely know the - value can take all possible struct types. In the code, we uise a struct - with no fields named `$BOT`. -* <font style="background-color:blue" color="white">Memory type</font> (new) - Represents memory and memory slices - * `⊤:mem` - Nothing known about a slice; in the code we use `MEM#TOP`. - * `#n` - refers to a memory slice, and `n` is the alias class. - * `⊥:mem` - all of memory; in the code we use `MEM#BOT`. -* <font style="background-color:pink">Tuple type</font> - when a Node results in a collection of values - -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 pointer `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?`, it is some `*S1?` with dualed - fields. - -* 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 symmetric and complete, 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 decides if 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()`. - -## Memory Operations Example - -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: - - - -The graph visual has the following enhancements: - -* The memory edges are shown in green and blue. -* The green edges occur on New nodes and anchor the ptr to a control flow node. -* The blue edges show the threading of a memory op via the equivalence aliasing. - -## Additional Peephole Optimizations - -The equivalence aliasing makes it possible to optimize following scenarios: - -* Store followed by another Store to the same field inside an object; the first Store is redundant if there are no other references to the first Store -* Load followed by a Store to a field inside an Object; the Load can be eliminated and the input value of the Store passed through. - -Both of these examples are illustrated in the example below: - -```java -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; -``` - -Before the peephole optimizations, we get: - - - -After enabling Store-Store peephole we get: - - - -After load-after-store elision: - - - -Note that at this stage the `bar` object is dead therefore the allocation of bar can be dropped. - - -[^1]: Diwan A., McKinley K.S., Moss J.E.B. (1998). - Type Based Alias Analysis. diff --git a/chapter10/docs/example1.gv b/chapter10/docs/example1.gv deleted file mode 100644 index cbb12cb5..00000000 --- a/chapter10/docs/example1.gv +++ /dev/null @@ -1,78 +0,0 @@ -digraph chapter10 { -/* -struct Vector2D { int x; int y; } -Vector2D v = new Vector2D; -v.x = 1; -if (arg) - v.y = 2; -else - v.y = 3; -return v; - -*/ - rankdir=BT; - 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">$2</TD><TD PORT="p3">$3</TD></TR> - </TABLE> - </TD></TR> - </TABLE>> - ]; - Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; - new8 [ label="new Vector2D" ]; - Con_9 [ label="#0" ]; - Store11 [ label=".y =" ]; - Con_12 [ label="#1" ]; - Store13 [ label=".x =" ]; - 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=".y =" ]; - Con_22 [ label="#3" ]; - Store23 [ label=".y =" ]; - Region24 [ shape=box style=filled fillcolor=yellow label="Region" ]; - Phi_326 [ style=filled fillcolor=lightyellow label="φ_$3" ]; - Return27 [ shape=box style=filled fillcolor=yellow label="Return" ]; - { rank=same; Region24;Phi_326;} - } - edge [ fontname=Helvetica, fontsize=8 ]; - Stop3 -> Return27[taillabel=0 color=red]; - new8 -> Start2:p0[taillabel=0 color=green]; - Store11 -> Start2:p3[taillabel=1 color=blue]; - Store11 -> new8[taillabel=2]; - Store11 -> Con_9[taillabel=3]; - Store13 -> Start2:p2[taillabel=1 color=blue]; - 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 color=blue]; - Store19 -> new8[taillabel=2]; - Store19 -> Con_18[taillabel=3]; - Store23 -> Store11[taillabel=1 color=blue]; - Store23 -> new8[taillabel=2]; - Store23 -> Con_22[taillabel=3]; - Region24 -> If14:p0[taillabel=1 color=red]; - Region24 -> If14:p1[taillabel=2 color=red]; - Phi_326 -> Region24 [style=dotted taillabel=0]; - Phi_326 -> Store19[taillabel=1 color=blue]; - Phi_326 -> Store23[taillabel=2 color=blue constraint=false]; - Return27 -> Region24[taillabel=0 color=red]; - Return27 -> new8[taillabel=1]; - Return27 -> Store13[taillabel=2 color=blue]; - Return27 -> Phi_326[taillabel=3 color=blue]; -} diff --git a/chapter10/docs/example1.svg b/chapter10/docs/example1.svg deleted file mode 100644 index 6f493e7e..00000000 --- a/chapter10/docs/example1.svg +++ /dev/null @@ -1,294 +0,0 @@ -<?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="394pt" height="564pt" - viewBox="0.00 0.00 394.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 390,-560 390,4 -4,4"/> -<g id="clust1" class="cluster"> -<title>cluster_Nodes</title> -<polygon fill="none" stroke="black" points="8,-8 8,-548 378,-548 378,-8 8,-8"/> -</g> -<!-- Start2 --> -<g id="node1" class="node"> -<title>Start2</title> -<polygon fill="yellow" stroke="transparent" points="25,-511 25,-536 156,-536 156,-511 25,-511"/> -<polygon fill="none" stroke="black" points="25,-511 25,-536 156,-536 156,-511 25,-511"/> -<text text-anchor="start" x="72" y="-519.8" font-family="Times,serif" font-size="14.00">Start</text> -<polygon fill="none" stroke="black" points="25,-480 25,-511 156,-511 156,-480 25,-480"/> -<polygon fill="yellow" stroke="transparent" points="30,-485 30,-506 70,-506 70,-485 30,-485"/> -<polygon fill="none" stroke="black" points="30,-485 30,-506 70,-506 70,-485 30,-485"/> -<text text-anchor="start" x="33" y="-491.8" font-family="Times,serif" font-size="14.00">$ctrl</text> -<polygon fill="none" stroke="black" points="70,-485 70,-506 101,-506 101,-485 70,-485"/> -<text text-anchor="start" x="73" y="-491.8" font-family="Times,serif" font-size="14.00">arg</text> -<polygon fill="none" stroke="black" points="101,-485 101,-506 126,-506 126,-485 101,-485"/> -<text text-anchor="start" x="104" y="-491.8" font-family="Times,serif" font-size="14.00">$2</text> -<polygon fill="none" stroke="black" points="126,-485 126,-506 151,-506 151,-485 126,-485"/> -<text text-anchor="start" x="129" y="-491.8" font-family="Times,serif" font-size="14.00">$3</text> -</g> -<!-- Stop3 --> -<g id="node2" class="node"> -<title>Stop3</title> -<polygon fill="yellow" stroke="black" points="201,-52 147,-52 147,-16 201,-16 201,-52"/> -<text text-anchor="middle" x="174" y="-30.3" font-family="Times,serif" font-size="14.00">Stop</text> -</g> -<!-- Return27 --> -<g id="node15" class="node"> -<title>Return27</title> -<polygon fill="yellow" stroke="black" points="207,-124 141,-124 141,-88 207,-88 207,-124"/> -<text text-anchor="middle" x="174" y="-102.3" font-family="Times,serif" font-size="14.00">Return</text> -</g> -<!-- Stop3->Return27 --> -<g id="edge1" class="edge"> -<title>Stop3->Return27</title> -<path fill="none" stroke="red" d="M174,-52.3C174,-60.02 174,-69.29 174,-77.89"/> -<polygon fill="red" stroke="red" points="170.5,-77.9 174,-87.9 177.5,-77.9 170.5,-77.9"/> -<text text-anchor="middle" x="171" 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="203" cy="-422" rx="75.29" ry="18"/> -<text text-anchor="middle" x="203" y="-418.3" font-family="Times,serif" font-size="14.00">new Vector2D</text> -</g> -<!-- new8->Start2 --> -<g id="edge2" class="edge"> -<title>new8->Start2:p0</title> -<path fill="none" stroke="green" d="M147.76,-434.26C110.79,-443.18 66.61,-456.99 53.71,-474.14"/> -<polygon fill="green" stroke="green" points="50.24,-473.41 50,-484 56.79,-475.87 50.24,-473.41"/> -<text text-anchor="middle" x="144.76" y="-436.86" 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="63" cy="-422" rx="27" ry="18"/> -<text text-anchor="middle" x="63" y="-418.3" font-family="Times,serif" font-size="14.00">#0</text> -</g> -<!-- Store11 --> -<g id="node5" class="node"> -<title>Store11</title> -<ellipse fill="none" stroke="black" cx="170" cy="-350" rx="29.8" ry="18"/> -<text text-anchor="middle" x="170" y="-346.3" font-family="Times,serif" font-size="14.00">.y =</text> -</g> -<!-- Store11->Start2 --> -<g id="edge3" class="edge"> -<title>Store11->Start2:p3</title> -<path fill="none" stroke="blue" d="M151.42,-364.19C139.69,-373.74 125.54,-387.77 119,-404 113.02,-418.84 115.61,-424.36 119,-440 122.69,-457.06 133.59,-461.26 137.54,-473.76"/> -<polygon fill="blue" stroke="blue" points="134.13,-474.59 139,-484 141.06,-473.61 134.13,-474.59"/> -<text text-anchor="middle" x="148.42" y="-366.79" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> -</g> -<!-- Store11->new8 --> -<g id="edge4" class="edge"> -<title>Store11->new8</title> -<path fill="none" stroke="black" d="M177.82,-367.59C181.67,-375.75 186.4,-385.78 190.71,-394.93"/> -<polygon fill="black" stroke="black" points="187.58,-396.49 195.01,-404.04 193.91,-393.51 187.58,-396.49"/> -<text text-anchor="middle" x="174.82" y="-370.19" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> -</g> -<!-- Store11->Con_9 --> -<g id="edge5" class="edge"> -<title>Store11->Con_9</title> -<path fill="none" stroke="black" d="M150.37,-363.84C133.5,-374.88 108.99,-390.92 90.22,-403.2"/> -<polygon fill="black" stroke="black" points="88.24,-400.31 81.78,-408.71 92.07,-406.17 88.24,-400.31"/> -<text text-anchor="middle" x="153.37" y="-366.44" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> -</g> -<!-- Con_12 --> -<g id="node6" class="node"> -<title>Con_12</title> -<ellipse fill="none" stroke="black" cx="323" cy="-422" rx="27" ry="18"/> -<text text-anchor="middle" x="323" y="-418.3" font-family="Times,serif" font-size="14.00">#1</text> -</g> -<!-- Store13 --> -<g id="node7" class="node"> -<title>Store13</title> -<ellipse fill="none" stroke="black" cx="340" cy="-350" rx="29.8" ry="18"/> -<text text-anchor="middle" x="340" y="-346.3" font-family="Times,serif" font-size="14.00">.x =</text> -</g> -<!-- Store13->Start2 --> -<g id="edge6" class="edge"> -<title>Store13->Start2:p2</title> -<path fill="none" stroke="blue" d="M350.63,-367.07C362.03,-386.53 376.33,-419.28 359,-440 290.38,-522.04 125.44,-383.17 114.57,-473.83"/> -<polygon fill="blue" stroke="blue" points="111.06,-473.82 114,-484 118.05,-474.21 111.06,-473.82"/> -<text text-anchor="middle" x="347.63" y="-369.67" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> -</g> -<!-- Store13->new8 --> -<g id="edge7" class="edge"> -<title>Store13->new8</title> -<path fill="none" stroke="black" d="M317.99,-362.25C297.67,-372.63 267.01,-388.3 242.46,-400.84"/> -<polygon fill="black" stroke="black" points="240.69,-397.81 233.38,-405.48 243.88,-404.04 240.69,-397.81"/> -<text text-anchor="middle" x="314.99" y="-364.85" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> -</g> -<!-- Store13->Con_12 --> -<g id="edge8" class="edge"> -<title>Store13->Con_12</title> -<path fill="none" stroke="black" d="M335.88,-367.95C333.99,-375.74 331.7,-385.18 329.58,-393.92"/> -<polygon fill="black" stroke="black" points="326.15,-393.18 327.2,-403.72 332.96,-394.83 326.15,-393.18"/> -<text text-anchor="middle" x="332.88" y="-370.55" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> -</g> -<!-- If14 --> -<g id="node8" class="node"> -<title>If14</title> -<polygon fill="yellow" stroke="transparent" points="24,-267 24,-292 118,-292 118,-267 24,-267"/> -<polygon fill="none" stroke="black" points="24,-267 24,-292 118,-292 118,-267 24,-267"/> -<text text-anchor="start" x="65.5" y="-275.8" font-family="Times,serif" font-size="14.00">If</text> -<polygon fill="none" stroke="black" points="24,-236 24,-267 118,-267 118,-236 24,-236"/> -<polygon fill="yellow" stroke="transparent" points="29,-241 29,-262 69,-262 69,-241 29,-241"/> -<polygon fill="none" stroke="black" points="29,-241 29,-262 69,-262 69,-241 29,-241"/> -<text text-anchor="start" x="32" y="-247.8" font-family="Times,serif" font-size="14.00">True</text> -<polygon fill="yellow" stroke="transparent" points="69,-241 69,-262 113,-262 113,-241 69,-241"/> -<polygon fill="none" stroke="black" points="69,-241 69,-262 113,-262 113,-241 69,-241"/> -<text text-anchor="start" x="72" y="-247.8" font-family="Times,serif" font-size="14.00">False</text> -</g> -<!-- If14->Start2 --> -<g id="edge9" class="edge"> -<title>If14->Start2:p0</title> -<path fill="none" stroke="red" d="M58.13,-296.08C47.38,-323.83 32.89,-365.85 27,-404 24.56,-419.81 23.18,-424.46 27,-440 31.32,-457.58 44.03,-461.08 48.46,-473.95"/> -<polygon fill="red" stroke="red" points="45.02,-474.65 50,-484 51.94,-473.58 45.02,-474.65"/> -<text text-anchor="middle" x="55.13" y="-298.68" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> -</g> -<!-- Con_18 --> -<g id="node9" class="node"> -<title>Con_18</title> -<ellipse fill="none" stroke="black" cx="95" cy="-350" rx="27" ry="18"/> -<text text-anchor="middle" x="95" y="-346.3" font-family="Times,serif" font-size="14.00">#2</text> -</g> -<!-- Store19 --> -<g id="node10" class="node"> -<title>Store19</title> -<ellipse fill="none" stroke="black" cx="174" cy="-264" rx="29.8" ry="18"/> -<text text-anchor="middle" x="174" y="-260.3" font-family="Times,serif" font-size="14.00">.y =</text> -</g> -<!-- Store19->new8 --> -<g id="edge12" class="edge"> -<title>Store19->new8</title> -<path fill="none" stroke="black" d="M188.91,-279.62C203.35,-295.52 222.56,-322.27 219,-349"/> -<path fill="none" stroke="black" d="M219,-351C217.08,-365.43 213.58,-381.26 210.34,-394.21"/> -<polygon fill="black" stroke="black" points="206.95,-393.34 207.84,-403.9 213.73,-395.09 206.95,-393.34"/> -<text text-anchor="middle" x="185.91" y="-282.22" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> -</g> -<!-- Store19->Store11 --> -<g id="edge11" class="edge"> -<title>Store19->Store11</title> -<path fill="none" stroke="blue" d="M173.19,-282C172.65,-293.31 171.93,-308.42 171.31,-321.43"/> -<polygon fill="blue" stroke="blue" points="167.8,-321.58 170.82,-331.74 174.79,-321.92 167.8,-321.58"/> -<text text-anchor="middle" x="170.19" y="-284.6" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> -</g> -<!-- Store19->Con_18 --> -<g id="edge13" class="edge"> -<title>Store19->Con_18</title> -<path fill="none" stroke="black" d="M159.88,-280.02C147.56,-293.12 129.52,-312.29 115.59,-327.1"/> -<polygon fill="black" stroke="black" points="113.04,-324.71 108.74,-334.39 118.14,-329.51 113.04,-324.71"/> -<text text-anchor="middle" x="156.88" y="-282.62" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> -</g> -<!-- Con_22 --> -<g id="node11" class="node"> -<title>Con_22</title> -<ellipse fill="none" stroke="black" cx="265" cy="-350" rx="27" ry="18"/> -<text text-anchor="middle" x="265" y="-346.3" font-family="Times,serif" font-size="14.00">#3</text> -</g> -<!-- Store23 --> -<g id="node12" class="node"> -<title>Store23</title> -<ellipse fill="none" stroke="black" cx="272" cy="-264" rx="29.8" ry="18"/> -<text text-anchor="middle" x="272" y="-260.3" font-family="Times,serif" font-size="14.00">.y =</text> -</g> -<!-- Store23->new8 --> -<g id="edge15" class="edge"> -<title>Store23->new8</title> -<path fill="none" stroke="black" d="M257.04,-279.75C242.69,-295.35 222.67,-321.48 219,-349"/> -<text text-anchor="middle" x="254.04" y="-282.35" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> -</g> -<!-- Store23->Store11 --> -<g id="edge14" class="edge"> -<title>Store23->Store11</title> -<path fill="none" stroke="blue" d="M254.7,-279.25C238.26,-292.79 213.37,-313.28 194.82,-328.56"/> -<polygon fill="blue" stroke="blue" points="192.54,-325.9 187.05,-334.96 196.99,-331.31 192.54,-325.9"/> -<text text-anchor="middle" x="251.7" y="-272.85" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> -</g> -<!-- Store23->Con_22 --> -<g id="edge16" class="edge"> -<title>Store23->Con_22</title> -<path fill="none" stroke="black" d="M270.58,-282C269.64,-293.31 268.38,-308.42 267.3,-321.43"/> -<polygon fill="black" stroke="black" points="263.78,-321.48 266.44,-331.74 270.76,-322.06 263.78,-321.48"/> -<text text-anchor="middle" x="267.58" y="-284.6" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> -</g> -<!-- Region24 --> -<g id="node13" class="node"> -<title>Region24</title> -<polygon fill="yellow" stroke="black" points="123,-196 57,-196 57,-160 123,-160 123,-196"/> -<text text-anchor="middle" x="90" y="-174.3" font-family="Times,serif" font-size="14.00">Region</text> -</g> -<!-- Region24->If14 --> -<g id="edge17" class="edge"> -<title>Region24->If14:p0</title> -<path fill="none" stroke="red" d="M70.46,-196.22C62.05,-205.18 53.41,-216.97 50.26,-229.91"/> -<polygon fill="red" stroke="red" points="46.76,-229.64 49,-240 53.71,-230.51 46.76,-229.64"/> -<text text-anchor="middle" x="67.46" y="-198.82" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> -</g> -<!-- Region24->If14 --> -<g id="edge18" class="edge"> -<title>Region24->If14:p1</title> -<path fill="none" stroke="red" d="M90.43,-196.09C90.64,-205.55 90.87,-217.77 90.96,-229.69"/> -<polygon fill="red" stroke="red" points="87.46,-230.01 91,-240 94.46,-229.99 87.46,-230.01"/> -<text text-anchor="middle" x="87.43" y="-198.69" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> -</g> -<!-- Phi_326 --> -<g id="node14" class="node"> -<title>Phi_326</title> -<ellipse fill="lightyellow" stroke="black" cx="174" cy="-178" rx="33.29" ry="18"/> -<text text-anchor="middle" x="174" y="-174.3" font-family="Times,serif" font-size="14.00">φ_$3</text> -</g> -<!-- Phi_326->Store19 --> -<g id="edge20" class="edge"> -<title>Phi_326->Store19</title> -<path fill="none" stroke="blue" d="M174,-196.4C174,-207.74 174,-222.77 174,-235.68"/> -<polygon fill="blue" stroke="blue" points="170.5,-235.9 174,-245.9 177.5,-235.9 170.5,-235.9"/> -<text text-anchor="middle" x="171" y="-199" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> -</g> -<!-- Phi_326->Store23 --> -<g id="edge21" class="edge"> -<title>Phi_326->Store23</title> -<path fill="none" stroke="blue" d="M191.07,-193.63C206.76,-207.08 230.2,-227.17 247.81,-242.27"/> -<polygon fill="blue" stroke="blue" points="245.58,-244.97 255.45,-248.82 250.14,-239.65 245.58,-244.97"/> -<text text-anchor="middle" x="188.07" y="-196.23" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> -</g> -<!-- Phi_326->Region24 --> -<g id="edge19" class="edge"> -<title>Phi_326->Region24</title> -<path fill="none" stroke="black" stroke-dasharray="1,5" d="M140.62,-178C138.17,-178 135.71,-178 133.25,-178"/> -<polygon fill="black" stroke="black" points="133.14,-174.5 123.14,-178 133.14,-181.5 133.14,-174.5"/> -<text text-anchor="middle" x="137.62" y="-171.6" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> -</g> -<!-- Return27->new8 --> -<g id="edge23" class="edge"> -<title>Return27->new8</title> -<path fill="none" stroke="black" d="M207.48,-120.36C230.67,-131.64 259.67,-150.25 273,-177"/> -<path fill="none" stroke="black" d="M273,-179C286.16,-205.41 244.02,-204.62 233,-232 213.44,-280.58 225.92,-297.09 219,-349"/> -<text text-anchor="middle" x="210.48" y="-113.96" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> -</g> -<!-- Return27->Store13 --> -<g id="edge24" class="edge"> -<title>Return27->Store13</title> -<path fill="none" stroke="blue" d="M273,-179C286,-204.9 298.85,-205.68 311,-232 324.45,-261.13 332.19,-297.33 336.24,-321.69"/> -<polygon fill="blue" stroke="blue" points="332.79,-322.32 337.8,-331.66 339.71,-321.24 332.79,-322.32"/> -<text text-anchor="middle" x="270" y="-172.6" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> -</g> -<!-- Return27->Region24 --> -<g id="edge22" class="edge"> -<title>Return27->Region24</title> -<path fill="none" stroke="red" d="M153.24,-124.3C142.76,-133.03 129.89,-143.76 118.5,-153.25"/> -<polygon fill="red" stroke="red" points="115.97,-150.81 110.53,-159.9 120.45,-156.18 115.97,-150.81"/> -<text text-anchor="middle" x="150.24" y="-126.9" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> -</g> -<!-- Return27->Phi_326 --> -<g id="edge25" class="edge"> -<title>Return27->Phi_326</title> -<path fill="none" stroke="blue" d="M174,-124.3C174,-132.02 174,-141.29 174,-149.89"/> -<polygon fill="blue" stroke="blue" points="170.5,-149.9 174,-159.9 177.5,-149.9 170.5,-149.9"/> -<text text-anchor="middle" x="171" y="-126.9" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> -</g> -</g> -</svg> diff --git a/chapter10/docs/example2a.gv b/chapter10/docs/example2a.gv deleted file mode 100644 index 23f31bf8..00000000 --- a/chapter10/docs/example2a.gv +++ /dev/null @@ -1,65 +0,0 @@ -digraph chapter10 { -/* -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; - -*/ - rankdir=BT; - 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="p2">$2</TD><TD PORT="p3">$3</TD><TD PORT="p4">$4</TD></TR> - </TABLE> - </TD></TR> - </TABLE>> - ]; - Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; - new10 [ label="new Bar" ]; - Con_11 [ label="#0" ]; - Store12 [ label=".a =" ]; - Store13 [ label=".b =" ]; - Con_14 [ label="#1" ]; - Store15 [ label=".a =" ]; - Con_16 [ label="#2" ]; - Store17 [ label=".a =" ]; - Load18 [ label=".a" ]; - Return19 [ shape=box style=filled fillcolor=yellow label="Return" ]; - } - edge [ fontname=Helvetica, fontsize=8 ]; - Stop3 -> Return19[taillabel=0 color=red]; - new10 -> Start2:p0[taillabel=0 color=green]; - Store12 -> Start2:p2[taillabel=1 color=blue]; - Store12 -> new10[taillabel=2]; - Store12 -> Con_11[taillabel=3]; - Store13 -> Start2:p3[taillabel=1 color=blue]; - Store13 -> new10[taillabel=2]; - Store13 -> Con_11[taillabel=3]; - Store15 -> Store12[taillabel=1 color=blue]; - Store15 -> new10[taillabel=2]; - Store15 -> Con_14[taillabel=3]; - Store17 -> Store15[taillabel=1 color=blue]; - Store17 -> new10[taillabel=2]; - Store17 -> Con_16[taillabel=3]; - Load18 -> Store17[taillabel=1 color=blue]; - Load18 -> new10[taillabel=2]; - Return19 -> Start2:p0[taillabel=0 color=red]; - Return19 -> Load18[taillabel=1]; - Return19 -> Store17[taillabel=2 color=blue]; - Return19 -> Store13[taillabel=3 color=blue]; - Return19 -> Start2:p4[taillabel=4 color=blue]; -} diff --git a/chapter10/docs/example2a.svg b/chapter10/docs/example2a.svg deleted file mode 100644 index 9f069ad4..00000000 --- a/chapter10/docs/example2a.svg +++ /dev/null @@ -1,249 +0,0 @@ -<?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="331pt" height="608pt" - viewBox="0.00 0.00 331.00 608.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 604)"> -<title>chapter10</title> -<polygon fill="white" stroke="transparent" points="-4,4 -4,-604 327,-604 327,4 -4,4"/> -<g id="clust1" class="cluster"> -<title>cluster_Nodes</title> -<polygon fill="none" stroke="black" points="8,-8 8,-592 315,-592 315,-8 8,-8"/> -</g> -<!-- Start2 --> -<g id="node1" class="node"> -<title>Start2</title> -<polygon fill="yellow" stroke="transparent" points="129,-555 129,-580 254,-580 254,-555 129,-555"/> -<polygon fill="none" stroke="black" points="129,-555 129,-580 254,-580 254,-555 129,-555"/> -<text text-anchor="start" x="173" y="-563.8" font-family="Times,serif" font-size="14.00">Start</text> -<polygon fill="none" stroke="black" points="129,-524 129,-555 254,-555 254,-524 129,-524"/> -<polygon fill="yellow" stroke="transparent" points="134,-529 134,-550 174,-550 174,-529 134,-529"/> -<polygon fill="none" stroke="black" points="134,-529 134,-550 174,-550 174,-529 134,-529"/> -<text text-anchor="start" x="137" y="-535.8" font-family="Times,serif" font-size="14.00">$ctrl</text> -<polygon fill="none" stroke="black" points="174,-529 174,-550 199,-550 199,-529 174,-529"/> -<text text-anchor="start" x="177" y="-535.8" font-family="Times,serif" font-size="14.00">$2</text> -<polygon fill="none" stroke="black" points="199,-529 199,-550 224,-550 224,-529 199,-529"/> -<text text-anchor="start" x="202" y="-535.8" font-family="Times,serif" font-size="14.00">$3</text> -<polygon fill="none" stroke="black" points="224,-529 224,-550 249,-550 249,-529 224,-529"/> -<text text-anchor="start" x="227" y="-535.8" font-family="Times,serif" font-size="14.00">$4</text> -</g> -<!-- Stop3 --> -<g id="node2" class="node"> -<title>Stop3</title> -<polygon fill="yellow" stroke="black" points="101,-52 47,-52 47,-16 101,-16 101,-52"/> -<text text-anchor="middle" x="74" y="-30.3" font-family="Times,serif" font-size="14.00">Stop</text> -</g> -<!-- Return19 --> -<g id="node12" class="node"> -<title>Return19</title> -<polygon fill="yellow" stroke="black" points="107,-124 41,-124 41,-88 107,-88 107,-124"/> -<text text-anchor="middle" x="74" y="-102.3" font-family="Times,serif" font-size="14.00">Return</text> -</g> -<!-- Stop3->Return19 --> -<g id="edge1" class="edge"> -<title>Stop3->Return19</title> -<path fill="none" stroke="red" d="M74,-52.3C74,-60.02 74,-69.29 74,-77.89"/> -<polygon fill="red" stroke="red" points="70.5,-77.9 74,-87.9 77.5,-77.9 70.5,-77.9"/> -<text text-anchor="middle" x="71" y="-54.9" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> -</g> -<!-- new10 --> -<g id="node3" class="node"> -<title>new10</title> -<ellipse fill="none" stroke="black" cx="118" cy="-466" rx="49.29" ry="18"/> -<text text-anchor="middle" x="118" y="-462.3" font-family="Times,serif" font-size="14.00">new Bar</text> -</g> -<!-- new10->Start2 --> -<g id="edge2" class="edge"> -<title>new10->Start2:p0</title> -<path fill="none" stroke="green" d="M133.89,-483.12C141.58,-492.29 149.78,-504.57 152.8,-517.77"/> -<polygon fill="green" stroke="green" points="149.36,-518.48 154,-528 156.31,-517.66 149.36,-518.48"/> -<text text-anchor="middle" x="130.89" y="-485.72" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> -</g> -<!-- Con_11 --> -<g id="node4" class="node"> -<title>Con_11</title> -<ellipse fill="none" stroke="black" cx="232" cy="-466" rx="27" ry="18"/> -<text text-anchor="middle" x="232" y="-462.3" font-family="Times,serif" font-size="14.00">#0</text> -</g> -<!-- Store12 --> -<g id="node5" class="node"> -<title>Store12</title> -<ellipse fill="none" stroke="black" cx="178" cy="-394" rx="29.8" ry="18"/> -<text text-anchor="middle" x="178" y="-390.3" font-family="Times,serif" font-size="14.00">.a =</text> -</g> -<!-- Store12->Start2 --> -<g id="edge3" class="edge"> -<title>Store12->Start2:p2</title> -<path fill="none" stroke="blue" d="M179.7,-412.2C181.85,-435.45 185.36,-478.51 185.92,-517.62"/> -<polygon fill="blue" stroke="blue" points="182.43,-518.03 186,-528 189.43,-517.97 182.43,-518.03"/> -<text text-anchor="middle" x="176.7" y="-414.8" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> -</g> -<!-- Store12->new10 --> -<g id="edge4" class="edge"> -<title>Store12->new10</title> -<path fill="none" stroke="black" d="M164.68,-410.54C157.06,-419.43 147.32,-430.79 138.71,-440.84"/> -<polygon fill="black" stroke="black" points="135.97,-438.66 132.11,-448.53 141.28,-443.22 135.97,-438.66"/> -<text text-anchor="middle" x="161.68" y="-413.14" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> -</g> -<!-- Store12->Con_11 --> -<g id="edge5" class="edge"> -<title>Store12->Con_11</title> -<path fill="none" stroke="black" d="M189.98,-410.54C196.98,-419.6 205.94,-431.21 213.79,-441.39"/> -<polygon fill="black" stroke="black" points="211.17,-443.73 220.05,-449.51 216.71,-439.45 211.17,-443.73"/> -<text text-anchor="middle" x="186.98" y="-413.14" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> -</g> -<!-- Store13 --> -<g id="node6" class="node"> -<title>Store13</title> -<ellipse fill="none" stroke="black" cx="256" cy="-394" rx="30.59" ry="18"/> -<text text-anchor="middle" x="256" y="-390.3" font-family="Times,serif" font-size="14.00">.b =</text> -</g> -<!-- Store13->Start2 --> -<g id="edge6" class="edge"> -<title>Store13->Start2:p3</title> -<path fill="none" stroke="blue" d="M263.6,-411.63C270.91,-430.21 279.31,-460.72 268,-484 256,-508.69 222.7,-499.84 214.07,-517.7"/> -<polygon fill="blue" stroke="blue" points="210.54,-517.51 212,-528 217.4,-518.88 210.54,-517.51"/> -<text text-anchor="middle" x="260.6" y="-414.23" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> -</g> -<!-- Store13->new10 --> -<g id="edge7" class="edge"> -<title>Store13->new10</title> -<path fill="none" stroke="black" d="M233.52,-406.4C212.26,-417.19 179.96,-433.57 154.91,-446.28"/> -<polygon fill="black" stroke="black" points="153.04,-443.3 145.71,-450.95 156.21,-449.54 153.04,-443.3"/> -<text text-anchor="middle" x="230.52" y="-409" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> -</g> -<!-- Store13->Con_11 --> -<g id="edge8" class="edge"> -<title>Store13->Con_11</title> -<path fill="none" stroke="black" d="M250.19,-411.95C247.45,-419.94 244.11,-429.67 241.05,-438.6"/> -<polygon fill="black" stroke="black" points="237.69,-437.62 237.76,-448.21 244.31,-439.89 237.69,-437.62"/> -<text text-anchor="middle" x="247.19" y="-414.55" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> -</g> -<!-- Con_14 --> -<g id="node7" class="node"> -<title>Con_14</title> -<ellipse fill="none" stroke="black" cx="83" cy="-394" rx="27" ry="18"/> -<text text-anchor="middle" x="83" y="-390.3" font-family="Times,serif" font-size="14.00">#1</text> -</g> -<!-- Store15 --> -<g id="node8" class="node"> -<title>Store15</title> -<ellipse fill="none" stroke="black" cx="86" cy="-322" rx="29.8" ry="18"/> -<text text-anchor="middle" x="86" y="-318.3" font-family="Times,serif" font-size="14.00">.a =</text> -</g> -<!-- Store15->new10 --> -<g id="edge10" class="edge"> -<title>Store15->new10</title> -<path fill="none" stroke="black" d="M98.96,-338.58C106.42,-348.58 115.07,-362.24 119,-376 124.76,-396.15 124.14,-419.93 122.28,-437.84"/> -<polygon fill="black" stroke="black" points="118.79,-437.53 121.04,-447.89 125.74,-438.39 118.79,-437.53"/> -<text text-anchor="middle" x="95.96" y="-341.18" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> -</g> -<!-- Store15->Store12 --> -<g id="edge9" class="edge"> -<title>Store15->Store12</title> -<path fill="none" stroke="blue" d="M103.74,-336.5C117.4,-346.89 136.48,-361.41 151.88,-373.13"/> -<polygon fill="blue" stroke="blue" points="149.92,-376.04 160,-379.31 154.16,-370.47 149.92,-376.04"/> -<text text-anchor="middle" x="106.74" y="-339.1" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> -</g> -<!-- Store15->Con_14 --> -<g id="edge11" class="edge"> -<title>Store15->Con_14</title> -<path fill="none" stroke="black" d="M85.26,-340.3C84.93,-348.02 84.53,-357.29 84.16,-365.89"/> -<polygon fill="black" stroke="black" points="80.66,-365.75 83.73,-375.9 87.66,-366.05 80.66,-365.75"/> -<text text-anchor="middle" x="82.26" y="-342.9" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> -</g> -<!-- Con_16 --> -<g id="node9" class="node"> -<title>Con_16</title> -<ellipse fill="none" stroke="black" cx="161" cy="-322" rx="27" ry="18"/> -<text text-anchor="middle" x="161" y="-318.3" font-family="Times,serif" font-size="14.00">#2</text> -</g> -<!-- Store17 --> -<g id="node10" class="node"> -<title>Store17</title> -<ellipse fill="none" stroke="black" cx="86" cy="-250" rx="29.8" ry="18"/> -<text text-anchor="middle" x="86" y="-246.3" font-family="Times,serif" font-size="14.00">.a =</text> -</g> -<!-- Store17->new10 --> -<g id="edge13" class="edge"> -<title>Store17->new10</title> -<path fill="none" stroke="black" d="M70.13,-265.26C57.27,-278.25 40.68,-298.75 37,-321"/> -<text text-anchor="middle" x="67.13" y="-267.86" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> -</g> -<!-- Store17->Store15 --> -<g id="edge12" class="edge"> -<title>Store17->Store15</title> -<path fill="none" stroke="blue" d="M86,-268.3C86,-276.02 86,-285.29 86,-293.89"/> -<polygon fill="blue" stroke="blue" points="82.5,-293.9 86,-303.9 89.5,-293.9 82.5,-293.9"/> -<text text-anchor="middle" x="83" y="-270.9" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> -</g> -<!-- Store17->Con_16 --> -<g id="edge14" class="edge"> -<title>Store17->Con_16</title> -<path fill="none" stroke="black" d="M101.54,-265.5C112.24,-275.49 126.62,-288.91 138.56,-300.06"/> -<polygon fill="black" stroke="black" points="136.29,-302.73 145.99,-306.99 141.07,-297.61 136.29,-302.73"/> -<text text-anchor="middle" x="98.54" y="-268.1" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> -</g> -<!-- Load18 --> -<g id="node11" class="node"> -<title>Load18</title> -<ellipse fill="none" stroke="black" cx="74" cy="-178" rx="27" ry="18"/> -<text text-anchor="middle" x="74" y="-174.3" font-family="Times,serif" font-size="14.00">.a</text> -</g> -<!-- Load18->new10 --> -<g id="edge16" class="edge"> -<title>Load18->new10</title> -<path fill="none" stroke="black" d="M63.89,-195.07C57.97,-205.26 50.92,-218.94 47,-232 35.56,-270.12 43.5,-281.73 37,-321"/> -<path fill="none" stroke="black" d="M37,-323C29.78,-362.14 27.2,-377.47 47,-412 54.9,-425.77 67.96,-437.05 80.75,-445.61"/> -<polygon fill="black" stroke="black" points="79.16,-448.74 89.49,-451.1 82.88,-442.82 79.16,-448.74"/> -<text text-anchor="middle" x="60.89" y="-197.67" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> -</g> -<!-- Load18->Store17 --> -<g id="edge15" class="edge"> -<title>Load18->Store17</title> -<path fill="none" stroke="blue" d="M76.9,-195.95C78.23,-203.65 79.82,-212.97 81.31,-221.64"/> -<polygon fill="blue" stroke="blue" points="77.9,-222.46 83.04,-231.72 84.8,-221.28 77.9,-222.46"/> -<text text-anchor="middle" x="73.9" y="-198.55" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> -</g> -<!-- Return19->Start2 --> -<g id="edge17" class="edge"> -<title>Return19->Start2:p0</title> -<path fill="none" stroke="red" d="M59.94,-124.2C52.45,-134.13 43.64,-147.17 38,-160 21.64,-197.21 17,-208.36 17,-249 17,-249 17,-249 17,-395 17,-473.65 47.97,-533.65 122.67,-538.66"/> -<polygon fill="red" stroke="red" points="122.89,-542.17 133,-539 123.12,-535.17 122.89,-542.17"/> -<text text-anchor="middle" x="56.94" y="-126.8" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> -</g> -<!-- Return19->Start2 --> -<g id="edge21" class="edge"> -<title>Return19->Start2:p4</title> -<path fill="none" stroke="blue" d="M306,-323C287.16,-411.95 339.16,-519.36 271.5,-536.61"/> -<polygon fill="blue" stroke="blue" points="270.87,-533.17 261.65,-538.38 272.11,-540.06 270.87,-533.17"/> -<text text-anchor="middle" x="303" y="-316.6" font-family="Helvetica,sans-Serif" font-size="8.00">4</text> -</g> -<!-- Return19->Store13 --> -<g id="edge20" class="edge"> -<title>Return19->Store13</title> -<path fill="none" stroke="blue" d="M306,-179C314.76,-241.5 316.5,-258.77 306,-321"/> -<path fill="none" stroke="blue" d="M306,-323C302.13,-341.27 290.11,-358.39 278.83,-371.13"/> -<polygon fill="blue" stroke="blue" points="276.24,-368.77 271.98,-378.47 281.36,-373.55 276.24,-368.77"/> -<text text-anchor="middle" x="303" y="-172.6" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> -</g> -<!-- Return19->Store17 --> -<g id="edge19" class="edge"> -<title>Return19->Store17</title> -<path fill="none" stroke="blue" d="M107.24,-110.22C170.02,-117.23 300.33,-136.59 306,-177"/> -<path fill="none" stroke="blue" d="M306,-179C311.23,-216.32 190.09,-236.58 125.4,-244.71"/> -<polygon fill="blue" stroke="blue" points="124.65,-241.27 115.15,-245.95 125.5,-248.22 124.65,-241.27"/> -<text text-anchor="middle" x="110.24" y="-103.82" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> -</g> -<!-- Return19->Load18 --> -<g id="edge18" class="edge"> -<title>Return19->Load18</title> -<path fill="none" stroke="black" d="M74,-124.3C74,-132.02 74,-141.29 74,-149.89"/> -<polygon fill="black" stroke="black" points="70.5,-149.9 74,-159.9 77.5,-149.9 70.5,-149.9"/> -<text text-anchor="middle" x="71" y="-126.9" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> -</g> -</g> -</svg> diff --git a/chapter10/docs/example2b.gv b/chapter10/docs/example2b.gv deleted file mode 100644 index d732e874..00000000 --- a/chapter10/docs/example2b.gv +++ /dev/null @@ -1,56 +0,0 @@ -digraph chapter10 { -/* -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; - -*/ - rankdir=BT; - 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="p2">$2</TD><TD PORT="p3">$3</TD><TD PORT="p4">$4</TD></TR> - </TABLE> - </TD></TR> - </TABLE>> - ]; - Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; - new10 [ label="new Bar" ]; - Con_11 [ label="#0" ]; - Store13 [ label=".b =" ]; - Con_16 [ label="#2" ]; - Store17 [ label=".a =" ]; - Load18 [ label=".a" ]; - Return19 [ shape=box style=filled fillcolor=yellow label="Return" ]; - } - edge [ fontname=Helvetica, fontsize=8 ]; - Stop3 -> Return19[taillabel=0 color=red]; - new10 -> Start2:p0[taillabel=0 color=green]; - Store13 -> Start2:p3[taillabel=1 color=blue]; - Store13 -> new10[taillabel=2]; - Store13 -> Con_11[taillabel=3]; - Store17 -> Start2:p2[taillabel=1 color=blue]; - Store17 -> new10[taillabel=2]; - Store17 -> Con_16[taillabel=3]; - Load18 -> Store17[taillabel=1 color=blue]; - Load18 -> new10[taillabel=2]; - Return19 -> Start2:p0[taillabel=0 color=red]; - Return19 -> Load18[taillabel=1]; - Return19 -> Store17[taillabel=2 color=blue]; - Return19 -> Store13[taillabel=3 color=blue]; - Return19 -> Start2:p4[taillabel=4 color=blue]; -} diff --git a/chapter10/docs/example2b.svg b/chapter10/docs/example2b.svg deleted file mode 100644 index 1ecf4de7..00000000 --- a/chapter10/docs/example2b.svg +++ /dev/null @@ -1,189 +0,0 @@ -<?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="369pt" height="464pt" - viewBox="0.00 0.00 368.60 464.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 460)"> -<title>chapter10</title> -<polygon fill="white" stroke="transparent" points="-4,4 -4,-460 364.6,-460 364.6,4 -4,4"/> -<g id="clust1" class="cluster"> -<title>cluster_Nodes</title> -<polygon fill="none" stroke="black" points="8,-8 8,-448 346,-448 346,-8 8,-8"/> -</g> -<!-- Start2 --> -<g id="node1" class="node"> -<title>Start2</title> -<polygon fill="yellow" stroke="transparent" points="152,-411 152,-436 277,-436 277,-411 152,-411"/> -<polygon fill="none" stroke="black" points="152,-411 152,-436 277,-436 277,-411 152,-411"/> -<text text-anchor="start" x="196" y="-419.8" font-family="Times,serif" font-size="14.00">Start</text> -<polygon fill="none" stroke="black" points="152,-380 152,-411 277,-411 277,-380 152,-380"/> -<polygon fill="yellow" stroke="transparent" points="157,-385 157,-406 197,-406 197,-385 157,-385"/> -<polygon fill="none" stroke="black" points="157,-385 157,-406 197,-406 197,-385 157,-385"/> -<text text-anchor="start" x="160" y="-391.8" font-family="Times,serif" font-size="14.00">$ctrl</text> -<polygon fill="none" stroke="black" points="197,-385 197,-406 222,-406 222,-385 197,-385"/> -<text text-anchor="start" x="200" y="-391.8" font-family="Times,serif" font-size="14.00">$2</text> -<polygon fill="none" stroke="black" points="222,-385 222,-406 247,-406 247,-385 222,-385"/> -<text text-anchor="start" x="225" y="-391.8" font-family="Times,serif" font-size="14.00">$3</text> -<polygon fill="none" stroke="black" points="247,-385 247,-406 272,-406 272,-385 247,-385"/> -<text text-anchor="start" x="250" y="-391.8" font-family="Times,serif" font-size="14.00">$4</text> -</g> -<!-- Stop3 --> -<g id="node2" class="node"> -<title>Stop3</title> -<polygon fill="yellow" stroke="black" points="139,-52 85,-52 85,-16 139,-16 139,-52"/> -<text text-anchor="middle" x="112" y="-30.3" font-family="Times,serif" font-size="14.00">Stop</text> -</g> -<!-- Return19 --> -<g id="node9" class="node"> -<title>Return19</title> -<polygon fill="yellow" stroke="black" points="145,-124 79,-124 79,-88 145,-88 145,-124"/> -<text text-anchor="middle" x="112" y="-102.3" font-family="Times,serif" font-size="14.00">Return</text> -</g> -<!-- Stop3->Return19 --> -<g id="edge1" class="edge"> -<title>Stop3->Return19</title> -<path fill="none" stroke="red" d="M112,-52.3C112,-60.02 112,-69.29 112,-77.89"/> -<polygon fill="red" stroke="red" points="108.5,-77.9 112,-87.9 115.5,-77.9 108.5,-77.9"/> -<text text-anchor="middle" x="109" y="-54.9" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> -</g> -<!-- new10 --> -<g id="node3" class="node"> -<title>new10</title> -<ellipse fill="none" stroke="black" cx="177" cy="-322" rx="49.29" ry="18"/> -<text text-anchor="middle" x="177" y="-318.3" font-family="Times,serif" font-size="14.00">new Bar</text> -</g> -<!-- new10->Start2 --> -<g id="edge2" class="edge"> -<title>new10->Start2:p0</title> -<path fill="none" stroke="green" d="M177,-340.09C177,-349.55 177,-361.77 177,-373.69"/> -<polygon fill="green" stroke="green" points="173.5,-374 177,-384 180.5,-374 173.5,-374"/> -<text text-anchor="middle" x="174" y="-342.69" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> -</g> -<!-- Con_11 --> -<g id="node4" class="node"> -<title>Con_11</title> -<ellipse fill="none" stroke="black" cx="291" cy="-322" rx="27" ry="18"/> -<text text-anchor="middle" x="291" y="-318.3" font-family="Times,serif" font-size="14.00">#0</text> -</g> -<!-- Store13 --> -<g id="node5" class="node"> -<title>Store13</title> -<ellipse fill="none" stroke="black" cx="266" cy="-250" rx="30.59" ry="18"/> -<text text-anchor="middle" x="266" y="-246.3" font-family="Times,serif" font-size="14.00">.b =</text> -</g> -<!-- Store13->Start2 --> -<g id="edge3" class="edge"> -<title>Store13->Start2:p3</title> -<path fill="none" stroke="blue" d="M259.43,-267.92C251.04,-290.96 237.36,-333.9 235.27,-373.91"/> -<polygon fill="blue" stroke="blue" points="231.77,-373.91 235,-384 238.77,-374.1 231.77,-373.91"/> -<text text-anchor="middle" x="256.43" y="-270.52" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> -</g> -<!-- Store13->new10 --> -<g id="edge4" class="edge"> -<title>Store13->new10</title> -<path fill="none" stroke="black" d="M248.42,-264.83C235.97,-274.62 219.01,-287.96 204.75,-299.17"/> -<polygon fill="black" stroke="black" points="202.41,-296.56 196.72,-305.49 206.74,-302.06 202.41,-296.56"/> -<text text-anchor="middle" x="245.42" y="-267.43" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> -</g> -<!-- Store13->Con_11 --> -<g id="edge5" class="edge"> -<title>Store13->Con_11</title> -<path fill="none" stroke="black" d="M272.05,-267.95C274.91,-275.94 278.38,-285.67 281.57,-294.6"/> -<polygon fill="black" stroke="black" points="278.34,-295.97 285,-304.21 284.94,-293.62 278.34,-295.97"/> -<text text-anchor="middle" x="269.05" y="-270.55" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> -</g> -<!-- Con_16 --> -<g id="node6" class="node"> -<title>Con_16</title> -<ellipse fill="none" stroke="black" cx="83" cy="-322" rx="27" ry="18"/> -<text text-anchor="middle" x="83" y="-318.3" font-family="Times,serif" font-size="14.00">#2</text> -</g> -<!-- Store17 --> -<g id="node7" class="node"> -<title>Store17</title> -<ellipse fill="none" stroke="black" cx="74" cy="-250" rx="29.8" ry="18"/> -<text text-anchor="middle" x="74" y="-246.3" font-family="Times,serif" font-size="14.00">.a =</text> -</g> -<!-- Store17->Start2 --> -<g id="edge6" class="edge"> -<title>Store17->Start2:p2</title> -<path fill="none" stroke="blue" d="M61.5,-266.67C47.91,-285.72 30.23,-318.07 47,-340 90.02,-396.26 197.33,-318.14 208.12,-373.87"/> -<polygon fill="blue" stroke="blue" points="204.65,-374.34 209,-384 211.62,-373.74 204.65,-374.34"/> -<text text-anchor="middle" x="58.5" y="-269.27" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> -</g> -<!-- Store17->new10 --> -<g id="edge7" class="edge"> -<title>Store17->new10</title> -<path fill="none" stroke="black" d="M93.37,-264.17C108.16,-274.22 128.83,-288.27 145.92,-299.88"/> -<polygon fill="black" stroke="black" points="144.25,-302.97 154.49,-305.7 148.18,-297.18 144.25,-302.97"/> -<text text-anchor="middle" x="90.37" y="-266.77" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> -</g> -<!-- Store17->Con_16 --> -<g id="edge8" class="edge"> -<title>Store17->Con_16</title> -<path fill="none" stroke="black" d="M76.22,-268.3C77.22,-276.02 78.41,-285.29 79.51,-293.89"/> -<polygon fill="black" stroke="black" points="76.05,-294.42 80.8,-303.9 83,-293.53 76.05,-294.42"/> -<text text-anchor="middle" x="73.22" y="-270.9" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> -</g> -<!-- Load18 --> -<g id="node8" class="node"> -<title>Load18</title> -<ellipse fill="none" stroke="black" cx="112" cy="-178" rx="27" ry="18"/> -<text text-anchor="middle" x="112" y="-174.3" font-family="Times,serif" font-size="14.00">.a</text> -</g> -<!-- Load18->new10 --> -<g id="edge10" class="edge"> -<title>Load18->new10</title> -<path fill="none" stroke="black" d="M119.58,-195.57C130.78,-220.02 151.82,-266 165.09,-294.98"/> -<polygon fill="black" stroke="black" points="161.92,-296.46 169.26,-304.1 168.28,-293.55 161.92,-296.46"/> -<text text-anchor="middle" x="116.58" y="-198.17" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> -</g> -<!-- Load18->Store17 --> -<g id="edge9" class="edge"> -<title>Load18->Store17</title> -<path fill="none" stroke="blue" d="M103.19,-195.24C98.65,-203.6 93.01,-213.98 87.9,-223.39"/> -<polygon fill="blue" stroke="blue" points="84.72,-221.91 83.03,-232.37 90.88,-225.25 84.72,-221.91"/> -<text text-anchor="middle" x="100.19" y="-197.84" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> -</g> -<!-- Return19->Start2 --> -<g id="edge11" class="edge"> -<title>Return19->Start2:p0</title> -<path fill="none" stroke="red" d="M78.92,-118.71C54.82,-129.48 25.06,-148.19 17,-177"/> -<path fill="none" stroke="red" d="M17,-179C-2.42,-248.44 -5.52,-281.77 37,-340 66.51,-380.41 90.13,-391.98 133.12,-394.44"/> -<polygon fill="red" stroke="red" points="133.09,-397.94 143.22,-394.85 133.37,-390.94 133.09,-397.94"/> -<text text-anchor="middle" x="75.92" y="-112.31" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> -</g> -<!-- Return19->Start2 --> -<g id="edge15" class="edge"> -<title>Return19->Start2:p4</title> -<path fill="none" stroke="blue" d="M337,-179C373.1,-264.02 373.34,-375.99 294.56,-392.84"/> -<polygon fill="blue" stroke="blue" points="293.9,-389.4 284.56,-394.41 294.99,-396.32 293.9,-389.4"/> -<text text-anchor="middle" x="334" y="-172.6" font-family="Helvetica,sans-Serif" font-size="8.00">4</text> -</g> -<!-- Return19->Store13 --> -<g id="edge14" class="edge"> -<title>Return19->Store13</title> -<path fill="none" stroke="blue" d="M145.2,-111.29C204,-119.77 321.73,-141.04 337,-177"/> -<path fill="none" stroke="blue" d="M337,-179C346.91,-202.35 322.21,-221.91 299.27,-234.36"/> -<polygon fill="blue" stroke="blue" points="297.63,-231.27 290.32,-238.94 300.82,-237.5 297.63,-231.27"/> -<text text-anchor="middle" x="148.2" y="-104.89" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> -</g> -<!-- Return19->Store17 --> -<g id="edge13" class="edge"> -<title>Return19->Store17</title> -<path fill="none" stroke="blue" d="M17,-179C11.2,-199.75 28.07,-218.41 44.94,-231.23"/> -<polygon fill="blue" stroke="blue" points="42.94,-234.1 53.12,-237.04 46.99,-228.4 42.94,-234.1"/> -<text text-anchor="middle" x="14" y="-172.6" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> -</g> -<!-- Return19->Load18 --> -<g id="edge12" class="edge"> -<title>Return19->Load18</title> -<path fill="none" stroke="black" d="M112,-124.3C112,-132.02 112,-141.29 112,-149.89"/> -<polygon fill="black" stroke="black" points="108.5,-149.9 112,-159.9 115.5,-149.9 108.5,-149.9"/> -<text text-anchor="middle" x="109" y="-126.9" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> -</g> -</g> -</svg> diff --git a/chapter10/docs/example2c.gv b/chapter10/docs/example2c.gv deleted file mode 100644 index 76db03bb..00000000 --- a/chapter10/docs/example2c.gv +++ /dev/null @@ -1,53 +0,0 @@ -digraph chapter10 { -/* -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; - -*/ - rankdir=BT; - concentrate="true"; - compound="true"; - subgraph cluster_Nodes { - Con_16 [ label="#2" ]; - Store17 [ label=".a =" ]; - 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="p2">$2</TD><TD PORT="p3">$3</TD><TD PORT="p4">$4</TD></TR> - </TABLE> - </TD></TR> - </TABLE>> - ]; - Return19 [ shape=box style=filled fillcolor=yellow label="Return" ]; - Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; - new10 [ label="new Bar" ]; - Con_11 [ label="#0" ]; - Store13 [ label=".b =" ]; - } - edge [ fontname=Helvetica, fontsize=8 ]; - Store17 -> Start2:p2[taillabel=1 color=blue]; - Store17 -> new10[taillabel=2]; - Store17 -> Con_16[taillabel=3]; - Return19 -> Start2:p0[taillabel=0 color=red]; - Return19 -> Con_16[taillabel=1]; - Return19 -> Store17[taillabel=2 color=blue]; - Return19 -> Store13[taillabel=3 color=blue]; - Return19 -> Start2:p4[taillabel=4 color=blue]; - Stop3 -> Return19[taillabel=0 color=red]; - new10 -> Start2:p0[taillabel=0 color=green]; - Store13 -> Start2:p3[taillabel=1 color=blue]; - Store13 -> new10[taillabel=2]; - Store13 -> Con_11[taillabel=3]; -} diff --git a/chapter10/docs/example2c.svg b/chapter10/docs/example2c.svg deleted file mode 100644 index d70660a2..00000000 --- a/chapter10/docs/example2c.svg +++ /dev/null @@ -1,168 +0,0 @@ -<?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="362pt" height="392pt" - viewBox="0.00 0.00 362.00 392.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 388)"> -<title>chapter10</title> -<polygon fill="white" stroke="transparent" points="-4,4 -4,-388 358,-388 358,4 -4,4"/> -<g id="clust1" class="cluster"> -<title>cluster_Nodes</title> -<polygon fill="none" stroke="black" points="8,-8 8,-376 346,-376 346,-8 8,-8"/> -</g> -<!-- Con_16 --> -<g id="node1" class="node"> -<title>Con_16</title> -<ellipse fill="none" stroke="black" cx="63" cy="-250" rx="27" ry="18"/> -<text text-anchor="middle" x="63" y="-246.3" font-family="Times,serif" font-size="14.00">#2</text> -</g> -<!-- Store17 --> -<g id="node2" class="node"> -<title>Store17</title> -<ellipse fill="none" stroke="black" cx="130" cy="-178" rx="29.8" ry="18"/> -<text text-anchor="middle" x="130" y="-174.3" font-family="Times,serif" font-size="14.00">.a =</text> -</g> -<!-- Store17->Con_16 --> -<g id="edge3" class="edge"> -<title>Store17->Con_16</title> -<path fill="none" stroke="black" d="M115.79,-193.85C106.64,-203.4 94.58,-216.01 84.3,-226.75"/> -<polygon fill="black" stroke="black" points="81.57,-224.53 77.19,-234.18 86.63,-229.37 81.57,-224.53"/> -<text text-anchor="middle" x="112.79" y="-196.45" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> -</g> -<!-- Start2 --> -<g id="node3" class="node"> -<title>Start2</title> -<polygon fill="yellow" stroke="transparent" points="152,-339 152,-364 277,-364 277,-339 152,-339"/> -<polygon fill="none" stroke="black" points="152,-339 152,-364 277,-364 277,-339 152,-339"/> -<text text-anchor="start" x="196" y="-347.8" font-family="Times,serif" font-size="14.00">Start</text> -<polygon fill="none" stroke="black" points="152,-308 152,-339 277,-339 277,-308 152,-308"/> -<polygon fill="yellow" stroke="transparent" points="157,-313 157,-334 197,-334 197,-313 157,-313"/> -<polygon fill="none" stroke="black" points="157,-313 157,-334 197,-334 197,-313 157,-313"/> -<text text-anchor="start" x="160" y="-319.8" font-family="Times,serif" font-size="14.00">$ctrl</text> -<polygon fill="none" stroke="black" points="197,-313 197,-334 222,-334 222,-313 197,-313"/> -<text text-anchor="start" x="200" y="-319.8" font-family="Times,serif" font-size="14.00">$2</text> -<polygon fill="none" stroke="black" points="222,-313 222,-334 247,-334 247,-313 222,-313"/> -<text text-anchor="start" x="225" y="-319.8" font-family="Times,serif" font-size="14.00">$3</text> -<polygon fill="none" stroke="black" points="247,-313 247,-334 272,-334 272,-313 247,-313"/> -<text text-anchor="start" x="250" y="-319.8" font-family="Times,serif" font-size="14.00">$4</text> -</g> -<!-- Store17->Start2 --> -<g id="edge1" class="edge"> -<title>Store17->Start2:p2</title> -<path fill="none" stroke="blue" d="M122.08,-195.45C114.22,-214.42 105.15,-245.81 119,-268 140.46,-302.38 197.41,-273.74 207.47,-301.89"/> -<polygon fill="blue" stroke="blue" points="204.04,-302.64 209,-312 210.97,-301.59 204.04,-302.64"/> -<text text-anchor="middle" x="119.08" y="-198.05" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> -</g> -<!-- new10 --> -<g id="node6" class="node"> -<title>new10</title> -<ellipse fill="none" stroke="black" cx="177" cy="-250" rx="49.29" ry="18"/> -<text text-anchor="middle" x="177" y="-246.3" font-family="Times,serif" font-size="14.00">new Bar</text> -</g> -<!-- Store17->new10 --> -<g id="edge2" class="edge"> -<title>Store17->new10</title> -<path fill="none" stroke="black" d="M140.67,-194.88C146.39,-203.41 153.58,-214.11 160.05,-223.75"/> -<polygon fill="black" stroke="black" points="157.24,-225.85 165.72,-232.2 163.05,-221.95 157.24,-225.85"/> -<text text-anchor="middle" x="137.67" y="-197.48" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> -</g> -<!-- Return19 --> -<g id="node4" class="node"> -<title>Return19</title> -<polygon fill="yellow" stroke="black" points="223,-124 157,-124 157,-88 223,-88 223,-124"/> -<text text-anchor="middle" x="190" y="-102.3" font-family="Times,serif" font-size="14.00">Return</text> -</g> -<!-- Return19->Con_16 --> -<g id="edge5" class="edge"> -<title>Return19->Con_16</title> -<path fill="none" stroke="black" d="M17,-179C8.57,-197.2 22.58,-216.12 37.3,-229.69"/> -<polygon fill="black" stroke="black" points="35.08,-232.39 44.95,-236.26 39.65,-227.09 35.08,-232.39"/> -<text text-anchor="middle" x="14" y="-172.6" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> -</g> -<!-- Return19->Store17 --> -<g id="edge6" class="edge"> -<title>Return19->Store17</title> -<path fill="none" stroke="blue" d="M175.17,-124.3C167.47,-133.29 157.96,-144.38 149.66,-154.07"/> -<polygon fill="blue" stroke="blue" points="146.88,-151.93 143.03,-161.8 152.2,-156.48 146.88,-151.93"/> -<text text-anchor="middle" x="172.17" y="-126.9" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> -</g> -<!-- Return19->Start2 --> -<g id="edge4" class="edge"> -<title>Return19->Start2:p0</title> -<path fill="none" stroke="red" d="M156.89,-108.08C113.74,-111.57 40.96,-125.25 17,-177"/> -<path fill="none" stroke="red" d="M17,-179C-17.13,-252.71 52.8,-311.87 132.92,-321.6"/> -<polygon fill="red" stroke="red" points="132.97,-325.12 143.25,-322.57 133.63,-318.15 132.97,-325.12"/> -<text text-anchor="middle" x="153.89" y="-101.68" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> -</g> -<!-- Return19->Start2 --> -<g id="edge8" class="edge"> -<title>Return19->Start2:p4</title> -<path fill="none" stroke="blue" d="M223.46,-117.39C245.19,-125.72 272.61,-139.46 290,-160 334.77,-212.87 344.89,-312.63 283.09,-322.25"/> -<polygon fill="blue" stroke="blue" points="282.71,-318.77 273,-323 283.23,-325.75 282.71,-318.77"/> -<text text-anchor="middle" x="226.46" y="-110.99" font-family="Helvetica,sans-Serif" font-size="8.00">4</text> -</g> -<!-- Store13 --> -<g id="node8" class="node"> -<title>Store13</title> -<ellipse fill="none" stroke="black" cx="250" cy="-178" rx="30.59" ry="18"/> -<text text-anchor="middle" x="250" y="-174.3" font-family="Times,serif" font-size="14.00">.b =</text> -</g> -<!-- Return19->Store13 --> -<g id="edge7" class="edge"> -<title>Return19->Store13</title> -<path fill="none" stroke="blue" d="M204.83,-124.3C212.42,-133.16 221.77,-144.07 229.99,-153.66"/> -<polygon fill="blue" stroke="blue" points="227.4,-156.01 236.57,-161.33 232.72,-151.46 227.4,-156.01"/> -<text text-anchor="middle" x="201.83" y="-126.9" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> -</g> -<!-- Stop3 --> -<g id="node5" class="node"> -<title>Stop3</title> -<polygon fill="yellow" stroke="black" points="217,-52 163,-52 163,-16 217,-16 217,-52"/> -<text text-anchor="middle" x="190" y="-30.3" font-family="Times,serif" font-size="14.00">Stop</text> -</g> -<!-- Stop3->Return19 --> -<g id="edge9" class="edge"> -<title>Stop3->Return19</title> -<path fill="none" stroke="red" d="M190,-52.3C190,-60.02 190,-69.29 190,-77.89"/> -<polygon fill="red" stroke="red" points="186.5,-77.9 190,-87.9 193.5,-77.9 186.5,-77.9"/> -<text text-anchor="middle" x="187" y="-54.9" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> -</g> -<!-- new10->Start2 --> -<g id="edge10" class="edge"> -<title>new10->Start2:p0</title> -<path fill="none" stroke="green" d="M177,-268.09C177,-277.55 177,-289.77 177,-301.69"/> -<polygon fill="green" stroke="green" points="173.5,-302 177,-312 180.5,-302 173.5,-302"/> -<text text-anchor="middle" x="174" y="-270.69" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> -</g> -<!-- Con_11 --> -<g id="node7" class="node"> -<title>Con_11</title> -<ellipse fill="none" stroke="black" cx="271" cy="-250" rx="27" ry="18"/> -<text text-anchor="middle" x="271" y="-246.3" font-family="Times,serif" font-size="14.00">#0</text> -</g> -<!-- Store13->Start2 --> -<g id="edge11" class="edge"> -<title>Store13->Start2:p3</title> -<path fill="none" stroke="blue" d="M270.42,-191.5C283.57,-200.84 299.55,-214.9 307,-232 313.39,-246.67 314.83,-254.04 307,-268 290.59,-297.26 245.93,-279.37 236.69,-302.12"/> -<polygon fill="blue" stroke="blue" points="233.23,-301.55 235,-312 240.13,-302.73 233.23,-301.55"/> -<text text-anchor="middle" x="267.42" y="-194.1" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> -</g> -<!-- Store13->new10 --> -<g id="edge12" class="edge"> -<title>Store13->new10</title> -<path fill="none" stroke="black" d="M234.52,-193.85C224.79,-203.17 212.05,-215.39 201.03,-225.96"/> -<polygon fill="black" stroke="black" points="198.53,-223.51 193.73,-232.96 203.37,-228.56 198.53,-223.51"/> -<text text-anchor="middle" x="231.52" y="-196.45" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> -</g> -<!-- Store13->Con_11 --> -<g id="edge13" class="edge"> -<title>Store13->Con_11</title> -<path fill="none" stroke="black" d="M255.08,-195.95C257.48,-203.94 260.4,-213.67 263.08,-222.6"/> -<polygon fill="black" stroke="black" points="259.74,-223.64 265.96,-232.21 266.44,-221.63 259.74,-223.64"/> -<text text-anchor="middle" x="252.08" y="-198.55" 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 deleted file mode 100644 index cf20e9f3..00000000 --- a/chapter10/docs/lattice.drawio +++ /dev/null @@ -1,379 +0,0 @@ -<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<br>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<br>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 deleted file mode 100644 index 7723e2a2..00000000 --- a/chapter10/docs/lattice.gv +++ /dev/null @@ -1,161 +0,0 @@ -digraph Lattice { - compound = true; - // Define nodes - splines=false; - node [shape=circle, style=filled]; - nodesep=0.6; - - TOP [label=<<B>⊤</B >>, fillcolor=black, fontcolor=white]; - BOT [label=<<B>⊥</B>>, fillcolor=black, fontcolor=white]; - - // ---------------------- - // Control - XCTRL [label=<<B>⊤</B >:ctrl>, fillcolor=yellow, shape=oval]; - CTRL [label=<<B>⊥</B>:ctrl>, fillcolor=yellow, shape=oval]; - - TOP -> XCTRL; - XCTRL -> CTRL; - CTRL -> BOT; - - // ---------------------- - // Integers - INT_TOP [label=<<B>⊤</B >:int>, fillcolor=lightblue, shape=oval]; - INT_BOT [label=<<B>⊥</B>:int>, fillcolor=lightblue, shape=oval]; - - INT_MINDOTS [label="...", fillcolor=lightblue, shape=oval]; - INT_ZERO [label="0" , fillcolor=lightblue]; - INT_MAXDOTS [label="...", fillcolor=lightblue, shape=oval]; - - TOP -> INT_TOP; - INT_TOP -> INT_MINDOTS; - INT_TOP -> INT_ZERO; - INT_TOP -> INT_MAXDOTS; - INT_MINDOTS -> INT_BOT; - INT_ZERO -> INT_BOT; - INT_MAXDOTS -> INT_BOT; - INT_BOT -> BOT; - - INT_MINDOTS -> INT_ZERO -> INT_MAXDOTS [style=invis]; - - // ---------------------- - // Pointers, just a boolean nil/not and a struct - PTR [shape=none, fillcolor=none, label=< - <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> - <TR><TD BGCOLOR="green"><FONT color="white">*ptr</FONT></TD></TR> - <TR><TD BGCOLOR="lightgreen">struct</TD></TR> - </TABLE> - >]; - PTR0 [shape=none, fillcolor=none, label=< - <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> - <TR><TD BGCOLOR="green"><FONT color="white">*ptr?</FONT></TD></TR> - <TR><TD BGCOLOR="lightgreen">struct</TD></TR> - </TABLE> - >]; - - TOP -> PTR; - PTR -> PTR0; - PTR0 -> BOT; - - // ---------------------- - // Structs - STRUCT_TOP [label=<<B>⊤</B >:struct>, fillcolor=lightgreen, shape=oval]; - STRUCT_BOT [label=<<B>⊥</B>:struct>, fillcolor=lightgreen, shape=oval]; - - STRUCT_S1 [shape=none, fillcolor=none, label=< - <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> - <TR><TD BGCOLOR="lightgreen">field</TD><TD BGCOLOR="lightblue:green" gradientangle="315">int/ptr</TD></TR> - </TABLE> - >]; - STRUCT_S2 [shape=none, fillcolor=none, label=< - <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> - <TR><TD BGCOLOR="lightgreen">field</TD><TD BGCOLOR="lightblue:green" gradientangle="315">int/ptr</TD></TR> - <TR><TD BGCOLOR="lightgreen">field</TD><TD BGCOLOR="lightblue:green" gradientangle="315">int/ptr</TD></TR> - </TABLE> - >]; - STRUCT_SN [shape=none, fillcolor=none, label=< - <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> - <TR><TD BGCOLOR="lightgreen">field</TD><TD BGCOLOR="lightblue:green" gradientangle="315">int/ptr</TD></TR> - <TR><TD BGCOLOR="lightgreen">... </TD><TD BGCOLOR="lightblue:green" gradientangle="315">int/ptr</TD></TR> - <TR><TD BGCOLOR="lightgreen">field</TD><TD BGCOLOR="lightblue:green" gradientangle="315">int/ptr</TD></TR> - </TABLE> - >]; - - - TOP -> STRUCT_TOP; - STRUCT_TOP -> STRUCT_S1; - STRUCT_TOP -> STRUCT_S2; - STRUCT_TOP -> STRUCT_SN; - STRUCT_S1 -> STRUCT_BOT; - STRUCT_S2 -> STRUCT_BOT; - STRUCT_SN -> STRUCT_BOT; - STRUCT_BOT -> BOT; - - STRUCT_S1 -> STRUCT_S2 -> STRUCT_SN [style=invis]; - - // ---------------------- - // Memory - MEM_TOP [label=<<B>⊤</B >:mem>, fillcolor=blue, fontcolor=white, shape=oval]; - MEM_BOT [label=<<B>⊥</B>:mem>, fillcolor=blue, fontcolor=white, shape=oval]; - MEM_ALIAS1 [label="#1" , fillcolor=blue, fontcolor=white]; - MEM_ALIASDOTS [label="..." , fillcolor=blue, fontcolor=white, shape=oval]; - MEM_ALIASN [label="#n" , fillcolor=blue, fontcolor=white]; - - TOP -> MEM_TOP; - MEM_TOP -> MEM_ALIAS1; - MEM_TOP -> MEM_ALIASDOTS; - MEM_TOP -> MEM_ALIASN; - MEM_ALIAS1 -> MEM_BOT; - MEM_ALIASDOTS -> MEM_BOT; - MEM_ALIASN -> MEM_BOT; - MEM_BOT -> BOT; - - MEM_ALIAS1 -> MEM_ALIASDOTS -> MEM_ALIASN [style=invis]; - - // ---------------------- - // Tuples - - TUPLE_TOP [label=<<B>⊤</B >:tuple>, fillcolor=pink, shape=oval]; - TUPLE_BOT [label=<<B>⊥</B>:tuple>, fillcolor=pink, shape=oval]; - - TUPLE1 [shape=none, fillcolor=none, label=< - <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> - <TH><TD BGCOLOR="pink">Tuple</TD></TH> - <TR><TD BGCOLOR="black" ><FONT color="white">type</FONT></TD></TR> - </TABLE> - >]; - TUPLE2 [shape=none, fillcolor=none, label=< - <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> - <TH><TD BGCOLOR="pink">Tuple</TD></TH> - <TR><TD BGCOLOR="black" ><FONT color="white">type</FONT></TD></TR> - <TR><TD BGCOLOR="black" ><FONT color="white">type</FONT></TD></TR> - </TABLE> - >]; - TUPLEN [shape=none, fillcolor=none, label=< - <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0"> - <TH><TD BGCOLOR="pink">Tuple</TD></TH> - <TR><TD BGCOLOR="black" ><FONT color="white">type</FONT></TD></TR> - <TR><TD BGCOLOR="black" ><FONT color="white">...</FONT></TD></TR> - <TR><TD BGCOLOR="black" ><FONT color="white">type</FONT></TD></TR> - </TABLE> - >]; - - - TOP -> TUPLE_TOP; - TUPLE_TOP -> TUPLE1; - TUPLE_TOP -> TUPLE2; - TUPLE_TOP -> TUPLEN; - TUPLE1 -> TUPLE_BOT; - TUPLE2 -> TUPLE_BOT; - TUPLEN -> TUPLE_BOT; - TUPLE_BOT -> BOT; - - // ---------------------- - // Additional customization - {rank=same; TOP;} - {rank=same; INT_TOP; XCTRL; STRUCT_TOP; MEM_TOP; TUPLE_TOP} - {rank=same; INT_MINDOTS; INT_ZERO; INT_MAXDOTS; - STRUCT_S1; STRUCT_S2; STRUCT_SN; - MEM_ALIAS1; MEM_ALIASDOTS; MEM_ALIASN;} - {rank=same; INT_BOT; CTRL; STRUCT_BOT; MEM_BOT; TUPLE_BOT} - {rank=same; BOT;} -} diff --git a/chapter10/docs/lattice.svg b/chapter10/docs/lattice.svg deleted file mode 100644 index 9eb36bbe..00000000 --- a/chapter10/docs/lattice.svg +++ /dev/null @@ -1,516 +0,0 @@ -<?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 12.2.0 (20241103.1931)
- -->
-<!-- Title: Lattice Pages: 1 -->
-<svg width="1308pt" height="440pt"
- viewBox="0.00 0.00 1307.51 440.50" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 436.5)">
-<title>Lattice</title>
-<polygon fill="white" stroke="none" points="-4,4 -4,-436.5 1303.51,-436.5 1303.51,4 -4,4"/>
-<!-- TOP -->
-<g id="node1" class="node">
-<title>TOP</title>
-<ellipse fill="black" stroke="black" cx="478.51" cy="-411.37" rx="21.12" ry="21.12"/>
-<text text-anchor="start" x="470.63" y="-406.95" font-family="Times New Roman,serif" font-weight="bold" font-size="14.00" fill="white">⊤</text>
-</g>
-<!-- XCTRL -->
-<g id="node3" class="node">
-<title>XCTRL</title>
-<ellipse fill="yellow" stroke="black" cx="36.51" cy="-327.75" rx="36.51" ry="18"/>
-<text text-anchor="start" x="17.76" y="-323.7" font-family="Times New Roman,serif" font-weight="bold" font-size="14.00">⊤</text>
-<text text-anchor="start" x="33.51" y="-323.7" font-family="Times New Roman,serif" font-size="14.00">:ctrl</text>
-</g>
-<!-- TOP->XCTRL -->
-<g id="edge1" class="edge">
-<title>TOP->XCTRL</title>
-<path fill="none" stroke="black" d="M457.49,-406.49C389.63,-393.96 175.41,-354.4 81.44,-337.04"/>
-<polygon fill="black" stroke="black" points="82.28,-333.64 71.81,-335.27 81.01,-340.53 82.28,-333.64"/>
-</g>
-<!-- INT_TOP -->
-<g id="node5" class="node">
-<title>INT_TOP</title>
-<ellipse fill="lightblue" stroke="black" cx="220.51" cy="-327.75" rx="33.95" ry="18"/>
-<text text-anchor="start" x="203.63" y="-323.7" font-family="Times New Roman,serif" font-weight="bold" font-size="14.00">⊤</text>
-<text text-anchor="start" x="219.38" y="-323.7" font-family="Times New Roman,serif" font-size="14.00">:int</text>
-</g>
-<!-- TOP->INT_TOP -->
-<g id="edge4" class="edge">
-<title>TOP->INT_TOP</title>
-<path fill="none" stroke="black" d="M458.26,-403.97C415.59,-390.47 315.75,-358.88 259.97,-341.23"/>
-<polygon fill="black" stroke="black" points="261.14,-337.93 250.55,-338.25 259.03,-344.61 261.14,-337.93"/>
-</g>
-<!-- PTR -->
-<g id="node10" class="node">
-<title>PTR</title>
-<polygon fill="none" stroke="none" points="387.51,-354.25 333.51,-354.25 333.51,-301.25 387.51,-301.25 387.51,-354.25"/>
-<polygon fill="green" stroke="none" points="342.51,-327.75 342.51,-350.25 378.51,-350.25 378.51,-327.75 342.51,-327.75"/>
-<polygon fill="none" stroke="black" points="342.51,-327.75 342.51,-350.25 378.51,-350.25 378.51,-327.75 342.51,-327.75"/>
-<text text-anchor="start" x="349.63" y="-333.95" font-family="Times New Roman,serif" font-size="14.00" fill="white">*ptr</text>
-<polygon fill="lightgreen" stroke="none" points="342.51,-305.25 342.51,-327.75 378.51,-327.75 378.51,-305.25 342.51,-305.25"/>
-<polygon fill="none" stroke="black" points="342.51,-305.25 342.51,-327.75 378.51,-327.75 378.51,-305.25 342.51,-305.25"/>
-<text text-anchor="start" x="345.51" y="-311.45" font-family="Times New Roman,serif" font-size="14.00">struct</text>
-</g>
-<!-- TOP->PTR -->
-<g id="edge14" class="edge">
-<title>TOP->PTR</title>
-<path fill="none" stroke="black" d="M461.35,-398.5C444.42,-386.79 418.03,-368.54 396.49,-353.64"/>
-<polygon fill="black" stroke="black" points="398.75,-350.95 388.54,-348.14 394.77,-356.7 398.75,-350.95"/>
-</g>
-<!-- STRUCT_TOP -->
-<g id="node12" class="node">
-<title>STRUCT_TOP</title>
-<ellipse fill="lightgreen" stroke="black" cx="514.51" cy="-327.75" rx="44.7" ry="18"/>
-<text text-anchor="start" x="489.76" y="-323.7" font-family="Times New Roman,serif" font-weight="bold" font-size="14.00">⊤</text>
-<text text-anchor="start" x="505.51" y="-323.7" font-family="Times New Roman,serif" font-size="14.00">:struct</text>
-</g>
-<!-- TOP->STRUCT_TOP -->
-<g id="edge17" class="edge">
-<title>TOP->STRUCT_TOP</title>
-<path fill="none" stroke="black" d="M486.86,-391.44C491.45,-381.02 497.24,-367.9 502.33,-356.35"/>
-<polygon fill="black" stroke="black" points="505.5,-357.86 506.33,-347.29 499.09,-355.03 505.5,-357.86"/>
-</g>
-<!-- MEM_TOP -->
-<g id="node17" class="node">
-<title>MEM_TOP</title>
-<ellipse fill="blue" stroke="black" cx="853.51" cy="-327.75" rx="43.67" ry="18"/>
-<text text-anchor="start" x="829.51" y="-323.7" font-family="Times New Roman,serif" font-weight="bold" font-size="14.00" fill="white">⊤</text>
-<text text-anchor="start" x="845.26" y="-323.7" font-family="Times New Roman,serif" font-size="14.00" fill="white">:mem</text>
-</g>
-<!-- TOP->MEM_TOP -->
-<g id="edge27" class="edge">
-<title>TOP->MEM_TOP</title>
-<path fill="none" stroke="black" d="M499.2,-405.87C556.98,-393.29 721.22,-357.54 804.21,-339.48"/>
-<polygon fill="black" stroke="black" points="804.86,-342.92 813.89,-337.37 803.37,-336.08 804.86,-342.92"/>
-</g>
-<!-- TUPLE_TOP -->
-<g id="node22" class="node">
-<title>TUPLE_TOP</title>
-<ellipse fill="pink" stroke="black" cx="1126.51" cy="-327.75" rx="42.65" ry="18"/>
-<text text-anchor="start" x="1103.26" y="-323.7" font-family="Times New Roman,serif" font-weight="bold" font-size="14.00">⊤</text>
-<text text-anchor="start" x="1119.01" y="-323.7" font-family="Times New Roman,serif" font-size="14.00">:tuple</text>
-</g>
-<!-- TOP->TUPLE_TOP -->
-<g id="edge37" class="edge">
-<title>TOP->TUPLE_TOP</title>
-<path fill="none" stroke="black" d="M499.7,-407.7C589.67,-396.37 941.49,-352.05 1074.87,-335.25"/>
-<polygon fill="black" stroke="black" points="1074.97,-338.77 1084.46,-334.05 1074.1,-331.82 1074.97,-338.77"/>
-</g>
-<!-- BOT -->
-<g id="node2" class="node">
-<title>BOT</title>
-<ellipse fill="black" stroke="black" cx="448.51" cy="-21.12" rx="21.12" ry="21.12"/>
-<text text-anchor="start" x="440.63" y="-16.7" font-family="Times New Roman,serif" font-weight="bold" font-size="14.00" fill="white">⊥</text>
-</g>
-<!-- CTRL -->
-<g id="node4" class="node">
-<title>CTRL</title>
-<ellipse fill="yellow" stroke="black" cx="36.51" cy="-104.75" rx="36.51" ry="18"/>
-<text text-anchor="start" x="17.76" y="-100.7" font-family="Times New Roman,serif" font-weight="bold" font-size="14.00">⊥</text>
-<text text-anchor="start" x="33.51" y="-100.7" font-family="Times New Roman,serif" font-size="14.00">:ctrl</text>
-</g>
-<!-- XCTRL->CTRL -->
-<g id="edge2" class="edge">
-<title>XCTRL->CTRL</title>
-<path fill="none" stroke="black" d="M36.51,-309.5C36.51,-271.02 36.51,-181.25 36.51,-134.4"/>
-<polygon fill="black" stroke="black" points="40.01,-134.45 36.51,-124.45 33.01,-134.45 40.01,-134.45"/>
-</g>
-<!-- CTRL->BOT -->
-<g id="edge3" class="edge">
-<title>CTRL->BOT</title>
-<path fill="none" stroke="black" d="M69.88,-97.14C147.7,-81.72 340.42,-43.54 416.69,-28.43"/>
-<polygon fill="black" stroke="black" points="417.29,-31.88 426.41,-26.5 415.93,-25.01 417.29,-31.88"/>
-</g>
-<!-- INT_MINDOTS -->
-<g id="node7" class="node">
-<title>INT_MINDOTS</title>
-<ellipse fill="lightblue" stroke="black" cx="68.51" cy="-216.25" rx="27" ry="18"/>
-<text text-anchor="middle" x="68.51" y="-211.2" font-family="Times New Roman,serif" font-size="14.00">...</text>
-</g>
-<!-- INT_TOP->INT_MINDOTS -->
-<g id="edge5" class="edge">
-<title>INT_TOP->INT_MINDOTS</title>
-<path fill="none" stroke="black" d="M200.95,-312.66C174.03,-293.27 125.48,-258.29 95.24,-236.5"/>
-<polygon fill="black" stroke="black" points="97.5,-233.82 87.34,-230.81 93.41,-239.5 97.5,-233.82"/>
-</g>
-<!-- INT_ZERO -->
-<g id="node8" class="node">
-<title>INT_ZERO</title>
-<ellipse fill="lightblue" stroke="black" cx="156.51" cy="-216.25" rx="18" ry="18"/>
-<text text-anchor="middle" x="156.51" y="-211.2" font-family="Times New Roman,serif" font-size="14.00">0</text>
-</g>
-<!-- INT_TOP->INT_ZERO -->
-<g id="edge6" class="edge">
-<title>INT_TOP->INT_ZERO</title>
-<path fill="none" stroke="black" d="M210.79,-310.12C200.2,-292 183.15,-262.82 170.98,-242.01"/>
-<polygon fill="black" stroke="black" points="174.12,-240.45 166.05,-233.58 168.08,-243.98 174.12,-240.45"/>
-</g>
-<!-- INT_MAXDOTS -->
-<g id="node9" class="node">
-<title>INT_MAXDOTS</title>
-<ellipse fill="lightblue" stroke="black" cx="244.51" cy="-216.25" rx="27" ry="18"/>
-<text text-anchor="middle" x="244.51" y="-211.2" font-family="Times New Roman,serif" font-size="14.00">...</text>
-</g>
-<!-- INT_TOP->INT_MAXDOTS -->
-<g id="edge7" class="edge">
-<title>INT_TOP->INT_MAXDOTS</title>
-<path fill="none" stroke="black" d="M224.26,-309.64C228.03,-292.44 233.9,-265.64 238.35,-245.33"/>
-<polygon fill="black" stroke="black" points="241.74,-246.23 240.46,-235.72 234.9,-244.73 241.74,-246.23"/>
-</g>
-<!-- INT_BOT -->
-<g id="node6" class="node">
-<title>INT_BOT</title>
-<ellipse fill="lightblue" stroke="black" cx="220.51" cy="-104.75" rx="33.95" ry="18"/>
-<text text-anchor="start" x="203.63" y="-100.7" font-family="Times New Roman,serif" font-weight="bold" font-size="14.00">⊥</text>
-<text text-anchor="start" x="219.38" y="-100.7" font-family="Times New Roman,serif" font-size="14.00">:int</text>
-</g>
-<!-- INT_BOT->BOT -->
-<g id="edge11" class="edge">
-<title>INT_BOT->BOT</title>
-<path fill="none" stroke="black" d="M248.01,-93.9C290.83,-78.57 373.15,-49.1 417.83,-33.11"/>
-<polygon fill="black" stroke="black" points="418.93,-36.43 427.17,-29.76 416.57,-29.84 418.93,-36.43"/>
-</g>
-<!-- INT_MINDOTS->INT_BOT -->
-<g id="edge8" class="edge">
-<title>INT_MINDOTS->INT_BOT</title>
-<path fill="none" stroke="black" d="M86.24,-202.47C112.22,-183.76 160.89,-148.7 191.95,-126.33"/>
-<polygon fill="black" stroke="black" points="193.68,-129.39 199.75,-120.71 189.59,-123.71 193.68,-129.39"/>
-</g>
-<!-- INT_MINDOTS->INT_ZERO -->
-<!-- INT_ZERO->INT_BOT -->
-<g id="edge9" class="edge">
-<title>INT_ZERO->INT_BOT</title>
-<path fill="none" stroke="black" d="M165.14,-200.48C175.34,-183.02 192.52,-153.64 205.04,-132.22"/>
-<polygon fill="black" stroke="black" points="207.97,-134.13 210,-123.73 201.93,-130.6 207.97,-134.13"/>
-</g>
-<!-- INT_ZERO->INT_MAXDOTS -->
-<!-- INT_MAXDOTS->INT_BOT -->
-<g id="edge10" class="edge">
-<title>INT_MAXDOTS->INT_BOT</title>
-<path fill="none" stroke="black" d="M240.76,-198.14C237.01,-181.04 231.18,-154.46 226.74,-134.19"/>
-<polygon fill="black" stroke="black" points="230.2,-133.6 224.64,-124.58 223.36,-135.09 230.2,-133.6"/>
-</g>
-<!-- PTR0 -->
-<g id="node11" class="node">
-<title>PTR0</title>
-<polygon fill="none" stroke="none" points="387.51,-131.25 333.51,-131.25 333.51,-78.25 387.51,-78.25 387.51,-131.25"/>
-<polygon fill="green" stroke="none" points="342.51,-104.75 342.51,-127.25 378.51,-127.25 378.51,-104.75 342.51,-104.75"/>
-<polygon fill="none" stroke="black" points="342.51,-104.75 342.51,-127.25 378.51,-127.25 378.51,-104.75 342.51,-104.75"/>
-<text text-anchor="start" x="346.63" y="-110.95" font-family="Times New Roman,serif" font-size="14.00" fill="white">*ptr?</text>
-<polygon fill="lightgreen" stroke="none" points="342.51,-82.25 342.51,-104.75 378.51,-104.75 378.51,-82.25 342.51,-82.25"/>
-<polygon fill="none" stroke="black" points="342.51,-82.25 342.51,-104.75 378.51,-104.75 378.51,-82.25 342.51,-82.25"/>
-<text text-anchor="start" x="345.51" y="-88.45" font-family="Times New Roman,serif" font-size="14.00">struct</text>
-</g>
-<!-- PTR->PTR0 -->
-<g id="edge15" class="edge">
-<title>PTR->PTR0</title>
-<path fill="none" stroke="black" d="M360.51,-301.29C360.51,-261.89 360.51,-187.62 360.51,-142.52"/>
-<polygon fill="black" stroke="black" points="364.01,-142.68 360.51,-132.68 357.01,-142.68 364.01,-142.68"/>
-</g>
-<!-- PTR0->BOT -->
-<g id="edge16" class="edge">
-<title>PTR0->BOT</title>
-<path fill="none" stroke="black" d="M387.39,-78.81C399.39,-67.68 413.41,-54.68 424.99,-43.94"/>
-<polygon fill="black" stroke="black" points="427.24,-46.63 432.19,-37.26 422.48,-41.5 427.24,-46.63"/>
-</g>
-<!-- STRUCT_S1 -->
-<g id="node14" class="node">
-<title>STRUCT_S1</title>
-<polygon fill="none" stroke="none" points="487.38,-234.25 401.63,-234.25 401.63,-198.25 487.38,-198.25 487.38,-234.25"/>
-<polygon fill="lightgreen" stroke="none" points="409.63,-205 409.63,-227.5 440.38,-227.5 440.38,-205 409.63,-205"/>
-<polygon fill="none" stroke="black" points="409.63,-205 409.63,-227.5 440.38,-227.5 440.38,-205 409.63,-205"/>
-<text text-anchor="start" x="412.63" y="-211.2" font-family="Times New Roman,serif" font-size="14.00">field</text>
-<defs>
-<linearGradient id="l_0" gradientUnits="userSpaceOnUse" x1="446.1" y1="-224.2" x2="473.67" y2="-208.29" >
-<stop offset="0" style="stop-color:lightblue;stop-opacity:1.;"/>
-<stop offset="1" style="stop-color:green;stop-opacity:1.;"/>
-</linearGradient>
-</defs>
-<polygon fill="url(#l_0)" stroke="none" points="440.38,-205 440.38,-227.5 479.38,-227.5 479.38,-205 440.38,-205"/>
-<polygon fill="none" stroke="black" points="440.38,-205 440.38,-227.5 479.38,-227.5 479.38,-205 440.38,-205"/>
-<text text-anchor="start" x="443.38" y="-211.2" font-family="Times New Roman,serif" font-size="14.00">int/ptr</text>
-</g>
-<!-- STRUCT_TOP->STRUCT_S1 -->
-<g id="edge18" class="edge">
-<title>STRUCT_TOP->STRUCT_S1</title>
-<path fill="none" stroke="black" d="M503.88,-310.12C492.57,-292.43 474.53,-264.21 461.3,-243.51"/>
-<polygon fill="black" stroke="black" points="464.39,-241.85 456.06,-235.31 458.49,-245.62 464.39,-241.85"/>
-</g>
-<!-- STRUCT_S2 -->
-<g id="node15" class="node">
-<title>STRUCT_S2</title>
-<polygon fill="none" stroke="none" points="616.38,-242.75 530.63,-242.75 530.63,-189.75 616.38,-189.75 616.38,-242.75"/>
-<polygon fill="lightgreen" stroke="none" points="538.63,-216.25 538.63,-238.75 569.38,-238.75 569.38,-216.25 538.63,-216.25"/>
-<polygon fill="none" stroke="black" points="538.63,-216.25 538.63,-238.75 569.38,-238.75 569.38,-216.25 538.63,-216.25"/>
-<text text-anchor="start" x="541.63" y="-222.45" font-family="Times New Roman,serif" font-size="14.00">field</text>
-<defs>
-<linearGradient id="l_1" gradientUnits="userSpaceOnUse" x1="575.1" y1="-235.45" x2="602.67" y2="-219.54" >
-<stop offset="0" style="stop-color:lightblue;stop-opacity:1.;"/>
-<stop offset="1" style="stop-color:green;stop-opacity:1.;"/>
-</linearGradient>
-</defs>
-<polygon fill="url(#l_1)" stroke="none" points="569.38,-216.25 569.38,-238.75 608.38,-238.75 608.38,-216.25 569.38,-216.25"/>
-<polygon fill="none" stroke="black" points="569.38,-216.25 569.38,-238.75 608.38,-238.75 608.38,-216.25 569.38,-216.25"/>
-<text text-anchor="start" x="572.38" y="-222.45" font-family="Times New Roman,serif" font-size="14.00">int/ptr</text>
-<polygon fill="lightgreen" stroke="none" points="538.63,-193.75 538.63,-216.25 569.38,-216.25 569.38,-193.75 538.63,-193.75"/>
-<polygon fill="none" stroke="black" points="538.63,-193.75 538.63,-216.25 569.38,-216.25 569.38,-193.75 538.63,-193.75"/>
-<text text-anchor="start" x="541.63" y="-199.95" font-family="Times New Roman,serif" font-size="14.00">field</text>
-<defs>
-<linearGradient id="l_2" gradientUnits="userSpaceOnUse" x1="575.1" y1="-212.95" x2="602.67" y2="-197.04" >
-<stop offset="0" style="stop-color:lightblue;stop-opacity:1.;"/>
-<stop offset="1" style="stop-color:green;stop-opacity:1.;"/>
-</linearGradient>
-</defs>
-<polygon fill="url(#l_2)" stroke="none" points="569.38,-193.75 569.38,-216.25 608.38,-216.25 608.38,-193.75 569.38,-193.75"/>
-<polygon fill="none" stroke="black" points="569.38,-193.75 569.38,-216.25 608.38,-216.25 608.38,-193.75 569.38,-193.75"/>
-<text text-anchor="start" x="572.38" y="-199.95" font-family="Times New Roman,serif" font-size="14.00">int/ptr</text>
-</g>
-<!-- STRUCT_TOP->STRUCT_S2 -->
-<g id="edge19" class="edge">
-<title>STRUCT_TOP->STRUCT_S2</title>
-<path fill="none" stroke="black" d="M523.73,-309.64C531.97,-294.34 544.3,-271.45 554.63,-252.28"/>
-<polygon fill="black" stroke="black" points="557.59,-254.17 559.26,-243.7 551.43,-250.85 557.59,-254.17"/>
-</g>
-<!-- STRUCT_SN -->
-<g id="node16" class="node">
-<title>STRUCT_SN</title>
-<polygon fill="none" stroke="none" points="745.38,-254 659.63,-254 659.63,-178.5 745.38,-178.5 745.38,-254"/>
-<polygon fill="lightgreen" stroke="none" points="667.63,-227.5 667.63,-250 698.38,-250 698.38,-227.5 667.63,-227.5"/>
-<polygon fill="none" stroke="black" points="667.63,-227.5 667.63,-250 698.38,-250 698.38,-227.5 667.63,-227.5"/>
-<text text-anchor="start" x="670.63" y="-233.7" font-family="Times New Roman,serif" font-size="14.00">field</text>
-<defs>
-<linearGradient id="l_3" gradientUnits="userSpaceOnUse" x1="704.1" y1="-246.7" x2="731.67" y2="-230.79" >
-<stop offset="0" style="stop-color:lightblue;stop-opacity:1.;"/>
-<stop offset="1" style="stop-color:green;stop-opacity:1.;"/>
-</linearGradient>
-</defs>
-<polygon fill="url(#l_3)" stroke="none" points="698.38,-227.5 698.38,-250 737.38,-250 737.38,-227.5 698.38,-227.5"/>
-<polygon fill="none" stroke="black" points="698.38,-227.5 698.38,-250 737.38,-250 737.38,-227.5 698.38,-227.5"/>
-<text text-anchor="start" x="701.38" y="-233.7" font-family="Times New Roman,serif" font-size="14.00">int/ptr</text>
-<polygon fill="lightgreen" stroke="none" points="667.63,-205 667.63,-227.5 698.38,-227.5 698.38,-205 667.63,-205"/>
-<polygon fill="none" stroke="black" points="667.63,-205 667.63,-227.5 698.38,-227.5 698.38,-205 667.63,-205"/>
-<text text-anchor="start" x="673.63" y="-211.2" font-family="Times New Roman,serif" font-size="14.00">...  </text>
-<defs>
-<linearGradient id="l_4" gradientUnits="userSpaceOnUse" x1="704.1" y1="-224.2" x2="731.67" y2="-208.29" >
-<stop offset="0" style="stop-color:lightblue;stop-opacity:1.;"/>
-<stop offset="1" style="stop-color:green;stop-opacity:1.;"/>
-</linearGradient>
-</defs>
-<polygon fill="url(#l_4)" stroke="none" points="698.38,-205 698.38,-227.5 737.38,-227.5 737.38,-205 698.38,-205"/>
-<polygon fill="none" stroke="black" points="698.38,-205 698.38,-227.5 737.38,-227.5 737.38,-205 698.38,-205"/>
-<text text-anchor="start" x="701.38" y="-211.2" font-family="Times New Roman,serif" font-size="14.00">int/ptr</text>
-<polygon fill="lightgreen" stroke="none" points="667.63,-182.5 667.63,-205 698.38,-205 698.38,-182.5 667.63,-182.5"/>
-<polygon fill="none" stroke="black" points="667.63,-182.5 667.63,-205 698.38,-205 698.38,-182.5 667.63,-182.5"/>
-<text text-anchor="start" x="670.63" y="-188.7" font-family="Times New Roman,serif" font-size="14.00">field</text>
-<defs>
-<linearGradient id="l_5" gradientUnits="userSpaceOnUse" x1="704.1" y1="-201.7" x2="731.67" y2="-185.79" >
-<stop offset="0" style="stop-color:lightblue;stop-opacity:1.;"/>
-<stop offset="1" style="stop-color:green;stop-opacity:1.;"/>
-</linearGradient>
-</defs>
-<polygon fill="url(#l_5)" stroke="none" points="698.38,-182.5 698.38,-205 737.38,-205 737.38,-182.5 698.38,-182.5"/>
-<polygon fill="none" stroke="black" points="698.38,-182.5 698.38,-205 737.38,-205 737.38,-182.5 698.38,-182.5"/>
-<text text-anchor="start" x="701.38" y="-188.7" font-family="Times New Roman,serif" font-size="14.00">int/ptr</text>
-</g>
-<!-- STRUCT_TOP->STRUCT_SN -->
-<g id="edge20" class="edge">
-<title>STRUCT_TOP->STRUCT_SN</title>
-<path fill="none" stroke="black" d="M539.09,-312.43C567.11,-296.11 613.72,-268.96 650.01,-247.82"/>
-<polygon fill="black" stroke="black" points="651.76,-250.85 658.64,-242.8 648.24,-244.81 651.76,-250.85"/>
-</g>
-<!-- STRUCT_BOT -->
-<g id="node13" class="node">
-<title>STRUCT_BOT</title>
-<ellipse fill="lightgreen" stroke="black" cx="512.51" cy="-104.75" rx="44.7" ry="18"/>
-<text text-anchor="start" x="487.76" y="-100.7" font-family="Times New Roman,serif" font-weight="bold" font-size="14.00">⊥</text>
-<text text-anchor="start" x="503.51" y="-100.7" font-family="Times New Roman,serif" font-size="14.00">:struct</text>
-</g>
-<!-- STRUCT_BOT->BOT -->
-<g id="edge24" class="edge">
-<title>STRUCT_BOT->BOT</title>
-<path fill="none" stroke="black" d="M499.56,-87.23C490.56,-75.76 478.41,-60.26 468.22,-47.27"/>
-<polygon fill="black" stroke="black" points="471.08,-45.24 462.16,-39.53 465.57,-49.56 471.08,-45.24"/>
-</g>
-<!-- STRUCT_S1->STRUCT_BOT -->
-<g id="edge21" class="edge">
-<title>STRUCT_S1->STRUCT_BOT</title>
-<path fill="none" stroke="black" d="M454.84,-198.62C465.77,-181.01 483.19,-152.96 496.02,-132.29"/>
-<polygon fill="black" stroke="black" points="498.98,-134.16 501.29,-123.82 493.04,-130.47 498.98,-134.16"/>
-</g>
-<!-- STRUCT_S1->STRUCT_S2 -->
-<!-- STRUCT_S2->STRUCT_BOT -->
-<g id="edge22" class="edge">
-<title>STRUCT_S2->STRUCT_BOT</title>
-<path fill="none" stroke="black" d="M559.36,-189.85C549.77,-172.63 537.12,-149.92 527.38,-132.44"/>
-<polygon fill="black" stroke="black" points="530.68,-131.18 522.76,-124.15 524.57,-134.59 530.68,-131.18"/>
-</g>
-<!-- STRUCT_S2->STRUCT_SN -->
-<!-- STRUCT_SN->STRUCT_BOT -->
-<g id="edge23" class="edge">
-<title>STRUCT_SN->STRUCT_BOT</title>
-<path fill="none" stroke="black" d="M659.87,-190.67C625.81,-171.04 578.69,-143.89 546.98,-125.62"/>
-<polygon fill="black" stroke="black" points="548.99,-122.73 538.58,-120.77 545.49,-128.8 548.99,-122.73"/>
-</g>
-<!-- MEM_ALIAS1 -->
-<g id="node19" class="node">
-<title>MEM_ALIAS1</title>
-<ellipse fill="blue" stroke="black" cx="808.51" cy="-216.25" rx="20.13" ry="20.13"/>
-<text text-anchor="middle" x="808.51" y="-211.2" font-family="Times New Roman,serif" font-size="14.00" fill="white">#1</text>
-</g>
-<!-- MEM_TOP->MEM_ALIAS1 -->
-<g id="edge28" class="edge">
-<title>MEM_TOP->MEM_ALIAS1</title>
-<path fill="none" stroke="black" d="M846.48,-309.64C839.47,-292.59 828.58,-266.09 820.26,-245.84"/>
-<polygon fill="black" stroke="black" points="823.55,-244.65 816.52,-236.73 817.08,-247.31 823.55,-244.65"/>
-</g>
-<!-- MEM_ALIASDOTS -->
-<g id="node20" class="node">
-<title>MEM_ALIASDOTS</title>
-<ellipse fill="blue" stroke="black" cx="898.51" cy="-216.25" rx="27" ry="18"/>
-<text text-anchor="middle" x="898.51" y="-211.2" font-family="Times New Roman,serif" font-size="14.00" fill="white">...</text>
-</g>
-<!-- MEM_TOP->MEM_ALIASDOTS -->
-<g id="edge29" class="edge">
-<title>MEM_TOP->MEM_ALIASDOTS</title>
-<path fill="none" stroke="black" d="M860.54,-309.64C867.71,-292.18 878.96,-264.82 887.35,-244.4"/>
-<polygon fill="black" stroke="black" points="890.54,-245.86 891.1,-235.28 884.06,-243.2 890.54,-245.86"/>
-</g>
-<!-- MEM_ALIASN -->
-<g id="node21" class="node">
-<title>MEM_ALIASN</title>
-<ellipse fill="blue" stroke="black" cx="988.51" cy="-216.25" rx="20.13" ry="20.13"/>
-<text text-anchor="middle" x="988.51" y="-211.2" font-family="Times New Roman,serif" font-size="14.00" fill="white">#n</text>
-</g>
-<!-- MEM_TOP->MEM_ALIASN -->
-<g id="edge30" class="edge">
-<title>MEM_TOP->MEM_ALIASN</title>
-<path fill="none" stroke="black" d="M872.56,-311.29C896.65,-291.76 938.11,-258.13 964.38,-236.82"/>
-<polygon fill="black" stroke="black" points="966.57,-239.55 972.13,-230.53 962.16,-234.11 966.57,-239.55"/>
-</g>
-<!-- MEM_BOT -->
-<g id="node18" class="node">
-<title>MEM_BOT</title>
-<ellipse fill="blue" stroke="black" cx="853.51" cy="-104.75" rx="43.67" ry="18"/>
-<text text-anchor="start" x="829.51" y="-100.7" font-family="Times New Roman,serif" font-weight="bold" font-size="14.00" fill="white">⊥</text>
-<text text-anchor="start" x="845.26" y="-100.7" font-family="Times New Roman,serif" font-size="14.00" fill="white">:mem</text>
-</g>
-<!-- MEM_BOT->BOT -->
-<g id="edge34" class="edge">
-<title>MEM_BOT->BOT</title>
-<path fill="none" stroke="black" d="M815.04,-96C735.55,-79.97 554.45,-43.48 480.68,-28.61"/>
-<polygon fill="black" stroke="black" points="481.37,-25.18 470.88,-26.63 479.99,-32.04 481.37,-25.18"/>
-</g>
-<!-- MEM_ALIAS1->MEM_BOT -->
-<g id="edge31" class="edge">
-<title>MEM_ALIAS1->MEM_BOT</title>
-<path fill="none" stroke="black" d="M815.94,-197.17C823.07,-179.82 833.93,-153.39 842.15,-133.4"/>
-<polygon fill="black" stroke="black" points="845.37,-134.75 845.94,-124.17 838.9,-132.09 845.37,-134.75"/>
-</g>
-<!-- MEM_ALIAS1->MEM_ALIASDOTS -->
-<!-- MEM_ALIASDOTS->MEM_BOT -->
-<g id="edge32" class="edge">
-<title>MEM_ALIASDOTS->MEM_BOT</title>
-<path fill="none" stroke="black" d="M891.67,-198.62C884.58,-181.35 873.36,-154.05 864.92,-133.51"/>
-<polygon fill="black" stroke="black" points="868.17,-132.22 861.13,-124.3 861.7,-134.88 868.17,-132.22"/>
-</g>
-<!-- MEM_ALIASDOTS->MEM_ALIASN -->
-<!-- MEM_ALIASN->MEM_BOT -->
-<g id="edge33" class="edge">
-<title>MEM_ALIASN->MEM_BOT</title>
-<path fill="none" stroke="black" d="M973.02,-202.69C950.75,-184.62 909.29,-151 881.55,-128.49"/>
-<polygon fill="black" stroke="black" points="883.86,-125.86 873.89,-122.28 879.45,-131.3 883.86,-125.86"/>
-</g>
-<!-- TUPLE1 -->
-<g id="node24" class="node">
-<title>TUPLE1</title>
-<polygon fill="none" stroke="none" points="1105.51,-242.75 1051.51,-242.75 1051.51,-189.75 1105.51,-189.75 1105.51,-242.75"/>
-<polygon fill="pink" stroke="none" points="1059.76,-216.25 1059.76,-238.75 1097.26,-238.75 1097.26,-216.25 1059.76,-216.25"/>
-<polygon fill="none" stroke="black" points="1059.76,-216.25 1059.76,-238.75 1097.26,-238.75 1097.26,-216.25 1059.76,-216.25"/>
-<text text-anchor="start" x="1062.76" y="-222.45" font-family="Times New Roman,serif" font-size="14.00">Tuple</text>
-<polygon fill="black" stroke="none" points="1059.76,-193.75 1059.76,-216.25 1097.26,-216.25 1097.26,-193.75 1059.76,-193.75"/>
-<polygon fill="none" stroke="black" points="1059.76,-193.75 1059.76,-216.25 1097.26,-216.25 1097.26,-193.75 1059.76,-193.75"/>
-<text text-anchor="start" x="1066.88" y="-199.95" font-family="Times New Roman,serif" font-size="14.00" fill="white">type</text>
-</g>
-<!-- TUPLE_TOP->TUPLE1 -->
-<g id="edge38" class="edge">
-<title>TUPLE_TOP->TUPLE1</title>
-<path fill="none" stroke="black" d="M1119.01,-309.64C1112.37,-294.48 1102.45,-271.87 1094.1,-252.81"/>
-<polygon fill="black" stroke="black" points="1097.35,-251.51 1090.13,-243.76 1090.94,-254.32 1097.35,-251.51"/>
-</g>
-<!-- TUPLE2 -->
-<g id="node25" class="node">
-<title>TUPLE2</title>
-<polygon fill="none" stroke="none" points="1202.51,-254 1148.51,-254 1148.51,-178.5 1202.51,-178.5 1202.51,-254"/>
-<polygon fill="pink" stroke="none" points="1156.76,-227.5 1156.76,-250 1194.26,-250 1194.26,-227.5 1156.76,-227.5"/>
-<polygon fill="none" stroke="black" points="1156.76,-227.5 1156.76,-250 1194.26,-250 1194.26,-227.5 1156.76,-227.5"/>
-<text text-anchor="start" x="1159.76" y="-233.7" font-family="Times New Roman,serif" font-size="14.00">Tuple</text>
-<polygon fill="black" stroke="none" points="1156.76,-205 1156.76,-227.5 1194.26,-227.5 1194.26,-205 1156.76,-205"/>
-<polygon fill="none" stroke="black" points="1156.76,-205 1156.76,-227.5 1194.26,-227.5 1194.26,-205 1156.76,-205"/>
-<text text-anchor="start" x="1163.88" y="-211.2" font-family="Times New Roman,serif" font-size="14.00" fill="white">type</text>
-<polygon fill="black" stroke="none" points="1156.76,-182.5 1156.76,-205 1194.26,-205 1194.26,-182.5 1156.76,-182.5"/>
-<polygon fill="none" stroke="black" points="1156.76,-182.5 1156.76,-205 1194.26,-205 1194.26,-182.5 1156.76,-182.5"/>
-<text text-anchor="start" x="1163.88" y="-188.7" font-family="Times New Roman,serif" font-size="14.00" fill="white">type</text>
-</g>
-<!-- TUPLE_TOP->TUPLE2 -->
-<g id="edge39" class="edge">
-<title>TUPLE_TOP->TUPLE2</title>
-<path fill="none" stroke="black" d="M1134.17,-309.64C1139.64,-297.4 1147.29,-280.3 1154.53,-264.14"/>
-<polygon fill="black" stroke="black" points="1157.68,-265.65 1158.57,-255.09 1151.3,-262.79 1157.68,-265.65"/>
-</g>
-<!-- TUPLEN -->
-<g id="node26" class="node">
-<title>TUPLEN</title>
-<polygon fill="none" stroke="none" points="1299.51,-265.25 1245.51,-265.25 1245.51,-167.25 1299.51,-167.25 1299.51,-265.25"/>
-<polygon fill="pink" stroke="none" points="1253.76,-238.75 1253.76,-261.25 1291.26,-261.25 1291.26,-238.75 1253.76,-238.75"/>
-<polygon fill="none" stroke="black" points="1253.76,-238.75 1253.76,-261.25 1291.26,-261.25 1291.26,-238.75 1253.76,-238.75"/>
-<text text-anchor="start" x="1256.76" y="-244.95" font-family="Times New Roman,serif" font-size="14.00">Tuple</text>
-<polygon fill="black" stroke="none" points="1253.76,-216.25 1253.76,-238.75 1291.26,-238.75 1291.26,-216.25 1253.76,-216.25"/>
-<polygon fill="none" stroke="black" points="1253.76,-216.25 1253.76,-238.75 1291.26,-238.75 1291.26,-216.25 1253.76,-216.25"/>
-<text text-anchor="start" x="1260.88" y="-222.45" font-family="Times New Roman,serif" font-size="14.00" fill="white">type</text>
-<polygon fill="black" stroke="none" points="1253.76,-193.75 1253.76,-216.25 1291.26,-216.25 1291.26,-193.75 1253.76,-193.75"/>
-<polygon fill="none" stroke="black" points="1253.76,-193.75 1253.76,-216.25 1291.26,-216.25 1291.26,-193.75 1253.76,-193.75"/>
-<text text-anchor="start" x="1266.88" y="-199.95" font-family="Times New Roman,serif" font-size="14.00" fill="white">...</text>
-<polygon fill="black" stroke="none" points="1253.76,-171.25 1253.76,-193.75 1291.26,-193.75 1291.26,-171.25 1253.76,-171.25"/>
-<polygon fill="none" stroke="black" points="1253.76,-171.25 1253.76,-193.75 1291.26,-193.75 1291.26,-171.25 1253.76,-171.25"/>
-<text text-anchor="start" x="1260.88" y="-177.45" font-family="Times New Roman,serif" font-size="14.00" fill="white">type</text>
-</g>
-<!-- TUPLE_TOP->TUPLEN -->
-<g id="edge40" class="edge">
-<title>TUPLE_TOP->TUPLEN</title>
-<path fill="none" stroke="black" d="M1146.81,-311.52C1169.97,-294.15 1208.28,-265.42 1236.53,-244.23"/>
-<polygon fill="black" stroke="black" points="1238.58,-247.07 1244.48,-238.27 1234.38,-241.47 1238.58,-247.07"/>
-</g>
-<!-- TUPLE_BOT -->
-<g id="node23" class="node">
-<title>TUPLE_BOT</title>
-<ellipse fill="pink" stroke="black" cx="1126.51" cy="-104.75" rx="42.65" ry="18"/>
-<text text-anchor="start" x="1103.26" y="-100.7" font-family="Times New Roman,serif" font-weight="bold" font-size="14.00">⊥</text>
-<text text-anchor="start" x="1119.01" y="-100.7" font-family="Times New Roman,serif" font-size="14.00">:tuple</text>
-</g>
-<!-- TUPLE_BOT->BOT -->
-<g id="edge44" class="edge">
-<title>TUPLE_BOT->BOT</title>
-<path fill="none" stroke="black" d="M1085.88,-98.86C962.25,-83.97 592.31,-39.44 480.72,-26"/>
-<polygon fill="black" stroke="black" points="481.43,-22.56 471.08,-24.84 480.59,-29.51 481.43,-22.56"/>
-</g>
-<!-- TUPLE1->TUPLE_BOT -->
-<g id="edge41" class="edge">
-<title>TUPLE1->TUPLE_BOT</title>
-<path fill="none" stroke="black" d="M1089.64,-189.85C1097.12,-172.8 1106.96,-150.34 1114.59,-132.93"/>
-<polygon fill="black" stroke="black" points="1117.61,-134.78 1118.41,-124.21 1111.19,-131.97 1117.61,-134.78"/>
-</g>
-<!-- TUPLE2->TUPLE_BOT -->
-<g id="edge42" class="edge">
-<title>TUPLE2->TUPLE_BOT</title>
-<path fill="none" stroke="black" d="M1159.19,-178.79C1152.51,-163.86 1144.92,-146.89 1138.73,-133.07"/>
-<polygon fill="black" stroke="black" points="1142.02,-131.83 1134.74,-124.13 1135.63,-134.69 1142.02,-131.83"/>
-</g>
-<!-- TUPLEN->TUPLE_BOT -->
-<g id="edge43" class="edge">
-<title>TUPLEN->TUPLE_BOT</title>
-<path fill="none" stroke="black" d="M1245.73,-195.16C1220.22,-176.03 1181.9,-147.29 1155.64,-127.59"/>
-<polygon fill="black" stroke="black" points="1157.86,-124.89 1147.76,-121.69 1153.66,-130.49 1157.86,-124.89"/>
-</g>
-</g>
-</svg>
diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/StartNode.java deleted file mode 100644 index 4b7e02a8..00000000 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.type.*; -import java.util.Arrays; -import java.util.BitSet; -import java.util.HashMap; - -/** - * 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; - - // The alias number of the first field in the struct. - // - public final HashMap<String,Integer> _aliasStarts; - - - public StartNode(Type[] args) { - super(); - _args = TypeTuple.make(args); - _type = _args; - _aliasStarts = new HashMap<>(); - } - - /** - * Creates a projection for each of the struct's fields, using the field alias - * as the key. - */ - public void addMemProj( TypeStruct ts, ScopeNode scope) { - int len = _args._types.length; - _aliasStarts.put(ts._name,len); - - // resize the tuple's type array to include all fields of the struct - int max = len + ts._fields.length; - Type[] args = Arrays.copyOf(_args._types, max); - - // 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 = Parser.memName(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 int idepth() { return 0; } - @Override Node idom() { return null; } -} diff --git a/chapter10a/Makefile b/chapter10a/Makefile new file mode 100644 index 00000000..5ed21084 --- /dev/null +++ b/chapter10a/Makefile @@ -0,0 +1,7 @@ +# Standalone compiler snapshot; shared rules also work in linearized checkouts. +include $(firstword $(wildcard ../build-support/early-chapter.mk build-support/early-chapter.mk)) +test_cp += com.seaofnodes.simple.FuzzerWrap + +.PHONY: fuzzer +fuzzer: $(test_classes) + java -ea -cp "$(CLZDIR)/main$(SEP)$(CLZDIR)/test$(SEP)$(jars)" org.junit.runner.JUnitCore com.seaofnodes.simple.FuzzerWrap diff --git a/chapter10a/README.md b/chapter10a/README.md new file mode 100644 index 00000000..2f25f681 --- /dev/null +++ b/chapter10a/README.md @@ -0,0 +1,290 @@ +# Chapter 10a: Structs and Memory + +[Previous: Chapter 9](../chapter09/README.md) | +[Next: Chapter 10b](../chapter10b/README.md) + +This chapter adds user-defined structs, pointers, allocation, field loads and +stores, and null checking. It also introduces an evaluator that executes the +graph, including its memory effects. The [grammar](docs/10-grammar.md) is the +same in 10a and 10b; the next chapter improves the memory representation. + +## One memory value + +Arithmetic nodes depend on the values they use, similarly a load also depends +on when it observes memory. In this chapter every store consumes the preceding +memory state and produces the next one, and a load consumes memory without +changing it. This makes the source program's memory ordering explicit in the +graph. + +```java +struct Point { int x; int y; } +Point p = new Point; +p.x = 3; +p.y = arg; +return p.x; +``` + +`NewNode` produces the pointer. The parser emits stores of zero for both fields, +then stores 3 into `x` and `arg` into `y`. The final load observes the memory +after the `y` store. We deliberately keep that dependency here, even though +different fields are independent. Chapter 10b will remove it. + + + +| Node | Inputs | Result | +|----------|---|---| +| `New` | Control and a struct type | A fresh, non-null object pointer | +| `Store` | Memory, pointer, field name, value | The updated whole-memory state | +| `Load` | Memory, pointer, field name | The value of the field | +| `Start` | External entry | Control, initial memory, and `arg` | +| `Return` | Control, result, memory | Completion with all preceding effects retained | + +The memory edge is a dependency, not a copy of the heap. The graph determines +the legal execution order. In hardware, a Store updates physical memory +directly. In the evaluator, a Store updates an objects' attached memory array. + +## Memory is an SSA variable + +The parser keeps one hidden binding named `$mem`. Source identifiers cannot +contain `$`, so user code cannot name it. Reading and updating memory uses the +same `ScopeNode` lookup and update operations as a local variable. + +For an `if`, the two branches can finish with different memory values. An +ordinary Phi selects the memory from the branch that executed: + + + +Loops reuse Chapter 8's lazy Phi construction. Reading or updating `$mem` in a +loop creates an initially incomplete memory Phi. Closing the loop supplies its +backedge. `break`, `continue`, nested loops, and early returns use the same scope +mechanics as scalar variables. + + + +Every Return consumes the current whole-memory value. This keeps stores alive +even when the returned scalar does not depend on them, or when the program +returns the modified object itself. + +## Local memory optimizations + +A load immediately following a store to the **same pointer and field** can use +the stored value. A store immediately following another store to the same +address can discard the earlier store when no other user needs it. Both the +pointer and field checks matter: two different fields of one object are +different addresses, as are the same fields of two different objects. + +A load can also move through a memory Phi when doing so exposes a useful +load-after-store fold. On a loop, the backedge must fold, preventing this rewrite +from repeatedly moving the load around the cycle. + +The single chain is conservative. A store to `y` can prevent forwarding an +earlier store to `x`, even when the objects are identical. This is a missed +optimization, not a correctness problem; it motivates the next chapter. + +## Enhanced Type Lattice + +The Type Lattice for Simple has a major revision in this chapter. + + + +The diagram uses `⊤` for **top** and `⊥` for **bottom**. A suffix names the +domain: `⊤:struct` is *struct top*, while the bare `⊤` is global top. In the +prose we use those words; in code examples we use the actual Java constants. +The implementation's `$TOP` and `$BOT` names need not appear in the diagram: + +| Diagram | Prose | Java constant | Internal struct name | +|---|---|---|---| +| `⊤:struct` | struct top | `TypeStruct.TOP` | `$TOP` | +| `⊥:struct` | struct bottom | `TypeStruct.BOT` | `$BOT` | + +Edges in this diagram show lattice order, with top above bottom. They are not +the use-to-definition edges of the program graphs elsewhere in the chapter. +Boxes containing an ellipsis abbreviate further parallel alternatives; they +are not extra union types. Field and tuple-element type lattices are elided. + +Within the type lattice, we now have the following domains: + +*  represents control flow: unreachable control + at control top, and reachable control at control bottom. +*  has integer top, integer bottom, and parallel + constants between them. The ellipses stand for the other integer constants. +*  (new) combines a struct type with nullability. + * The prefix `*` means pointer-to: `*Person` is a non-null pointer to a Person. + The suffix `?` includes null, so `*Person?` permits a Person pointer or null. + * The pointer domain contains the whole struct lattice twice, once without + null and once with it. The diagram expands named alternatives in both + copies. It is not just two individual pointer types. + * `null` combines struct top with the nullable flag: it has no non-null + object alternative. The diagram labels it `*⊤:struct?` as well as `null`. + * A pointer to struct bottom can point to any struct. The diagram shows the + non-null form as `*⊥:struct` and the nullable form as `*⊥:struct?`. + The latter is pointer bottom; pointer top is `*⊤:struct`. + * Null evaluates to false; non-null pointers evaluate to true. + + > C analogy: the non-null pointer to struct bottom is akin to `void *`, except + > that C's `void *` may also be null. Simple names this constant + > `TypeMemPtr.VOIDPTR` and prints it as `*void`. Struct bottom itself is the + > common lower bound of struct types, not C's `void` type. + +*  (new) represents user-defined named structs. + Fields have integer types in this chapter. + * Struct top lies above every named struct; struct bottom lies below every + named struct. Top is the high/empty struct alternative, while bottom + conservatively includes any struct name. + * `Person`, `s1`, `s2`, and further names are parallel alternatives. There is + no inheritance, so different named structs do not nest beneath one another, + even if they have identical fields. + * There is no fixed bound on the names a program can declare. The diagram's + ellipsis stands for more named alternatives, not a distinguished unnamed + struct. Each named box abbreviates its own field-type lattice. +*  (new) represents the whole-memory token. + Memory top and memory bottom are `TypeMem.TOP` and `TypeMem.BOT`. Neither + records the values stored in individual fields. +*  represents a collection of results from one node, + such as Start's control, memory, and argument. The displayed shapes abbreviate + tuples of different lengths and their element-type lattices. + +We use the following operations on the lattice: + +* **Meet** computes the greatest lower bound. For these examples, think of + combining the alternatives represented by its inputs. + * The meet of `Person` and `s1` is struct bottom. There is no separate type + representing exactly those two names. + * The meet of `*Person` and `*s1` is the non-null pointer to struct bottom. + * The meet of `*Person` and `null` is `*Person?`. + * The meet of the non-null pointer to struct bottom and `null` is pointer bottom. + * The meet of `1` and `2` is integer bottom. +* **Dual** reflects the lattice about its centerline: top and bottom exchange + places, and applying dual twice returns the original type. + * Struct top and struct bottom are duals. + * `null` and the non-null pointer to struct bottom are duals. + * A pointer's dual also duals its struct's fields and flips nullability. Thus + the dual of `*Person` is not generally the original `*Person?`: the field + types must be dualed too. Those field details are omitted in the diagram. +* **Join** computes the least upper bound. It is defined through meet and dual: + `JOIN(x,y) = MEET(x.dual(),y.dual()).dual()`. + * The join of `Person` and `s1` is struct top. + * The join of `*Person` and `*s1` is pointer top. + * The join of the non-null pointer to struct bottom and `null` is pointer top. + * The join of `1` and `2` is integer 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 decides if 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 uses a `Cast` with the non-null pointer to struct bottom + (`TypeMemPtr.VOIDPTR`). It joins the original pointer type with that type, + corresponding to `*⊥:struct` in the diagram. 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()`. + + +## Executing the graph + +The evaluator includes a small late scheduler. A load must execute before a +later store that consumes the same memory definition, even though there is no +ordinary data edge from the load to that store. These load/store +anti-dependencies supplement the explicit graph edges. + + + +The scheduler is sufficient to execute this chapter. Chapter 11 develops +global code motion and scheduling as a separate topic. + +## Build and test + +From this directory, with JDK 21 or newer, Make, and a shell: + +```sh +make lib +make tests +make release +make view +``` + +Make builds this snapshot directly, without Maven. `make release` produces +`build/release/simple.jar`; `make view` launches the shared interactive graph +viewer. Maven and IDEA module descriptions are also supplied. Tests include all +earlier chapters, memory ordering, returned object contents, and nested loops. + +Next, [Chapter 10b](../chapter10b/README.md) keeps this one parser binding while +splitting the graph into independent memory slices. diff --git a/chapter10a/chapter10a.iml b/chapter10a/chapter10a.iml new file mode 100644 index 00000000..3f930463 --- /dev/null +++ b/chapter10a/chapter10a.iml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module type="JAVA_MODULE" version="4"> + <component name="NewModuleRootManager" inherit-compiler-output="false"> + <output url="file://$MODULE_DIR$/../build/idea/production/chapter10a" /> + <output-test url="file://$MODULE_DIR$/../build/idea/test/chapter10a" /> + <exclude-output /> + <content url="file://$MODULE_DIR$"> + <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" /> + <excludeFolder url="file://$MODULE_DIR$/build" /> + <excludeFolder url="file://$MODULE_DIR$/target" /> + </content> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + <orderEntry type="module" module-name="graph" /> + <orderEntry type="module-library" scope="TEST"> + <library name="JUnit4"> + <CLASSES> + <root url="jar://$MODULE_DIR$/lib/junit-4.12.jar!/" /> + <root url="jar://$MODULE_DIR$/lib/hamcrest-core-1.3.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES /> + </library> + </orderEntry> + </component> +</module> diff --git a/chapter10/docs/10-grammar.md b/chapter10a/docs/10-grammar.md index b76531d5..5b9b680e 100644 --- a/chapter10/docs/10-grammar.md +++ b/chapter10a/docs/10-grammar.md @@ -1,4 +1,4 @@ -# Grammar for Chapter 10 +# Grammar for Chapter 10a ```antlrv4 grammar SimpleLanguage; diff --git a/chapter10/docs/Makefile b/chapter10a/docs/Makefile index 12659b8f..055882b2 100644 --- a/chapter10/docs/Makefile +++ b/chapter10a/docs/Makefile @@ -1,11 +1,13 @@ GV := $(wildcard *.gv) SVG := $(patsubst %.gv,%.svg,$(GV)) +.DELETE_ON_ERROR: .PHONY: all all: $(SVG) %.svg: %.gv dot -Tsvg $< > $@ + @sed -i 's/\r$$//' $@ .PHONY: clean clean: diff --git a/chapter10a/docs/example1.gv b/chapter10a/docs/example1.gv new file mode 100644 index 00000000..d399b562 --- /dev/null +++ b/chapter10a/docs/example1.gv @@ -0,0 +1,10 @@ +digraph Memory { + rankdir=RL; + node [shape=box]; + M0 [label="Initial memory"]; + X [label="Store p.x = 3"]; + Y [label="Store p.y = arg"]; + L [label="Load p.x"]; + L -> Y -> X -> M0 [color=blue]; + Return -> Y [color=blue]; Return -> L; +} diff --git a/chapter10a/docs/example1.svg b/chapter10a/docs/example1.svg new file mode 100644 index 00000000..d1c5e3bd --- /dev/null +++ b/chapter10a/docs/example1.svg @@ -0,0 +1,73 @@ +<?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 12.2.0 (20241103.1931) + --> +<!-- Title: Memory Pages: 1 --> +<svg width="556pt" height="67pt" + viewBox="0.00 0.00 555.75 67.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 63)"> +<title>Memory</title> +<polygon fill="white" stroke="none" points="-4,4 -4,-63 551.75,-63 551.75,4 -4,4"/> +<!-- M0 --> +<g id="node1" class="node"> +<title>M0</title> +<polygon fill="none" stroke="black" points="98.5,-59 0,-59 0,-23 98.5,-23 98.5,-59"/> +<text text-anchor="middle" x="49.25" y="-35.95" font-family="Times New Roman,serif" font-size="14.00">Initial memory</text> +</g> +<!-- X --> +<g id="node2" class="node"> +<title>X</title> +<polygon fill="none" stroke="black" points="222.5,-59 134.5,-59 134.5,-23 222.5,-23 222.5,-59"/> +<text text-anchor="middle" x="178.5" y="-35.95" font-family="Times New Roman,serif" font-size="14.00">Store p.x = 3</text> +</g> +<!-- X->M0 --> +<g id="edge3" class="edge"> +<title>X->M0</title> +<path fill="none" stroke="blue" d="M134.06,-41C126.33,-41 118.16,-41 110.06,-41"/> +<polygon fill="blue" stroke="blue" points="110.24,-37.5 100.24,-41 110.24,-44.5 110.24,-37.5"/> +</g> +<!-- Y --> +<g id="node3" class="node"> +<title>Y</title> +<polygon fill="none" stroke="black" points="357,-59 258.5,-59 258.5,-23 357,-23 357,-59"/> +<text text-anchor="middle" x="307.75" y="-35.95" font-family="Times New Roman,serif" font-size="14.00">Store p.y = arg</text> +</g> +<!-- Y->X --> +<g id="edge2" class="edge"> +<title>Y->X</title> +<path fill="none" stroke="blue" d="M258.28,-41C250.45,-41 242.29,-41 234.32,-41"/> +<polygon fill="blue" stroke="blue" points="234.35,-37.5 224.35,-41 234.35,-44.5 234.35,-37.5"/> +</g> +<!-- L --> +<g id="node4" class="node"> +<title>L</title> +<polygon fill="none" stroke="black" points="457.75,-36 393,-36 393,0 457.75,0 457.75,-36"/> +<text text-anchor="middle" x="425.38" y="-12.95" font-family="Times New Roman,serif" font-size="14.00">Load p.x</text> +</g> +<!-- L->Y --> +<g id="edge1" class="edge"> +<title>L->Y</title> +<path fill="none" stroke="blue" d="M392.78,-24.28C385.16,-25.8 376.76,-27.47 368.31,-29.15"/> +<polygon fill="blue" stroke="blue" points="367.85,-25.68 358.73,-31.06 369.22,-32.54 367.85,-25.68"/> +</g> +<!-- Return --> +<g id="node5" class="node"> +<title>Return</title> +<polygon fill="none" stroke="black" points="547.75,-59 493.75,-59 493.75,-23 547.75,-23 547.75,-59"/> +<text text-anchor="middle" x="520.75" y="-35.95" font-family="Times New Roman,serif" font-size="14.00">Return</text> +</g> +<!-- Return->Y --> +<g id="edge4" class="edge"> +<title>Return->Y</title> +<path fill="none" stroke="blue" d="M493.52,-43.07C482.49,-43.84 469.5,-44.62 457.75,-45 428.99,-45.93 421.77,-45.68 393,-45 385.14,-44.81 376.87,-44.53 368.71,-44.2"/> +<polygon fill="blue" stroke="blue" points="368.95,-40.7 358.81,-43.76 368.65,-47.7 368.95,-40.7"/> +</g> +<!-- Return->L --> +<g id="edge5" class="edge"> +<title>Return->L</title> +<path fill="none" stroke="black" d="M493.74,-34.59C486.17,-32.73 477.69,-30.64 469.38,-28.59"/> +<polygon fill="black" stroke="black" points="470.24,-25.2 459.7,-26.21 468.57,-32 470.24,-25.2"/> +</g> +</g> +</svg> diff --git a/chapter10a/docs/example2a.gv b/chapter10a/docs/example2a.gv new file mode 100644 index 00000000..ec5afbdc --- /dev/null +++ b/chapter10a/docs/example2a.gv @@ -0,0 +1,10 @@ +digraph Memory { + rankdir=RL; + node [shape=box]; + M [label="Incoming memory"]; + A [label="True: Store x"]; + B [label="False: Store y"]; + P [label="Phi(memory)"]; + P -> A -> M [color=blue]; P -> B -> M [color=blue]; + P -> Region [style=dashed]; Return -> P [color=blue]; +} diff --git a/chapter10a/docs/example2a.svg b/chapter10a/docs/example2a.svg new file mode 100644 index 00000000..f9a9b4fd --- /dev/null +++ b/chapter10a/docs/example2a.svg @@ -0,0 +1,85 @@ +<?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 12.2.0 (20241103.1931) + --> +<!-- Title: Memory Pages: 1 --> +<svg width="469pt" height="152pt" + viewBox="0.00 0.00 469.25 152.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 148)"> +<title>Memory</title> +<polygon fill="white" stroke="none" points="-4,4 -4,-148 465.25,-148 465.25,4 -4,4"/> +<!-- M --> +<g id="node1" class="node"> +<title>M</title> +<polygon fill="none" stroke="black" points="118.75,-117 0,-117 0,-81 118.75,-81 118.75,-117"/> +<text text-anchor="middle" x="59.38" y="-93.95" font-family="Times New Roman,serif" font-size="14.00">Incoming memory</text> +</g> +<!-- A --> +<g id="node2" class="node"> +<title>A</title> +<polygon fill="none" stroke="black" points="244.25,-144 156.25,-144 156.25,-108 244.25,-108 244.25,-144"/> +<text text-anchor="middle" x="200.25" y="-120.95" font-family="Times New Roman,serif" font-size="14.00">True: Store x</text> +</g> +<!-- A->M --> +<g id="edge2" class="edge"> +<title>A->M</title> +<path fill="none" stroke="blue" d="M156.07,-117.61C147.84,-116.01 139.03,-114.29 130.22,-112.58"/> +<polygon fill="blue" stroke="blue" points="131.06,-109.18 120.58,-110.7 129.72,-116.05 131.06,-109.18"/> +</g> +<!-- B --> +<g id="node3" class="node"> +<title>B</title> +<polygon fill="none" stroke="black" points="245.75,-90 154.75,-90 154.75,-54 245.75,-54 245.75,-90"/> +<text text-anchor="middle" x="200.25" y="-66.95" font-family="Times New Roman,serif" font-size="14.00">False: Store y</text> +</g> +<!-- B->M --> +<g id="edge4" class="edge"> +<title>B->M</title> +<path fill="none" stroke="blue" d="M154.54,-80.69C146.8,-82.2 138.59,-83.79 130.37,-85.39"/> +<polygon fill="blue" stroke="blue" points="129.83,-81.93 120.68,-87.28 131.16,-88.8 129.83,-81.93"/> +</g> +<!-- P --> +<g id="node4" class="node"> +<title>P</title> +<polygon fill="none" stroke="black" points="371.25,-90 281.75,-90 281.75,-54 371.25,-54 371.25,-90"/> +<text text-anchor="middle" x="326.5" y="-66.95" font-family="Times New Roman,serif" font-size="14.00">Phi(memory)</text> +</g> +<!-- P->A --> +<g id="edge1" class="edge"> +<title>P->A</title> +<path fill="none" stroke="blue" d="M283.09,-90.43C273.69,-94.52 263.62,-98.89 253.88,-103.13"/> +<polygon fill="blue" stroke="blue" points="252.64,-99.85 244.86,-107.05 255.43,-106.27 252.64,-99.85"/> +</g> +<!-- P->B --> +<g id="edge3" class="edge"> +<title>P->B</title> +<path fill="none" stroke="blue" d="M281.34,-72C273.66,-72 265.57,-72 257.61,-72"/> +<polygon fill="blue" stroke="blue" points="257.62,-68.5 247.62,-72 257.62,-75.5 257.62,-68.5"/> +</g> +<!-- Region --> +<g id="node5" class="node"> +<title>Region</title> +<polygon fill="none" stroke="black" points="227.75,-36 172.75,-36 172.75,0 227.75,0 227.75,-36"/> +<text text-anchor="middle" x="200.25" y="-12.95" font-family="Times New Roman,serif" font-size="14.00">Region</text> +</g> +<!-- P->Region --> +<g id="edge5" class="edge"> +<title>P->Region</title> +<path fill="none" stroke="black" stroke-dasharray="5,2" d="M283.09,-53.57C268.52,-47.24 252.34,-40.21 238.24,-34.08"/> +<polygon fill="black" stroke="black" points="239.88,-30.97 229.31,-30.2 237.09,-37.39 239.88,-30.97"/> +</g> +<!-- Return --> +<g id="node6" class="node"> +<title>Return</title> +<polygon fill="none" stroke="black" points="461.25,-90 407.25,-90 407.25,-54 461.25,-54 461.25,-90"/> +<text text-anchor="middle" x="434.25" y="-66.95" font-family="Times New Roman,serif" font-size="14.00">Return</text> +</g> +<!-- Return->P --> +<g id="edge6" class="edge"> +<title>Return->P</title> +<path fill="none" stroke="blue" d="M406.84,-72C399.52,-72 391.29,-72 382.97,-72"/> +<polygon fill="blue" stroke="blue" points="383.17,-68.5 373.17,-72 383.17,-75.5 383.17,-68.5"/> +</g> +</g> +</svg> diff --git a/chapter10a/docs/example2b.gv b/chapter10a/docs/example2b.gv new file mode 100644 index 00000000..3f3309e5 --- /dev/null +++ b/chapter10a/docs/example2b.gv @@ -0,0 +1,11 @@ +digraph Memory { + rankdir=RL; + node [shape=box]; + P [label="Phi(memory)"]; + S [label="Loop-body Store"]; + P -> Entry [color=blue]; + P -> S [color=blue,constraint=false]; // Backedge must not place Store before Phi. + S -> P [color=blue]; + P -> Loop [style=dashed]; Return -> P [color=blue]; + Return -> S [style=invis]; // Layout only: Phi, Store, Return from left to right. +} diff --git a/chapter10a/docs/example2b.svg b/chapter10a/docs/example2b.svg new file mode 100644 index 00000000..8fff33d4 --- /dev/null +++ b/chapter10a/docs/example2b.svg @@ -0,0 +1,74 @@ +<?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 12.2.0 (20241103.1931) + --> +<!-- Title: Memory Pages: 1 --> +<svg width="422pt" height="98pt" + viewBox="0.00 0.00 421.75 98.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 94)"> +<title>Memory</title> +<polygon fill="white" stroke="none" points="-4,4 -4,-94 417.75,-94 417.75,4 -4,4"/> +<!-- P --> +<g id="node1" class="node"> +<title>P</title> +<polygon fill="none" stroke="black" points="179.5,-63 90,-63 90,-27 179.5,-27 179.5,-63"/> +<text text-anchor="middle" x="134.75" y="-39.95" font-family="Times New Roman,serif" font-size="14.00">Phi(memory)</text> +</g> +<!-- S --> +<g id="node2" class="node"> +<title>S</title> +<polygon fill="none" stroke="black" points="323.75,-40 215.5,-40 215.5,-4 323.75,-4 323.75,-40"/> +<text text-anchor="middle" x="269.62" y="-16.95" font-family="Times New Roman,serif" font-size="14.00">Loop-body Store</text> +</g> +<!-- P->S --> +<g id="edge2" class="edge"> +<title>P->S</title> +<path fill="none" stroke="blue" d="M179.67,-43.79C187.52,-42.69 195.84,-41.37 204.12,-39.91"/> +<polygon fill="blue" stroke="blue" points="204.65,-43.37 213.86,-38.13 203.39,-36.49 204.65,-43.37"/> +</g> +<!-- Entry --> +<g id="node3" class="node"> +<title>Entry</title> +<polygon fill="none" stroke="black" points="54,-90 0,-90 0,-54 54,-54 54,-90"/> +<text text-anchor="middle" x="27" y="-66.95" font-family="Times New Roman,serif" font-size="14.00">Entry</text> +</g> +<!-- P->Entry --> +<g id="edge1" class="edge"> +<title>P->Entry</title> +<path fill="none" stroke="blue" d="M89.52,-56.29C81.45,-58.35 73.12,-60.48 65.28,-62.48"/> +<polygon fill="blue" stroke="blue" points="64.54,-59.06 55.72,-64.92 66.27,-65.84 64.54,-59.06"/> +</g> +<!-- Loop --> +<g id="node4" class="node"> +<title>Loop</title> +<polygon fill="none" stroke="black" points="54,-36 0,-36 0,0 54,0 54,-36"/> +<text text-anchor="middle" x="27" y="-12.95" font-family="Times New Roman,serif" font-size="14.00">Loop</text> +</g> +<!-- P->Loop --> +<g id="edge4" class="edge"> +<title>P->Loop</title> +<path fill="none" stroke="black" stroke-dasharray="5,2" d="M89.52,-33.71C81.45,-31.65 73.12,-29.52 65.28,-27.52"/> +<polygon fill="black" stroke="black" points="66.27,-24.16 55.72,-25.08 64.54,-30.94 66.27,-24.16"/> +</g> +<!-- S->P --> +<g id="edge3" class="edge"> +<title>S->P</title> +<path fill="none" stroke="blue" d="M215.35,-24.59C207.3,-25.84 198.98,-27.27 190.89,-28.79"/> +<polygon fill="blue" stroke="blue" points="190.29,-25.34 181.15,-30.71 191.64,-32.21 190.29,-25.34"/> +</g> +<!-- Return --> +<g id="node5" class="node"> +<title>Return</title> +<polygon fill="none" stroke="black" points="413.75,-63 359.75,-63 359.75,-27 413.75,-27 413.75,-63"/> +<text text-anchor="middle" x="386.75" y="-39.95" font-family="Times New Roman,serif" font-size="14.00">Return</text> +</g> +<!-- Return->P --> +<g id="edge5" class="edge"> +<title>Return->P</title> +<path fill="none" stroke="blue" d="M359.52,-47.07C348.49,-47.84 335.5,-48.62 323.75,-49 275.66,-50.55 263.6,-50.21 215.5,-49 207.59,-48.8 199.24,-48.49 191.04,-48.13"/> +<polygon fill="blue" stroke="blue" points="191.28,-44.64 181.12,-47.67 190.95,-51.63 191.28,-44.64"/> +</g> +<!-- Return->S --> +</g> +</svg> diff --git a/chapter10a/docs/example2c.gv b/chapter10a/docs/example2c.gv new file mode 100644 index 00000000..ef8a5fc8 --- /dev/null +++ b/chapter10a/docs/example2c.gv @@ -0,0 +1,9 @@ +digraph Memory { + rankdir=RL; + node [shape=box]; + M [label="Prior memory"]; + L [label="Load p.x"]; + S [label="Store p.x"]; + L -> M [color=blue]; S -> M [color=blue]; + S -> L [style=dashed,label="execute load first"]; +} diff --git a/chapter10a/docs/example2c.svg b/chapter10a/docs/example2c.svg new file mode 100644 index 00000000..c7b37912 --- /dev/null +++ b/chapter10a/docs/example2c.svg @@ -0,0 +1,50 @@ +<?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 12.2.0 (20241103.1931) + --> +<!-- Title: Memory Pages: 1 --> +<svg width="398pt" height="67pt" + viewBox="0.00 0.00 398.25 67.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 63)"> +<title>Memory</title> +<polygon fill="white" stroke="none" points="-4,4 -4,-63 394.25,-63 394.25,4 -4,4"/> +<!-- M --> +<g id="node1" class="node"> +<title>M</title> +<polygon fill="none" stroke="black" points="93.25,-59 0,-59 0,-23 93.25,-23 93.25,-59"/> +<text text-anchor="middle" x="46.62" y="-35.95" font-family="Times New Roman,serif" font-size="14.00">Prior memory</text> +</g> +<!-- L --> +<g id="node2" class="node"> +<title>L</title> +<polygon fill="none" stroke="black" points="195,-36 130.25,-36 130.25,0 195,0 195,-36"/> +<text text-anchor="middle" x="162.62" y="-12.95" font-family="Times New Roman,serif" font-size="14.00">Load p.x</text> +</g> +<!-- L->M --> +<g id="edge1" class="edge"> +<title>L->M</title> +<path fill="none" stroke="blue" d="M129.87,-24.41C122.08,-25.98 113.51,-27.71 104.92,-29.44"/> +<polygon fill="blue" stroke="blue" points="104.29,-26 95.18,-31.41 105.68,-32.86 104.29,-26"/> +</g> +<!-- S --> +<g id="node3" class="node"> +<title>S</title> +<polygon fill="none" stroke="black" points="390.25,-59 324.75,-59 324.75,-23 390.25,-23 390.25,-59"/> +<text text-anchor="middle" x="357.5" y="-35.95" font-family="Times New Roman,serif" font-size="14.00">Store p.x</text> +</g> +<!-- S->M --> +<g id="edge2" class="edge"> +<title>S->M</title> +<path fill="none" stroke="blue" d="M324.5,-47.66C318.62,-48.63 312.52,-49.48 306.75,-50 237.56,-56.21 157.62,-51.66 105.04,-47.07"/> +<polygon fill="blue" stroke="blue" points="105.37,-43.59 95.1,-46.17 104.74,-50.56 105.37,-43.59"/> +</g> +<!-- S->L --> +<g id="edge3" class="edge"> +<title>S->L</title> +<path fill="none" stroke="black" stroke-dasharray="5,2" d="M324.41,-35.96C318.54,-35.1 312.47,-34.24 306.75,-33.5 273.09,-29.14 234.87,-25.05 206.4,-22.16"/> +<polygon fill="black" stroke="black" points="207.03,-18.71 196.73,-21.19 206.33,-25.67 207.03,-18.71"/> +<text text-anchor="middle" x="259.88" y="-36.7" font-family="Times New Roman,serif" font-size="14.00">execute load first</text> +</g> +</g> +</svg> diff --git a/chapter10a/docs/lattice.gv b/chapter10a/docs/lattice.gv new file mode 100644 index 00000000..733a5287 --- /dev/null +++ b/chapter10a/docs/lattice.gv @@ -0,0 +1,108 @@ +digraph Lattice { + rankdir=TB; + newrank=true; + // Preserve the same-rank ordering edges through cluster layout. + remincross=false; + nodesep=0.22; + ranksep=0.65; + node [shape=ellipse, style=filled, fontname="DejaVu Sans", fontsize=13]; + edge [color="#555555", arrowsize=0.6]; + + // These arrows are lattice-order edges, not use/definition edges. + TOP [label="⊤", fillcolor=black, fontcolor=white]; + BOT [label="⊥", fillcolor=black, fontcolor=white]; + + subgraph cluster_control { + label="Control"; fontname="Arial"; color="#cccccc"; + node [fillcolor="#ffff00"]; + XCTRL [label="⊤:ctrl"]; + CTRL [label="⊥:ctrl"]; + XCTRL -> CTRL; + } + + subgraph cluster_integer { + label="Integer"; fontname="Arial"; color="#cccccc"; + node [fillcolor="#add8e6"]; + INT_TOP [label="⊤:int"]; + INT_BOT [label="⊥:int"]; + INT_NEG [label="… −2, −1"]; + INT_ZERO [label="0"]; + INT_POS [label="1, 2, …"]; + {rank=same; INT_NEG; INT_ZERO; INT_POS;} + INT_NEG -> INT_ZERO -> INT_POS [style=invis]; + INT_TOP -> {INT_NEG INT_ZERO INT_POS}; + {INT_NEG INT_ZERO INT_POS} -> INT_BOT; + } + + subgraph cluster_pointer { + label="Pointer = nullability × struct type"; + fontname="Arial"; color="#cccccc"; + node [fillcolor="#008000", fontcolor=white]; + PTR_TOP [label="⊤:ptr\n[*⊤:struct]"]; + PTR_BOT [label="⊥:ptr\n[*⊥:struct?]"]; + PTR_NULL [label="null"]; + PTR_ANY [label="*⊥:struct\n[ptr any struct]"]; + PTR_PERSON [label="*Person"]; + PTR_S1 [label="*s1"]; + PTR_MORE [label="*s2, …"]; + PTR_PERSON_NIL [label="*Person?"]; + PTR_S1_NIL [label="*s1?"]; + PTR_MORE_NIL [label="*s2?, …"]; + {rank=same; PTR_PERSON; PTR_S1; PTR_MORE; PTR_NULL;} + {rank=same; PTR_ANY; PTR_PERSON_NIL; PTR_S1_NIL; PTR_MORE_NIL;} + PTR_PERSON -> PTR_S1 -> PTR_MORE -> PTR_NULL [style=invis]; + PTR_ANY -> PTR_PERSON_NIL -> PTR_S1_NIL -> PTR_MORE_NIL [style=invis]; + PTR_TOP -> {PTR_PERSON PTR_S1 PTR_MORE PTR_NULL}; + {PTR_PERSON PTR_S1 PTR_MORE} -> PTR_ANY; + PTR_PERSON -> PTR_PERSON_NIL; + PTR_S1 -> PTR_S1_NIL; + PTR_MORE -> PTR_MORE_NIL; + PTR_NULL -> {PTR_PERSON_NIL PTR_S1_NIL PTR_MORE_NIL}; + {PTR_ANY PTR_PERSON_NIL PTR_S1_NIL PTR_MORE_NIL} -> PTR_BOT; + } + + subgraph cluster_struct { + label="Struct\nnamed collection of named scalar types"; + fontname="Arial"; color="#cccccc"; + node [fillcolor="#90ee90"]; + STRUCT_TOP [label="⊤:struct\n[struct top]"]; + STRUCT_BOT [label="⊥:struct\n[struct bottom]"]; + STRUCT_PERSON [label="Person\nage: int", shape=box]; + STRUCT_S1 [label="s1\nx: int", shape=box]; + STRUCT_S2 [label="s2\nx: int, y: *s1", shape=box]; + STRUCT_MORE [label="…", shape=box]; + {rank=same; STRUCT_PERSON; STRUCT_S1; STRUCT_S2; STRUCT_MORE;} + STRUCT_PERSON -> STRUCT_S1 -> STRUCT_S2 -> STRUCT_MORE [style=invis]; + STRUCT_TOP -> {STRUCT_PERSON STRUCT_S1 STRUCT_S2 STRUCT_MORE}; + {STRUCT_PERSON STRUCT_S1 STRUCT_S2 STRUCT_MORE} -> STRUCT_BOT; + } + + subgraph cluster_memory { + label="Memory"; fontname="Arial"; color="#cccccc"; + node [fillcolor="#00008b", fontcolor=white]; + MEM_TOP [label="⊤:mem"]; + MEM_BOT [label="⊥:mem"]; + MEM_TOP -> MEM_BOT; + } + + subgraph cluster_tuple { + label="Tuple\ncollection of types"; + fontname="Arial"; color="#cccccc"; + node [fillcolor="#ffc0cb"]; + TUPLE_TOP [label="⊤:tuple"]; + TUPLE_BOT [label="⊥:tuple"]; + TUPLE1 [label="(type)", shape=box]; + TUPLE2 [label="(type, type)", shape=box]; + TUPLEN [label="(type, …)", shape=box]; + {rank=same; TUPLE1; TUPLE2; TUPLEN;} + TUPLE1 -> TUPLE2 -> TUPLEN [style=invis]; + TUPLE_TOP -> {TUPLE1 TUPLE2 TUPLEN}; + {TUPLE1 TUPLE2 TUPLEN} -> TUPLE_BOT; + } + + TOP -> {XCTRL INT_TOP PTR_TOP STRUCT_TOP MEM_TOP TUPLE_TOP}; + {CTRL INT_BOT PTR_BOT STRUCT_BOT MEM_BOT TUPLE_BOT} -> BOT; + {rank=same; XCTRL; INT_TOP; PTR_TOP; STRUCT_TOP; MEM_TOP; TUPLE_TOP;} + {rank=same; CTRL; INT_BOT; PTR_BOT; STRUCT_BOT; MEM_BOT; TUPLE_BOT;} + XCTRL -> INT_TOP -> PTR_TOP -> STRUCT_TOP -> MEM_TOP -> TUPLE_TOP [style=invis]; +} diff --git a/chapter10a/docs/lattice.svg b/chapter10a/docs/lattice.svg new file mode 100644 index 00000000..24c0de8c --- /dev/null +++ b/chapter10a/docs/lattice.svg @@ -0,0 +1,570 @@ +<?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 12.2.0 (20241103.1931) + --> +<!-- Title: Lattice Pages: 1 --> +<svg width="1647pt" height="555pt" + viewBox="0.00 0.00 1647.00 555.43" 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 551.43)"> +<title>Lattice</title> +<polygon fill="white" stroke="none" points="-4,4 -4,-551.43 1643,-551.43 1643,4 -4,4"/> +<g id="clust1" class="cluster"> +<title>cluster_control</title> +<polygon fill="none" stroke="#cccccc" points="8,-85.99 8,-476.69 98,-476.69 98,-85.99 8,-85.99"/> +<text text-anchor="middle" x="53" y="-459.39" font-family="Arial" font-size="14.00">Control</text> +</g> +<g id="clust2" class="cluster"> +<title>cluster_integer</title> +<polygon fill="none" stroke="#cccccc" points="106,-85.99 106,-476.69 404,-476.69 404,-85.99 106,-85.99"/> +<text text-anchor="middle" x="255" y="-459.39" font-family="Arial" font-size="14.00">Integer</text> +</g> +<g id="clust6" class="cluster"> +<title>cluster_pointer</title> +<polygon fill="none" stroke="#cccccc" points="412,-75 412,-487.68 893,-487.68 893,-75 412,-75"/> +<text text-anchor="middle" x="652.5" y="-470.38" font-family="Arial" font-size="14.00">Pointer = nullability × struct type</text> +</g> +<g id="clust13" class="cluster"> +<title>cluster_struct</title> +<polygon fill="none" stroke="#cccccc" points="901,-75 901,-503.43 1233,-503.43 1233,-75 901,-75"/> +<text text-anchor="middle" x="1067" y="-486.13" font-family="Arial" font-size="14.00">Struct</text> +<text text-anchor="middle" x="1067" y="-470.38" font-family="Arial" font-size="14.00">named collection of named scalar types</text> +</g> +<g id="clust17" class="cluster"> +<title>cluster_memory</title> +<polygon fill="none" stroke="#cccccc" points="1241,-85.99 1241,-476.69 1347,-476.69 1347,-85.99 1241,-85.99"/> +<text text-anchor="middle" x="1294" y="-459.39" font-family="Arial" font-size="14.00">Memory</text> +</g> +<g id="clust18" class="cluster"> +<title>cluster_tuple</title> +<polygon fill="none" stroke="#cccccc" points="1355,-85.99 1355,-492.44 1631,-492.44 1631,-85.99 1355,-85.99"/> +<text text-anchor="middle" x="1493" y="-475.14" font-family="Arial" font-size="14.00">Tuple</text> +<text text-anchor="middle" x="1493" y="-459.39" font-family="Arial" font-size="14.00">collection of types</text> +</g> +<!-- TOP --> +<g id="node1" class="node"> +<title>TOP</title> +<ellipse fill="black" stroke="black" cx="845" cy="-529.43" rx="27" ry="18"/> +<text text-anchor="middle" x="845" y="-525.33" font-family="DejaVu Sans" font-size="13.00" fill="white">⊤</text> +</g> +<!-- XCTRL --> +<g id="node3" class="node"> +<title>XCTRL</title> +<ellipse fill="#ffff00" stroke="black" cx="53" cy="-426.94" rx="36.51" ry="18"/> +<text text-anchor="middle" x="53" y="-422.84" font-family="DejaVu Sans" font-size="13.00">⊤:ctrl</text> +</g> +<!-- TOP->XCTRL --> +<g id="edge53" class="edge"> +<title>TOP->XCTRL</title> +<path fill="none" stroke="#555555" d="M817.66,-528.22C687.28,-527.11 133.71,-521.35 102,-503.43 82.18,-492.23 69.06,-469.34 61.42,-451.68"/> +<polygon fill="#555555" stroke="#555555" points="63.46,-451.12 59.24,-446.37 59.57,-452.72 63.46,-451.12"/> +</g> +<!-- INT_TOP --> +<g id="node5" class="node"> +<title>INT_TOP</title> +<ellipse fill="#add8e6" stroke="black" cx="353" cy="-426.94" rx="33.44" ry="18"/> +<text text-anchor="middle" x="353" y="-422.84" font-family="DejaVu Sans" font-size="13.00">⊤:int</text> +</g> +<!-- TOP->INT_TOP --> +<g id="edge54" class="edge"> +<title>TOP->INT_TOP</title> +<path fill="none" stroke="#555555" d="M817.72,-528.22C727.72,-527.33 443.86,-522.98 408,-503.43 387.26,-492.12 372.32,-469.24 363.29,-451.62"/> +<polygon fill="#555555" stroke="#555555" points="365.2,-450.74 360.67,-446.28 361.42,-452.59 365.2,-450.74"/> +</g> +<!-- PTR_TOP --> +<g id="node10" class="node"> +<title>PTR_TOP</title> +<ellipse fill="#008000" stroke="black" cx="685" cy="-426.94" rx="61.7" ry="28.99"/> +<text text-anchor="middle" x="685" y="-431.09" font-family="DejaVu Sans" font-size="13.00" fill="white">⊤:ptr</text> +<text text-anchor="middle" x="685" y="-414.59" font-family="DejaVu Sans" font-size="13.00" fill="white">[*⊤:struct]</text> +</g> +<!-- TOP->PTR_TOP --> +<g id="edge55" class="edge"> +<title>TOP->PTR_TOP</title> +<path fill="none" stroke="#555555" d="M825.7,-516.31C801.54,-501.14 759.48,-474.72 727.62,-454.71"/> +<polygon fill="#555555" stroke="#555555" points="728.87,-453.01 722.67,-451.6 726.64,-456.57 728.87,-453.01"/> +</g> +<!-- STRUCT_TOP --> +<g id="node20" class="node"> +<title>STRUCT_TOP</title> +<ellipse fill="#90ee90" stroke="black" cx="996" cy="-426.94" rx="64.35" ry="28.99"/> +<text text-anchor="middle" x="996" y="-431.09" font-family="DejaVu Sans" font-size="13.00">⊤:struct</text> +<text text-anchor="middle" x="996" y="-414.59" font-family="DejaVu Sans" font-size="13.00">[struct top]</text> +</g> +<!-- TOP->STRUCT_TOP --> +<g id="edge56" class="edge"> +<title>TOP->STRUCT_TOP</title> +<path fill="none" stroke="#555555" d="M867.28,-518.93C876.68,-514.61 887.61,-509.19 897,-503.43 919.21,-489.82 942.45,-472.29 960.83,-457.55"/> +<polygon fill="#555555" stroke="#555555" points="961.85,-459.43 965.2,-454.02 959.21,-456.16 961.85,-459.43"/> +</g> +<!-- MEM_TOP --> +<g id="node26" class="node"> +<title>MEM_TOP</title> +<ellipse fill="#00008b" stroke="black" cx="1294" cy="-426.94" rx="44.7" ry="18"/> +<text text-anchor="middle" x="1294" y="-422.84" font-family="DejaVu Sans" font-size="13.00" fill="white">⊤:mem</text> +</g> +<!-- TOP->MEM_TOP --> +<g id="edge57" class="edge"> +<title>TOP->MEM_TOP</title> +<path fill="none" stroke="#555555" d="M872.35,-528C956.06,-526.49 1205.1,-520.6 1237,-503.43 1258.25,-491.99 1273.89,-468.81 1283.35,-451.14"/> +<polygon fill="#555555" stroke="#555555" points="1284.99,-452.55 1285.86,-446.25 1281.25,-450.62 1284.99,-452.55"/> +</g> +<!-- TUPLE_TOP --> +<g id="node28" class="node"> +<title>TUPLE_TOP</title> +<ellipse fill="#ffc0cb" stroke="black" cx="1408" cy="-426.94" rx="44.7" ry="18"/> +<text text-anchor="middle" x="1408" y="-422.84" font-family="DejaVu Sans" font-size="13.00">⊤:tuple</text> +</g> +<!-- TOP->TUPLE_TOP --> +<g id="edge58" class="edge"> +<title>TOP->TUPLE_TOP</title> +<path fill="none" stroke="#555555" d="M872.34,-528.42C971.49,-528.22 1308.63,-525.85 1351,-503.43 1372.33,-492.14 1387.95,-468.94 1397.39,-451.22"/> +<polygon fill="#555555" stroke="#555555" points="1399.04,-452.62 1399.9,-446.32 1395.3,-450.7 1399.04,-452.62"/> +</g> +<!-- BOT --> +<g id="node2" class="node"> +<title>BOT</title> +<ellipse fill="black" stroke="black" cx="884" cy="-18" rx="27" ry="18"/> +<text text-anchor="middle" x="884" y="-13.9" font-family="DejaVu Sans" font-size="13.00" fill="white">⊥</text> +</g> +<!-- CTRL --> +<g id="node4" class="node"> +<title>CTRL</title> +<ellipse fill="#ffff00" stroke="black" cx="53" cy="-111.99" rx="36.51" ry="18"/> +<text text-anchor="middle" x="53" y="-107.89" font-family="DejaVu Sans" font-size="13.00">⊥:ctrl</text> +</g> +<!-- XCTRL->CTRL --> +<g id="edge1" class="edge"> +<title>XCTRL->CTRL</title> +<path fill="none" stroke="#555555" d="M53,-408.67C53,-356.2 53,-198.53 53,-137.49"/> +<polygon fill="#555555" stroke="#555555" points="55.1,-137.72 53,-131.72 50.9,-137.72 55.1,-137.72"/> +</g> +<!-- XCTRL->INT_TOP --> +<!-- CTRL->BOT --> +<g id="edge59" class="edge"> +<title>CTRL->BOT</title> +<path fill="none" stroke="#555555" d="M69.29,-95.39C78.22,-87.83 89.91,-79.45 102,-75 242.26,-23.4 717.31,-19.12 849.57,-18.95"/> +<polygon fill="#555555" stroke="#555555" points="849.19,-21.05 855.19,-18.94 849.18,-16.85 849.19,-21.05"/> +</g> +<!-- INT_NEG --> +<g id="node7" class="node"> +<title>INT_NEG</title> +<ellipse fill="#add8e6" stroke="black" cx="169" cy="-216.97" rx="55.45" ry="18"/> +<text text-anchor="middle" x="169" y="-212.87" font-family="DejaVu Sans" font-size="13.00">… −2, −1</text> +</g> +<!-- INT_TOP->INT_NEG --> +<g id="edge4" class="edge"> +<title>INT_TOP->INT_NEG</title> +<path fill="none" stroke="#555555" d="M339.17,-410.31C306.78,-373.7 225.91,-282.29 188.52,-240.04"/> +<polygon fill="#555555" stroke="#555555" points="190.31,-238.89 184.76,-235.78 187.16,-241.67 190.31,-238.89"/> +</g> +<!-- INT_ZERO --> +<g id="node8" class="node"> +<title>INT_ZERO</title> +<ellipse fill="#add8e6" stroke="black" cx="267" cy="-216.97" rx="27" ry="18"/> +<text text-anchor="middle" x="267" y="-212.87" font-family="DejaVu Sans" font-size="13.00">0</text> +</g> +<!-- INT_TOP->INT_ZERO --> +<g id="edge5" class="edge"> +<title>INT_TOP->INT_ZERO</title> +<path fill="none" stroke="#555555" d="M345.96,-408.92C330.64,-371.87 294.24,-283.85 276.74,-241.52"/> +<polygon fill="#555555" stroke="#555555" points="278.71,-240.79 274.47,-236.05 274.83,-242.39 278.71,-240.79"/> +</g> +<!-- INT_POS --> +<g id="node9" class="node"> +<title>INT_POS</title> +<ellipse fill="#add8e6" stroke="black" cx="353" cy="-216.97" rx="43.16" ry="18"/> +<text text-anchor="middle" x="353" y="-212.87" font-family="DejaVu Sans" font-size="13.00">1, 2, …</text> +</g> +<!-- INT_TOP->INT_POS --> +<g id="edge6" class="edge"> +<title>INT_TOP->INT_POS</title> +<path fill="none" stroke="#555555" d="M353,-408.57C353,-371.71 353,-285.59 353,-242.89"/> +<polygon fill="#555555" stroke="#555555" points="355.1,-242.96 353,-236.96 350.9,-242.96 355.1,-242.96"/> +</g> +<!-- INT_TOP->PTR_TOP --> +<!-- INT_BOT --> +<g id="node6" class="node"> +<title>INT_BOT</title> +<ellipse fill="#add8e6" stroke="black" cx="310" cy="-111.99" rx="33.44" ry="18"/> +<text text-anchor="middle" x="310" y="-107.89" font-family="DejaVu Sans" font-size="13.00">⊥:int</text> +</g> +<!-- INT_BOT->BOT --> +<g id="edge60" class="edge"> +<title>INT_BOT->BOT</title> +<path fill="none" stroke="#555555" d="M335.28,-99.84C354.78,-91.7 382.61,-81.07 408,-75 570.29,-36.22 770.4,-23.83 849.5,-20.29"/> +<polygon fill="#555555" stroke="#555555" points="849.36,-22.39 855.26,-20.04 849.17,-18.2 849.36,-22.39"/> +</g> +<!-- INT_NEG->INT_BOT --> +<g id="edge7" class="edge"> +<title>INT_NEG->INT_BOT</title> +<path fill="none" stroke="#555555" d="M190.72,-200.11C216.15,-181.54 258.17,-150.85 284.95,-131.29"/> +<polygon fill="#555555" stroke="#555555" points="285.92,-133.18 289.52,-127.95 283.44,-129.79 285.92,-133.18"/> +</g> +<!-- INT_NEG->INT_ZERO --> +<!-- INT_ZERO->INT_BOT --> +<g id="edge8" class="edge"> +<title>INT_ZERO->INT_BOT</title> +<path fill="none" stroke="#555555" d="M273.91,-199.43C281.01,-182.41 292.14,-155.76 300.16,-136.55"/> +<polygon fill="#555555" stroke="#555555" points="302.01,-137.58 302.38,-131.24 298.13,-135.96 302.01,-137.58"/> +</g> +<!-- INT_ZERO->INT_POS --> +<!-- INT_POS->INT_BOT --> +<g id="edge9" class="edge"> +<title>INT_POS->INT_BOT</title> +<path fill="none" stroke="#555555" d="M345.9,-198.97C338.83,-182.05 327.93,-155.94 319.99,-136.93"/> +<polygon fill="#555555" stroke="#555555" points="321.94,-136.15 317.69,-131.42 318.07,-137.76 321.94,-136.15"/> +</g> +<!-- PTR_NULL --> +<g id="node12" class="node"> +<title>PTR_NULL</title> +<ellipse fill="#008000" stroke="black" cx="808" cy="-321.96" rx="66.47" ry="28.99"/> +<text text-anchor="middle" x="808" y="-326.11" font-family="DejaVu Sans" font-size="13.00" fill="white">null</text> +<text text-anchor="middle" x="808" y="-309.61" font-family="DejaVu Sans" font-size="13.00" fill="white">[*⊤:struct?]</text> +</g> +<!-- PTR_TOP->PTR_NULL --> +<g id="edge16" class="edge"> +<title>PTR_TOP->PTR_NULL</title> +<path fill="none" stroke="#555555" d="M714.46,-401.27C731.85,-386.71 753.98,-368.18 772.35,-352.8"/> +<polygon fill="#555555" stroke="#555555" points="773.51,-354.57 776.76,-349.11 770.82,-351.35 773.51,-354.57"/> +</g> +<!-- PTR_PERSON --> +<g id="node14" class="node"> +<title>PTR_PERSON</title> +<ellipse fill="#008000" stroke="black" cx="512" cy="-321.96" rx="45.72" ry="18"/> +<text text-anchor="middle" x="512" y="-317.86" font-family="DejaVu Sans" font-size="13.00" fill="white">*Person</text> +</g> +<!-- PTR_TOP->PTR_PERSON --> +<g id="edge17" class="edge"> +<title>PTR_TOP->PTR_PERSON</title> +<path fill="none" stroke="#555555" d="M647.46,-403.59C616.14,-384.95 572.05,-358.7 542.78,-341.28"/> +<polygon fill="#555555" stroke="#555555" points="543.94,-339.52 537.71,-338.26 541.79,-343.13 543.94,-339.52"/> +</g> +<!-- PTR_S1 --> +<g id="node15" class="node"> +<title>PTR_S1</title> +<ellipse fill="#008000" stroke="black" cx="601" cy="-321.96" rx="27" ry="18"/> +<text text-anchor="middle" x="601" y="-317.86" font-family="DejaVu Sans" font-size="13.00" fill="white">*s1</text> +</g> +<!-- PTR_TOP->PTR_S1 --> +<g id="edge18" class="edge"> +<title>PTR_TOP->PTR_S1</title> +<path fill="none" stroke="#555555" d="M663.37,-399.42C649.25,-382.12 631.09,-359.85 618.01,-343.81"/> +<polygon fill="#555555" stroke="#555555" points="619.81,-342.7 614.39,-339.38 616.56,-345.36 619.81,-342.7"/> +</g> +<!-- PTR_MORE --> +<g id="node16" class="node"> +<title>PTR_MORE</title> +<ellipse fill="#008000" stroke="black" cx="685" cy="-321.96" rx="40.6" ry="18"/> +<text text-anchor="middle" x="685" y="-317.86" font-family="DejaVu Sans" font-size="13.00" fill="white">*s2, …</text> +</g> +<!-- PTR_TOP->PTR_MORE --> +<g id="edge19" class="edge"> +<title>PTR_TOP->PTR_MORE</title> +<path fill="none" stroke="#555555" d="M685,-397.53C685,-381.91 685,-362.73 685,-347.73"/> +<polygon fill="#555555" stroke="#555555" points="687.1,-347.82 685,-341.82 682.9,-347.82 687.1,-347.82"/> +</g> +<!-- PTR_TOP->STRUCT_TOP --> +<!-- PTR_BOT --> +<g id="node11" class="node"> +<title>PTR_BOT</title> +<ellipse fill="#008000" stroke="black" cx="748" cy="-111.99" rx="66.47" ry="28.99"/> +<text text-anchor="middle" x="748" y="-116.14" font-family="DejaVu Sans" font-size="13.00" fill="white">⊥:ptr</text> +<text text-anchor="middle" x="748" y="-99.64" font-family="DejaVu Sans" font-size="13.00" fill="white">[*⊥:struct?]</text> +</g> +<!-- PTR_BOT->BOT --> +<g id="edge61" class="edge"> +<title>PTR_BOT->BOT</title> +<path fill="none" stroke="#555555" d="M783.38,-87.06C807.3,-70.88 838.29,-49.92 859.52,-35.56"/> +<polygon fill="#555555" stroke="#555555" points="860.46,-37.46 864.25,-32.36 858.1,-33.98 860.46,-37.46"/> +</g> +<!-- PTR_PERSON_NIL --> +<g id="node17" class="node"> +<title>PTR_PERSON_NIL</title> +<ellipse fill="#008000" stroke="black" cx="651" cy="-216.97" rx="50.33" ry="18"/> +<text text-anchor="middle" x="651" y="-212.87" font-family="DejaVu Sans" font-size="13.00" fill="white">*Person?</text> +</g> +<!-- PTR_NULL->PTR_PERSON_NIL --> +<g id="edge26" class="edge"> +<title>PTR_NULL->PTR_PERSON_NIL</title> +<path fill="none" stroke="#555555" d="M771.98,-297.33C744.16,-279.08 706.14,-254.14 680.2,-237.12"/> +<polygon fill="#555555" stroke="#555555" points="681.63,-235.56 675.47,-234.02 679.33,-239.07 681.63,-235.56"/> +</g> +<!-- PTR_S1_NIL --> +<g id="node18" class="node"> +<title>PTR_S1_NIL</title> +<ellipse fill="#008000" stroke="black" cx="748" cy="-216.97" rx="30.37" ry="18"/> +<text text-anchor="middle" x="748" y="-212.87" font-family="DejaVu Sans" font-size="13.00" fill="white">*s1?</text> +</g> +<!-- PTR_NULL->PTR_S1_NIL --> +<g id="edge27" class="edge"> +<title>PTR_NULL->PTR_S1_NIL</title> +<path fill="none" stroke="#555555" d="M791.92,-293.36C782.32,-276.89 770.28,-256.22 761.24,-240.69"/> +<polygon fill="#555555" stroke="#555555" points="763.16,-239.82 758.32,-235.69 759.53,-241.93 763.16,-239.82"/> +</g> +<!-- PTR_MORE_NIL --> +<g id="node19" class="node"> +<title>PTR_MORE_NIL</title> +<ellipse fill="#008000" stroke="black" cx="840" cy="-216.97" rx="45.21" ry="18"/> +<text text-anchor="middle" x="840" y="-212.87" font-family="DejaVu Sans" font-size="13.00" fill="white">*s2?, …</text> +</g> +<!-- PTR_NULL->PTR_MORE_NIL --> +<g id="edge28" class="edge"> +<title>PTR_NULL->PTR_MORE_NIL</title> +<path fill="none" stroke="#555555" d="M816.74,-292.82C821.67,-276.95 827.77,-257.33 832.48,-242.16"/> +<polygon fill="#555555" stroke="#555555" points="834.45,-242.91 834.23,-236.56 830.44,-241.66 834.45,-242.91"/> +</g> +<!-- PTR_ANY --> +<g id="node13" class="node"> +<title>PTR_ANY</title> +<ellipse fill="#008000" stroke="black" cx="502" cy="-216.97" rx="82.38" ry="28.99"/> +<text text-anchor="middle" x="502" y="-221.12" font-family="DejaVu Sans" font-size="13.00" fill="white">*⊥:struct</text> +<text text-anchor="middle" x="502" y="-204.62" font-family="DejaVu Sans" font-size="13.00" fill="white">[ptr any struct]</text> +</g> +<!-- PTR_ANY->PTR_BOT --> +<g id="edge29" class="edge"> +<title>PTR_ANY->PTR_BOT</title> +<path fill="none" stroke="#555555" d="M554.17,-194.13C595.32,-176.91 652.51,-152.97 694.1,-135.56"/> +<polygon fill="#555555" stroke="#555555" points="694.78,-137.55 699.5,-133.29 693.15,-133.67 694.78,-137.55"/> +</g> +<!-- PTR_ANY->PTR_PERSON_NIL --> +<!-- PTR_PERSON->PTR_ANY --> +<g id="edge20" class="edge"> +<title>PTR_PERSON->PTR_ANY</title> +<path fill="none" stroke="#555555" d="M510.3,-303.49C508.98,-289.83 507.09,-270.39 505.45,-253.5"/> +<polygon fill="#555555" stroke="#555555" points="507.58,-253.71 504.91,-247.94 503.4,-254.12 507.58,-253.71"/> +</g> +<!-- PTR_PERSON->PTR_S1 --> +<!-- PTR_PERSON->PTR_PERSON_NIL --> +<g id="edge23" class="edge"> +<title>PTR_PERSON->PTR_PERSON_NIL</title> +<path fill="none" stroke="#555555" d="M532.81,-305.54C557.17,-287.49 597.57,-257.56 624.25,-237.8"/> +<polygon fill="#555555" stroke="#555555" points="625.26,-239.66 628.83,-234.4 622.76,-236.28 625.26,-239.66"/> +</g> +<!-- PTR_S1->PTR_ANY --> +<g id="edge21" class="edge"> +<title>PTR_S1->PTR_ANY</title> +<path fill="none" stroke="#555555" d="M586.82,-306.2C572.74,-291.56 550.72,-268.65 532.64,-249.85"/> +<polygon fill="#555555" stroke="#555555" points="534.38,-248.62 528.7,-245.75 531.35,-251.53 534.38,-248.62"/> +</g> +<!-- PTR_S1->PTR_MORE --> +<!-- PTR_S1->PTR_S1_NIL --> +<g id="edge24" class="edge"> +<title>PTR_S1->PTR_S1_NIL</title> +<path fill="none" stroke="#555555" d="M617.46,-307.22C623.23,-302.56 629.81,-297.42 636,-292.97 667.33,-270.44 677.09,-267.67 709,-245.97 713.75,-242.74 718.77,-239.22 723.56,-235.8"/> +<polygon fill="#555555" stroke="#555555" points="724.61,-237.64 728.26,-232.43 722.16,-234.22 724.61,-237.64"/> +</g> +<!-- PTR_MORE->PTR_NULL --> +<!-- PTR_MORE->PTR_ANY --> +<g id="edge22" class="edge"> +<title>PTR_MORE->PTR_ANY</title> +<path fill="none" stroke="#555555" d="M660.85,-307.1C652.94,-302.58 644.1,-297.55 636,-292.97 608.08,-277.18 576.81,-259.67 551.5,-245.54"/> +<polygon fill="#555555" stroke="#555555" points="552.79,-243.85 546.52,-242.76 550.74,-247.52 552.79,-243.85"/> +</g> +<!-- PTR_MORE->PTR_MORE_NIL --> +<g id="edge25" class="edge"> +<title>PTR_MORE->PTR_MORE_NIL</title> +<path fill="none" stroke="#555555" d="M706.88,-306.42C734.22,-288.25 781.1,-257.11 811.3,-237.04"/> +<polygon fill="#555555" stroke="#555555" points="812.38,-238.85 816.22,-233.78 810.06,-235.35 812.38,-238.85"/> +</g> +<!-- PTR_PERSON_NIL->PTR_BOT --> +<g id="edge30" class="edge"> +<title>PTR_PERSON_NIL->PTR_BOT</title> +<path fill="none" stroke="#555555" d="M666.58,-199.43C680.58,-184.56 701.51,-162.35 718.65,-144.16"/> +<polygon fill="#555555" stroke="#555555" points="719.75,-146.05 722.33,-140.24 716.69,-143.17 719.75,-146.05"/> +</g> +<!-- PTR_PERSON_NIL->PTR_S1_NIL --> +<!-- PTR_S1_NIL->PTR_BOT --> +<g id="edge31" class="edge"> +<title>PTR_S1_NIL->PTR_BOT</title> +<path fill="none" stroke="#555555" d="M748,-198.5C748,-184.97 748,-165.76 748,-148.97"/> +<polygon fill="#555555" stroke="#555555" points="750.1,-148.97 748,-142.97 745.9,-148.97 750.1,-148.97"/> +</g> +<!-- PTR_S1_NIL->PTR_MORE_NIL --> +<!-- PTR_MORE_NIL->PTR_BOT --> +<g id="edge32" class="edge"> +<title>PTR_MORE_NIL->PTR_BOT</title> +<path fill="none" stroke="#555555" d="M825.62,-199.88C812.53,-185.22 792.8,-163.14 776.51,-144.9"/> +<polygon fill="#555555" stroke="#555555" points="778.14,-143.58 772.58,-140.51 775.01,-146.38 778.14,-143.58"/> +</g> +<!-- STRUCT_PERSON --> +<g id="node22" class="node"> +<title>STRUCT_PERSON</title> +<polygon fill="#90ee90" stroke="black" points="976.5,-237.47 909.5,-237.47 909.5,-196.47 976.5,-196.47 976.5,-237.47"/> +<text text-anchor="middle" x="943" y="-221.12" font-family="DejaVu Sans" font-size="13.00">Person</text> +<text text-anchor="middle" x="943" y="-204.62" font-family="DejaVu Sans" font-size="13.00">age: int</text> +</g> +<!-- STRUCT_TOP->STRUCT_PERSON --> +<g id="edge36" class="edge"> +<title>STRUCT_TOP->STRUCT_PERSON</title> +<path fill="none" stroke="#555555" d="M988.85,-397.89C978.61,-357.71 959.89,-284.26 949.83,-244.77"/> +<polygon fill="#555555" stroke="#555555" points="951.95,-244.58 948.43,-239.28 947.88,-245.61 951.95,-244.58"/> +</g> +<!-- STRUCT_S1 --> +<g id="node23" class="node"> +<title>STRUCT_S1</title> +<polygon fill="#90ee90" stroke="black" points="1047,-237.47 993,-237.47 993,-196.47 1047,-196.47 1047,-237.47"/> +<text text-anchor="middle" x="1020" y="-221.12" font-family="DejaVu Sans" font-size="13.00">s1</text> +<text text-anchor="middle" x="1020" y="-204.62" font-family="DejaVu Sans" font-size="13.00">x: int</text> +</g> +<!-- STRUCT_TOP->STRUCT_S1 --> +<g id="edge37" class="edge"> +<title>STRUCT_TOP->STRUCT_S1</title> +<path fill="none" stroke="#555555" d="M999.24,-397.89C1003.85,-357.88 1012.28,-284.87 1016.85,-245.27"/> +<polygon fill="#555555" stroke="#555555" points="1018.94,-245.52 1017.54,-239.32 1014.76,-245.04 1018.94,-245.52"/> +</g> +<!-- STRUCT_S2 --> +<g id="node24" class="node"> +<title>STRUCT_S2</title> +<polygon fill="#90ee90" stroke="black" points="1154.88,-237.47 1063.12,-237.47 1063.12,-196.47 1154.88,-196.47 1154.88,-237.47"/> +<text text-anchor="middle" x="1109" y="-221.12" font-family="DejaVu Sans" font-size="13.00">s2</text> +<text text-anchor="middle" x="1109" y="-204.62" font-family="DejaVu Sans" font-size="13.00">x: int, y: int</text> +</g> +<!-- STRUCT_TOP->STRUCT_S2 --> +<g id="edge38" class="edge"> +<title>STRUCT_TOP->STRUCT_S2</title> +<path fill="none" stroke="#555555" d="M1011.01,-398.32C1032.86,-358.11 1073.16,-283.94 1094.64,-244.41"/> +<polygon fill="#555555" stroke="#555555" points="1096.43,-245.51 1097.45,-239.24 1092.74,-243.51 1096.43,-245.51"/> +</g> +<!-- STRUCT_MORE --> +<g id="node25" class="node"> +<title>STRUCT_MORE</title> +<polygon fill="#90ee90" stroke="black" points="1225,-234.97 1171,-234.97 1171,-198.97 1225,-198.97 1225,-234.97"/> +<text text-anchor="middle" x="1198" y="-212.87" font-family="DejaVu Sans" font-size="13.00">…</text> +</g> +<!-- STRUCT_TOP->STRUCT_MORE --> +<g id="edge39" class="edge"> +<title>STRUCT_TOP->STRUCT_MORE</title> +<path fill="none" stroke="#555555" d="M1021.18,-400.02C1061.16,-358.86 1138.49,-279.24 1176.23,-240.39"/> +<polygon fill="#555555" stroke="#555555" points="1177.33,-242.26 1180.01,-236.5 1174.32,-239.34 1177.33,-242.26"/> +</g> +<!-- STRUCT_TOP->MEM_TOP --> +<!-- STRUCT_BOT --> +<g id="node21" class="node"> +<title>STRUCT_BOT</title> +<ellipse fill="#90ee90" stroke="black" cx="1020" cy="-111.99" rx="82.91" ry="28.99"/> +<text text-anchor="middle" x="1020" y="-116.14" font-family="DejaVu Sans" font-size="13.00">⊥:struct</text> +<text text-anchor="middle" x="1020" y="-99.64" font-family="DejaVu Sans" font-size="13.00">[struct bottom]</text> +</g> +<!-- STRUCT_BOT->BOT --> +<g id="edge62" class="edge"> +<title>STRUCT_BOT->BOT</title> +<path fill="none" stroke="#555555" d="M982.84,-85.85C959.17,-69.85 929.19,-49.57 908.49,-35.56"/> +<polygon fill="#555555" stroke="#555555" points="909.78,-33.9 903.63,-32.28 907.42,-37.38 909.78,-33.9"/> +</g> +<!-- STRUCT_PERSON->STRUCT_BOT --> +<g id="edge40" class="edge"> +<title>STRUCT_PERSON->STRUCT_BOT</title> +<path fill="none" stroke="#555555" d="M957.85,-196.12C968.43,-181.96 982.92,-162.59 995.24,-146.11"/> +<polygon fill="#555555" stroke="#555555" points="996.85,-147.46 998.76,-141.4 993.49,-144.94 996.85,-147.46"/> +</g> +<!-- STRUCT_PERSON->STRUCT_S1 --> +<!-- STRUCT_S1->STRUCT_BOT --> +<g id="edge41" class="edge"> +<title>STRUCT_S1->STRUCT_BOT</title> +<path fill="none" stroke="#555555" d="M1020,-196.12C1020,-182.64 1020,-164.43 1020,-148.49"/> +<polygon fill="#555555" stroke="#555555" points="1022.1,-148.87 1020,-142.87 1017.9,-148.87 1022.1,-148.87"/> +</g> +<!-- STRUCT_S1->STRUCT_S2 --> +<!-- STRUCT_S2->STRUCT_BOT --> +<g id="edge42" class="edge"> +<title>STRUCT_S2->STRUCT_BOT</title> +<path fill="none" stroke="#555555" d="M1091.84,-196.12C1079.5,-181.83 1062.57,-162.25 1048.25,-145.68"/> +<polygon fill="#555555" stroke="#555555" points="1050,-144.5 1044.49,-141.33 1046.82,-147.24 1050,-144.5"/> +</g> +<!-- STRUCT_S2->STRUCT_MORE --> +<!-- STRUCT_MORE->STRUCT_BOT --> +<g id="edge43" class="edge"> +<title>STRUCT_MORE->STRUCT_BOT</title> +<path fill="none" stroke="#555555" d="M1177.76,-198.72C1173.05,-195 1167.95,-191.22 1163,-187.98 1135.19,-169.81 1102.49,-152.39 1075.38,-138.93"/> +<polygon fill="#555555" stroke="#555555" points="1076.64,-137.21 1070.33,-136.44 1074.78,-140.97 1076.64,-137.21"/> +</g> +<!-- MEM_BOT --> +<g id="node27" class="node"> +<title>MEM_BOT</title> +<ellipse fill="#00008b" stroke="black" cx="1294" cy="-111.99" rx="44.7" ry="18"/> +<text text-anchor="middle" x="1294" y="-107.89" font-family="DejaVu Sans" font-size="13.00" fill="white">⊥:mem</text> +</g> +<!-- MEM_TOP->MEM_BOT --> +<g id="edge44" class="edge"> +<title>MEM_TOP->MEM_BOT</title> +<path fill="none" stroke="#555555" d="M1294,-408.67C1294,-356.2 1294,-198.53 1294,-137.49"/> +<polygon fill="#555555" stroke="#555555" points="1296.1,-137.72 1294,-131.72 1291.9,-137.72 1296.1,-137.72"/> +</g> +<!-- MEM_TOP->TUPLE_TOP --> +<!-- MEM_BOT->BOT --> +<g id="edge63" class="edge"> +<title>MEM_BOT->BOT</title> +<path fill="none" stroke="#555555" d="M1273.96,-95.5C1263.49,-88.17 1250.14,-79.95 1237,-75 1125.68,-33.06 983.13,-22.53 918.55,-19.89"/> +<polygon fill="#555555" stroke="#555555" points="918.65,-17.79 912.58,-19.66 918.49,-21.99 918.65,-17.79"/> +</g> +<!-- TUPLE1 --> +<g id="node30" class="node"> +<title>TUPLE1</title> +<polygon fill="#ffc0cb" stroke="black" points="1418.88,-339.96 1363.12,-339.96 1363.12,-303.96 1418.88,-303.96 1418.88,-339.96"/> +<text text-anchor="middle" x="1391" y="-317.86" font-family="DejaVu Sans" font-size="13.00">(type)</text> +</g> +<!-- TUPLE_TOP->TUPLE1 --> +<g id="edge47" class="edge"> +<title>TUPLE_TOP->TUPLE1</title> +<path fill="none" stroke="#555555" d="M1405.19,-408.93C1402.44,-392.26 1398.22,-366.67 1395.09,-347.74"/> +<polygon fill="#555555" stroke="#555555" points="1397.16,-347.4 1394.11,-341.83 1393.02,-348.09 1397.16,-347.4"/> +</g> +<!-- TUPLE2 --> +<g id="node31" class="node"> +<title>TUPLE2</title> +<polygon fill="#ffc0cb" stroke="black" points="1529,-339.96 1435,-339.96 1435,-303.96 1529,-303.96 1529,-339.96"/> +<text text-anchor="middle" x="1482" y="-317.86" font-family="DejaVu Sans" font-size="13.00">(type, type)</text> +</g> +<!-- TUPLE_TOP->TUPLE2 --> +<g id="edge48" class="edge"> +<title>TUPLE_TOP->TUPLE2</title> +<path fill="none" stroke="#555555" d="M1419.89,-409.39C1432.2,-392.26 1451.54,-365.35 1465.37,-346.1"/> +<polygon fill="#555555" stroke="#555555" points="1466.97,-347.48 1468.76,-341.38 1463.56,-345.02 1466.97,-347.48"/> +</g> +<!-- TUPLEN --> +<g id="node32" class="node"> +<title>TUPLEN</title> +<polygon fill="#ffc0cb" stroke="black" points="1622.75,-339.96 1545.25,-339.96 1545.25,-303.96 1622.75,-303.96 1622.75,-339.96"/> +<text text-anchor="middle" x="1584" y="-317.86" font-family="DejaVu Sans" font-size="13.00">(type, …)</text> +</g> +<!-- TUPLE_TOP->TUPLEN --> +<g id="edge49" class="edge"> +<title>TUPLE_TOP->TUPLEN</title> +<path fill="none" stroke="#555555" d="M1432.78,-411.64C1458.78,-396.61 1500.83,-372.23 1537,-350.95 1540.81,-348.7 1544.8,-346.35 1548.76,-344"/> +<polygon fill="#555555" stroke="#555555" points="1549.56,-345.97 1553.64,-341.1 1547.41,-342.36 1549.56,-345.97"/> +</g> +<!-- TUPLE_BOT --> +<g id="node29" class="node"> +<title>TUPLE_BOT</title> +<ellipse fill="#ffc0cb" stroke="black" cx="1445" cy="-111.99" rx="44.7" ry="18"/> +<text text-anchor="middle" x="1445" y="-107.89" font-family="DejaVu Sans" font-size="13.00">⊥:tuple</text> +</g> +<!-- TUPLE_BOT->BOT --> +<g id="edge64" class="edge"> +<title>TUPLE_BOT->BOT</title> +<path fill="none" stroke="#555555" d="M1416.38,-97.91C1398.05,-90.08 1373.56,-80.58 1351,-75 1192.43,-35.77 996.51,-23.66 918.45,-20.23"/> +<polygon fill="#555555" stroke="#555555" points="918.86,-18.15 912.78,-19.99 918.69,-22.35 918.86,-18.15"/> +</g> +<!-- TUPLE1->TUPLE_BOT --> +<g id="edge50" class="edge"> +<title>TUPLE1->TUPLE_BOT</title> +<path fill="none" stroke="#555555" d="M1395.51,-303.58C1405.12,-266.56 1427.65,-179.82 1438.68,-137.32"/> +<polygon fill="#555555" stroke="#555555" points="1440.68,-137.97 1440.16,-131.63 1436.62,-136.91 1440.68,-137.97"/> +</g> +<!-- TUPLE1->TUPLE2 --> +<!-- TUPLE2->TUPLE_BOT --> +<g id="edge51" class="edge"> +<title>TUPLE2->TUPLE_BOT</title> +<path fill="none" stroke="#555555" d="M1478.91,-303.58C1472.32,-266.56 1456.89,-179.82 1449.33,-137.32"/> +<polygon fill="#555555" stroke="#555555" points="1451.44,-137.2 1448.32,-131.66 1447.3,-137.93 1451.44,-137.2"/> +</g> +<!-- TUPLE2->TUPLEN --> +<!-- TUPLEN->TUPLE_BOT --> +<g id="edge52" class="edge"> +<title>TUPLEN->TUPLE_BOT</title> +<path fill="none" stroke="#555555" d="M1572.39,-303.58C1547.38,-266.16 1488.41,-177.94 1460.36,-135.96"/> +<polygon fill="#555555" stroke="#555555" points="1462.21,-134.96 1457.13,-131.14 1458.72,-137.29 1462.21,-134.96"/> +</g> +</g> +</svg> diff --git a/chapter10a/docs/type-control.svg b/chapter10a/docs/type-control.svg new file mode 100644 index 00000000..d4b79c53 --- /dev/null +++ b/chapter10a/docs/type-control.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="61" height="20" viewBox="0 0 61 20" role="img" aria-labelledby="title"> + <title id="title">Control</title> + <rect width="61" height="20" rx="2" fill="#ffff00"/> + <text x="30.5" y="14" text-anchor="middle" font-family="Arial, sans-serif" font-size="13" fill="#000000">Control</text> +</svg> diff --git a/chapter10a/docs/type-integer.svg b/chapter10a/docs/type-integer.svg new file mode 100644 index 00000000..97d03372 --- /dev/null +++ b/chapter10a/docs/type-integer.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="60" height="20" viewBox="0 0 60 20" role="img" aria-labelledby="title"> + <title id="title">Integer</title> + <rect width="60" height="20" rx="2" fill="#add8e6"/> + <text x="30.0" y="14" text-anchor="middle" font-family="Arial, sans-serif" font-size="13" fill="#000000">Integer</text> +</svg> diff --git a/chapter10a/docs/type-memory.svg b/chapter10a/docs/type-memory.svg new file mode 100644 index 00000000..16b5121f --- /dev/null +++ b/chapter10a/docs/type-memory.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="67" height="20" viewBox="0 0 67 20" role="img" aria-labelledby="title"> + <title id="title">Memory</title> + <rect width="67" height="20" rx="2" fill="#00008b"/> + <text x="33.5" y="14" text-anchor="middle" font-family="Arial, sans-serif" font-size="13" fill="#ffffff">Memory</text> +</svg> diff --git a/chapter10a/docs/type-pointer.svg b/chapter10a/docs/type-pointer.svg new file mode 100644 index 00000000..7604492e --- /dev/null +++ b/chapter10a/docs/type-pointer.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="62" height="20" viewBox="0 0 62 20" role="img" aria-labelledby="title"> + <title id="title">Pointer</title> + <rect width="62" height="20" rx="2" fill="#008000"/> + <text x="31.0" y="14" text-anchor="middle" font-family="Arial, sans-serif" font-size="13" fill="#ffffff">Pointer</text> +</svg> diff --git a/chapter10a/docs/type-struct.svg b/chapter10a/docs/type-struct.svg new file mode 100644 index 00000000..97a16562 --- /dev/null +++ b/chapter10a/docs/type-struct.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="52" height="20" viewBox="0 0 52 20" role="img" aria-labelledby="title"> + <title id="title">Struct</title> + <rect width="52" height="20" rx="2" fill="#90ee90"/> + <text x="26.0" y="14" text-anchor="middle" font-family="Arial, sans-serif" font-size="13" fill="#000000">Struct</text> +</svg> diff --git a/chapter10a/docs/type-tuple.svg b/chapter10a/docs/type-tuple.svg new file mode 100644 index 00000000..691478ac --- /dev/null +++ b/chapter10a/docs/type-tuple.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="47" height="20" viewBox="0 0 47 20" role="img" aria-labelledby="title"> + <title id="title">Tuple</title> + <rect width="47" height="20" rx="2" fill="#ffc0cb"/> + <text x="23.5" y="14" text-anchor="middle" font-family="Arial, sans-serif" font-size="13" fill="#000000">Tuple</text> +</svg> diff --git a/chapter10/pom.xml b/chapter10a/pom.xml index 6c4500aa..141ff7d6 100644 --- a/chapter10/pom.xml +++ b/chapter10a/pom.xml @@ -8,9 +8,9 @@ <artifactId>simple</artifactId> <version>1.0</version> </parent> - <artifactId>chapter10</artifactId> + <artifactId>chapter10a</artifactId> <packaging>jar</packaging> - <name>Chapter 10</name> + <name>Chapter 10a</name> <properties> <simple.graph.directory>${project.basedir}/../graph</simple.graph.directory> </properties> diff --git a/chapter10/src/main/java/com/seaofnodes/simple/IRPrinter.java b/chapter10a/src/main/java/com/seaofnodes/simple/IRPrinter.java index 1ac32b1a..1ac32b1a 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/IRPrinter.java +++ b/chapter10a/src/main/java/com/seaofnodes/simple/IRPrinter.java diff --git a/chapter10/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter10a/src/main/java/com/seaofnodes/simple/IterPeeps.java index 7075d526..7075d526 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/IterPeeps.java +++ b/chapter10a/src/main/java/com/seaofnodes/simple/IterPeeps.java diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/Parser.java b/chapter10a/src/main/java/com/seaofnodes/simple/Parser.java new file mode 100644 index 00000000..7734f4df --- /dev/null +++ b/chapter10a/src/main/java/com/seaofnodes/simple/Parser.java @@ -0,0 +1,827 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.graph.GraphObserver; + +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 { + + // Current compilation context; the observer belongs to the parser. + public static Parser PARSER; + public GraphObserver<Node> _obs; + public int pos() { return _lexer._position; } + + + /** + * 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; + + // Mapping from a Struct name to a Struct. + public static Map<String, TypeStruct> OBJS = new HashMap<>(); + + public Parser(String source, TypeInteger arg) { + PARSER = this; + Node.reset(); + IterPeeps.reset(); + OBJS.clear(); + _lexer = new Lexer(source); + _scope = new ScopeNode(); + _continueScope = _breakScope = null; + START = new StartNode(new Type[]{ Type.CONTROL, TypeMem.BOT, 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() { + _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, 2, ScopeNode.ARG0).peephole()); + _scope.define("$mem", TypeMem.BOT, new ProjNode(START, 1, "$mem").peephole()); + parseBlock(); + _scope.pop(); + _xScopes.pop(); + if (!_lexer.isEOF()) throw error("Syntax error, unexpected " + _lexer.getAnyNextToken()); + STOP.peephole(); + 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(";") ) return null; // Empty statement + // 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(requireId().intern(),TypeInteger.BOT),";"); + 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 + 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 + fScope.ctrl(ifF); // The pending arm already starts at False + _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 + ifF.unkeep(); // fScope already owns the false control + 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; + } + + + + /** + * 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 ) { + mem(new StoreNode(field._fname, mem(), n, initValue).peephole()); + } + return n.unkeep(); + } + + // Memory is one hidden SSA variable, including across branches and loops. + private Node mem() { return _scope.lookup("$mem"); } + private Node mem(Node n) { return _scope.update("$mem",n); } + + /** + * 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().intern(); + int idx = ptr._obj==null ? -1 : ptr._obj.find(name); + if( idx == -1 ) 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(); + mem(new StoreNode(name, mem(), expr, val).peephole()); + return expr; // "obj.a = expr" returns the expression while updating memory + } + } + + Type declaredType = ptr._obj._fields[idx]._type; + return parsePostfix(new LoadNode(name, declaredType, mem(), 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 (isNumber(peek())) return parseNumberString(); + 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() { + String snum = parseNumberString(); + if (snum.length() > 1 && snum.charAt(0) == '0') + throw error("Syntax error: integer values cannot start with '0'"); + return TypeInteger.constant(Long.parseLong(snum)); + } + private String parseNumberString() { + int start = _position; + while (isNumber(nextChar())) ; + return new String(_input, start, --_position - start); + } + + // 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/chapter10a/src/main/java/com/seaofnodes/simple/Utils.java index 4f2af769..4f2af769 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/Utils.java +++ b/chapter10a/src/main/java/com/seaofnodes/simple/Utils.java diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/AddNode.java index de5044d7..de5044d7 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/AddNode.java +++ b/chapter10a/src/main/java/com/seaofnodes/simple/node/AddNode.java diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/BoolNode.java index bf0d3a84..bf0d3a84 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/BoolNode.java +++ b/chapter10a/src/main/java/com/seaofnodes/simple/node/BoolNode.java diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/CastNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/CastNode.java index fec540f7..fec540f7 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/CastNode.java +++ b/chapter10a/src/main/java/com/seaofnodes/simple/node/CastNode.java diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/ConstantNode.java index d760c0df..d760c0df 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/ConstantNode.java +++ b/chapter10a/src/main/java/com/seaofnodes/simple/node/ConstantNode.java diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/DivNode.java index 6cee90ad..6cee90ad 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/DivNode.java +++ b/chapter10a/src/main/java/com/seaofnodes/simple/node/DivNode.java diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/IfNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/IfNode.java index 1cf4ddaf..1cf4ddaf 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/IfNode.java +++ b/chapter10a/src/main/java/com/seaofnodes/simple/node/IfNode.java diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/LoadNode.java new file mode 100644 index 00000000..54689283 --- /dev/null +++ b/chapter10a/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -0,0 +1,72 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.Type; + +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 { + + Type _declaredType; + /** + * Load a value from a ptr.field. + * + * @param name The field we are loading + * @param memSlice The whole-memory node - this is updated after a Store + * @param memPtr The ptr to the struct from where we load a field + */ + public LoadNode(String name, Type glb, Node memSlice, Node memPtr) { + super(name, memSlice, memPtr, null); + _declaredType = glb; + } + + @Override + public String label() { return "Load"; } + + @Override + StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(".").append(_name); } + + @Override + public Type compute() { + return _declaredType; + } + + @Override + public Node idealize() { + + // Simple Load-after-Store on same address. + if( mem() instanceof StoreNode st && + ptr() == st.ptr() && _name.equals(st._name) ) { // Must check same object + return st.val(); + } + + // Push a Load up through a Phi, as long as it collapses on at least + // one arm. If at a Loop, the backedge MUST collapse - else we risk + // spinning the same transform around the loop indefinitely. + // BEFORE (2 Sts, 1 Ld): AFTER (1 St, 0 Ld): + // if( pred ) ptr.x = e0; val = pred ? e0 + // else ptr.x = e1; : e1; + // val = ptr.x; ptr.x = val; + if( mem() instanceof PhiNode memphi && memphi.region()._type == Type.CONTROL && memphi.nIns()== 3 ) { + // Profit on RHS/Loop backedge + if( profit(memphi,2) || + // Else must not be a loop to count profit on LHS. + (!(memphi.region() instanceof LoopNode) && profit(memphi,1)) ) { + Node ld1 = new LoadNode(_name,_declaredType,memphi.in(1),ptr()).peephole(); + Node ld2 = new LoadNode(_name,_declaredType,memphi.in(2),ptr()).peephole(); + return new PhiNode(_name,_type,memphi.region(),ld1,ld2); + } + } + + return null; + } + + // Profitable if we find a matching Store on this Phi arm. + private boolean profit(PhiNode phi, int idx) { + Node px = phi.in(idx); + return px!=null && px.addDep(this) instanceof StoreNode st1 && ptr()==st1.ptr() && _name.equals(st1._name); + } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/LoopNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/LoopNode.java index a0ba973e..a0ba973e 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/LoopNode.java +++ b/chapter10a/src/main/java/com/seaofnodes/simple/node/LoopNode.java diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/MemOpNode.java new file mode 100644 index 00000000..d811e71a --- /dev/null +++ b/chapter10a/src/main/java/com/seaofnodes/simple/node/MemOpNode.java @@ -0,0 +1,37 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeMemPtr; + +/** + * Convenience common base for Load and Store. + */ +public abstract class MemOpNode extends Node { + + public final String _name; + + public MemOpNode(String name, Node memSlice, Node memPtr, Node value) { + super(null, memSlice, memPtr, value); + _name = name; + } + + public Node mem() { return in(1); } + public Node ptr() { return in(2); } + + @Override + boolean eq(Node n) { + MemOpNode mem = (MemOpNode)n; // Invariant + return _name.equals(mem._name); + } + + @Override + int hash() { return _name.hashCode(); } + + @Override + String err() { + Type ptr = ptr()._type; + return (ptr==Type.BOTTOM || (ptr instanceof TypeMemPtr tmp && tmp._nil) ) + ? "Might be null accessing '" + _name + "'" + : null; + } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/MinusNode.java index 8b1b73bf..8b1b73bf 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/MinusNode.java +++ b/chapter10a/src/main/java/com/seaofnodes/simple/node/MinusNode.java diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/MulNode.java index 97721ce3..97721ce3 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/MulNode.java +++ b/chapter10a/src/main/java/com/seaofnodes/simple/node/MulNode.java diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/MultiNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/MultiNode.java index 2743bd19..2743bd19 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/MultiNode.java +++ b/chapter10a/src/main/java/com/seaofnodes/simple/node/MultiNode.java diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/NewNode.java index b13462f5..b13462f5 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/NewNode.java +++ b/chapter10a/src/main/java/com/seaofnodes/simple/node/NewNode.java diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/Node.java index eb6c1697..eb6c1697 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter10a/src/main/java/com/seaofnodes/simple/node/Node.java diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/NotNode.java index 83141836..83141836 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/NotNode.java +++ b/chapter10a/src/main/java/com/seaofnodes/simple/node/NotNode.java diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/PhiNode.java index bdf50847..bdf50847 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter10a/src/main/java/com/seaofnodes/simple/node/PhiNode.java diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/ProjNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/ProjNode.java index 735fcdf3..735fcdf3 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/ProjNode.java +++ b/chapter10a/src/main/java/com/seaofnodes/simple/node/ProjNode.java diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/RegionNode.java index f7a56503..f7a56503 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/RegionNode.java +++ b/chapter10a/src/main/java/com/seaofnodes/simple/node/RegionNode.java diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/ReturnNode.java index 92bda5bf..f13f3708 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ b/chapter10a/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -4,26 +4,11 @@ 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. - */ +/** Keeps the return value and all preceding memory effects alive. */ 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)); - } + super(ctrl, data, scope.lookup("$mem")); } public Node ctrl() { return in(0); } diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java index 455f4b3a..455f4b3a 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ b/chapter10a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/StartNode.java new file mode 100644 index 00000000..1ca52af0 --- /dev/null +++ b/chapter10a/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -0,0 +1,35 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.*; +import java.util.BitSet; + +/** The entry tuple: control, initial whole-memory state, and argument. */ +public class StartNode extends MultiNode { + final TypeTuple _args; + + public StartNode(Type[] args) { + super(); + _type = _args = TypeTuple.make(args); + } + + @Override + public String label() { return "Start"; } + + @Override + StringBuilder _print1(StringBuilder sb, BitSet visited) { + return sb.append(label()); + } + + @Override public boolean isCFG() { return true; } + @Override public boolean isMultiHead() { return true; } + + @Override + public TypeTuple compute() { return _args; } + + @Override + public Node idealize() { return null; } + + // No immediate dominator, and idepth==0 + @Override int idepth() { return 0; } + @Override Node idom() { return null; } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/StopNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/StopNode.java index cb4126f5..cb4126f5 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/StopNode.java +++ b/chapter10a/src/main/java/com/seaofnodes/simple/node/StopNode.java diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/StoreNode.java new file mode 100644 index 00000000..4bc67c3c --- /dev/null +++ b/chapter10a/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -0,0 +1,66 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.*; + +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 name The struct field we are assigning to + * @param memSlice The whole-memory 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(String name, Node memSlice, Node memPtr, Node value) { + super(name, memSlice, memPtr, value); + } + + @Override + public String label() { return "Store"; } + @Override + public boolean isMem() { return true; } + public Node val() { return in(3); } + + @Override + StringBuilder _print1(StringBuilder sb, BitSet visited) { + return sb.append(".").append(_name).append("=").append( val()).append(";"); + } + + @Override + public Type compute() { return TypeMem.BOT; } + + @Override + public Node idealize() { + + // Simple store-after-store on same address. Should pick up the + // required init-store being stomped by a first user store. + if( mem() instanceof StoreNode st && + ptr()==st.ptr() && _name.equals(st._name) && // Must check same object + ptr()._type instanceof TypeMemPtr && // No bother if weird dead pointers + // Must have exactly one use of "this" or you get weird + // non-serializable memory effects in the worse case. + st.checkNoUseBeyond(this) ) { + setDef(1,st.mem()); + return this; + } + + return null; + } + + // Check that `this` has no uses beyond `that` + private boolean checkNoUseBeyond(Node that) { + if( nOuts()==1 ) return true; + // Add deps on the other uses (can be e.g. ScopeNode mid-parse) so that + // when the other uses go away we can retry. + for( Node use : _outputs ) + if( use != that ) + use.addDep(that); + return false; + } + +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/SubNode.java index eeec5e1d..eeec5e1d 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/SubNode.java +++ b/chapter10a/src/main/java/com/seaofnodes/simple/node/SubNode.java diff --git a/chapter10/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter10a/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index 600e9c73..5e21c852 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/chapter10a/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -52,7 +52,7 @@ public class SimpleGraphAdapter extends GraphAdapter<Node> { if( n instanceof ProjNode ) return n.isCFG() ? Role.CTRL : isMem(n) ? Role.MEM : Role.DATA; if( n instanceof RegionNode && i == 0 ) return Role.ASSOC; if( i == 0 || n instanceof RegionNode || n instanceof StopNode ) return Role.CTRL; - if( i == 1 && (n instanceof MemOpNode || n instanceof ReturnNode) ) return Role.MEM; + if( i == 1 && n instanceof MemOpNode || i == 2 && n instanceof ReturnNode ) return Role.MEM; Node def = n.in(i); if( def != null && isMem(def) ) return Role.MEM; return Role.DATA; diff --git a/chapter10/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter10a/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java index 1980659e..1980659e 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java +++ b/chapter10a/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java diff --git a/chapter10/src/main/java/com/seaofnodes/simple/type/Field.java b/chapter10a/src/main/java/com/seaofnodes/simple/type/Field.java index 96088f90..cdc58215 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/type/Field.java +++ b/chapter10a/src/main/java/com/seaofnodes/simple/type/Field.java @@ -20,7 +20,6 @@ public class Field extends Type { _fname = fname; _type = type; } - // Make with existing alias public static Field make( String fname, Type type ) { return new Field(fname,type).intern(); } diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/type/Type.java b/chapter10a/src/main/java/com/seaofnodes/simple/type/Type.java new file mode 100644 index 00000000..bd9cf458 --- /dev/null +++ b/chapter10a/src/main/java/com/seaofnodes/simple/type/Type.java @@ -0,0 +1,187 @@ +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; // Whole-memory token + 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]; } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/chapter10a/src/main/java/com/seaofnodes/simple/type/TypeInteger.java index 8c77672b..8c77672b 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/type/TypeInteger.java +++ b/chapter10a/src/main/java/com/seaofnodes/simple/type/TypeInteger.java diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/type/TypeMem.java b/chapter10a/src/main/java/com/seaofnodes/simple/type/TypeMem.java new file mode 100644 index 00000000..d921bac3 --- /dev/null +++ b/chapter10a/src/main/java/com/seaofnodes/simple/type/TypeMem.java @@ -0,0 +1,20 @@ +package com.seaofnodes.simple.type; + +import java.util.ArrayList; + +/** A token for all memory; field values are not tracked in its type. */ +public class TypeMem extends Type { + private final boolean _high; + private TypeMem(boolean high) { super(TMEM); _high = high; } + public static final TypeMem TOP = new TypeMem(true ).intern(); + public static final TypeMem BOT = new TypeMem(false).intern(); + + public static void gather(ArrayList<Type> ts) { ts.add(BOT); } + @Override TypeMem xmeet(Type t) { return BOT; } + @Override public Type dual() { return _high ? BOT : TOP; } + @Override public Type glb() { return BOT; } + @Override int hash() { return _high ? 1 : 2; } + @Override boolean eq(Type t) { return _high==((TypeMem)t)._high; } + @Override public StringBuilder print(StringBuilder sb) { return sb.append(_high ? "MemTop" : "MemBot"); } + @Override public String str() { return print(new StringBuilder()).toString(); } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java b/chapter10a/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java index 6c874a93..6c874a93 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java +++ b/chapter10a/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java diff --git a/chapter10/src/main/java/com/seaofnodes/simple/type/TypeStruct.java b/chapter10a/src/main/java/com/seaofnodes/simple/type/TypeStruct.java index 44741def..44741def 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/type/TypeStruct.java +++ b/chapter10a/src/main/java/com/seaofnodes/simple/type/TypeStruct.java diff --git a/chapter10/src/main/java/com/seaofnodes/simple/type/TypeTuple.java b/chapter10a/src/main/java/com/seaofnodes/simple/type/TypeTuple.java index 0aa019a7..0aa019a7 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/type/TypeTuple.java +++ b/chapter10a/src/main/java/com/seaofnodes/simple/type/TypeTuple.java diff --git a/chapter10/src/test/java/com/seaofnodes/simple/Chapter01Test.java b/chapter10a/src/test/java/com/seaofnodes/simple/Chapter01Test.java index 89882b2f..89882b2f 100644 --- a/chapter10/src/test/java/com/seaofnodes/simple/Chapter01Test.java +++ b/chapter10a/src/test/java/com/seaofnodes/simple/Chapter01Test.java diff --git a/chapter10/src/test/java/com/seaofnodes/simple/Chapter02Test.java b/chapter10a/src/test/java/com/seaofnodes/simple/Chapter02Test.java index a6075b75..a6075b75 100644 --- a/chapter10/src/test/java/com/seaofnodes/simple/Chapter02Test.java +++ b/chapter10a/src/test/java/com/seaofnodes/simple/Chapter02Test.java diff --git a/chapter10/src/test/java/com/seaofnodes/simple/Chapter03Test.java b/chapter10a/src/test/java/com/seaofnodes/simple/Chapter03Test.java index 609dff76..609dff76 100644 --- a/chapter10/src/test/java/com/seaofnodes/simple/Chapter03Test.java +++ b/chapter10a/src/test/java/com/seaofnodes/simple/Chapter03Test.java diff --git a/chapter10/src/test/java/com/seaofnodes/simple/Chapter04Test.java b/chapter10a/src/test/java/com/seaofnodes/simple/Chapter04Test.java index f1603cc4..f1603cc4 100644 --- a/chapter10/src/test/java/com/seaofnodes/simple/Chapter04Test.java +++ b/chapter10a/src/test/java/com/seaofnodes/simple/Chapter04Test.java diff --git a/chapter10/src/test/java/com/seaofnodes/simple/Chapter05Test.java b/chapter10a/src/test/java/com/seaofnodes/simple/Chapter05Test.java index 35e54fa9..aefb3a5c 100644 --- a/chapter10/src/test/java/com/seaofnodes/simple/Chapter05Test.java +++ b/chapter10a/src/test/java/com/seaofnodes/simple/Chapter05Test.java @@ -22,7 +22,7 @@ else { return a;"""); StopNode ret = parser.parse().iterate(); - assertEquals("return Phi(Region18,(arg+2),(arg-3));", ret.toString()); + assertEquals("return Phi(Region19,(arg+2),(arg-3));", ret.toString()); } @Test @@ -37,7 +37,7 @@ if (arg == 1) { } return c;""", TypeInteger.BOT); StopNode ret = parser.parse().iterate(); - assertEquals("return Phi(Region16,4,3);", ret.toString()); + assertEquals("return Phi(Region17,4,3);", ret.toString()); } @Test @@ -65,7 +65,7 @@ else b=a+1; return a+b;"""); StopNode ret = parser.parse().iterate(); - assertEquals("return ((arg*2)+Phi(Region21,2,3));", ret.toString()); + assertEquals("return ((arg*2)+Phi(Region22,2,3));", ret.toString()); } @Test @@ -80,7 +80,7 @@ else a=b+1; return a+b;"""); StopNode ret = parser.parse().iterate(); - assertEquals("return ((Phi(Region32,(arg*2),arg)+arg)+Phi(Region,4,5));", ret.toString()); + assertEquals("return ((Phi(Region33,(arg*2),arg)+arg)+Phi(Region,4,5));", ret.toString()); } @Test @@ -100,7 +100,7 @@ else return a; """, TypeInteger.BOT); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region36,Phi(Region22,2,3),Phi(Region34,4,5));", stop.toString()); + assertEquals("return Phi(Region37,Phi(Region23,2,3),Phi(Region35,4,5));", stop.toString()); } @Test @@ -116,7 +116,7 @@ if( arg==0 ) return arg+a+b; """, TypeInteger.BOT); StopNode stop = parser.parse(); - assertEquals("return ((arg+Phi(Region13,1,0))+Phi(Region28,2,0));", stop.toString()); + assertEquals("return ((arg+Phi(Region14,1,0))+Phi(Region29,2,0));", stop.toString()); } @Test @@ -130,7 +130,7 @@ if( arg==1 ) } return a;"""); StopNode ret = parser.parse().iterate(); - assertEquals("return (arg==Phi(Region16,3,2));", ret.toString()); + assertEquals("return (arg==Phi(Region17,3,2));", ret.toString()); } @Test diff --git a/chapter10/src/test/java/com/seaofnodes/simple/Chapter06Test.java b/chapter10a/src/test/java/com/seaofnodes/simple/Chapter06Test.java index 097c2617..920e26e3 100644 --- a/chapter10/src/test/java/com/seaofnodes/simple/Chapter06Test.java +++ b/chapter10a/src/test/java/com/seaofnodes/simple/Chapter06Test.java @@ -30,7 +30,7 @@ if (arg) return (arg < a) < 3; """); StopNode stop = parser.parse().iterate(); - assertEquals("return ((arg<Phi(Region12,2,1))<3);", stop.toString()); + assertEquals("return ((arg<Phi(Region13,2,1))<3);", stop.toString()); } @Test @@ -65,7 +65,7 @@ else b=5; return b;""", TypeInteger.BOT); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region37,42,5);", stop.toString()); + assertEquals("return Phi(Region38,42,5);", stop.toString()); } @Test @@ -84,7 +84,7 @@ else b=5; return b;""", TypeInteger.BOT); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region30,2,5);", stop.toString()); + assertEquals("return Phi(Region31,2,5);", stop.toString()); } @Test @@ -144,7 +144,7 @@ else return a; """, TypeInteger.BOT); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region41,3,Phi(Region39,4,5));", stop.toString()); + assertEquals("return Phi(Region42,3,Phi(Region40,4,5));", stop.toString()); } @Test @@ -200,7 +200,7 @@ if( arg ) { return a+b; """); StopNode ret = parser.parse().iterate(); - assertEquals("return Phi(Region24,4,1);", ret.toString()); + assertEquals("return Phi(Region25,4,1);", ret.toString()); } @@ -251,7 +251,7 @@ if( arg ) { return a+b+c; """); StopNode ret = parser.parse().iterate(); - assertEquals("return (Phi(Region36,Phi(Region23,2,3),0)+Phi(Region,3,1));", ret.toString()); + assertEquals("return (Phi(Region37,Phi(Region24,2,3),0)+Phi(Region,3,1));", ret.toString()); } diff --git a/chapter10/src/test/java/com/seaofnodes/simple/Chapter07Test.java b/chapter10a/src/test/java/com/seaofnodes/simple/Chapter07Test.java index 58ecb7bc..ff0ca599 100644 --- a/chapter10/src/test/java/com/seaofnodes/simple/Chapter07Test.java +++ b/chapter10a/src/test/java/com/seaofnodes/simple/Chapter07Test.java @@ -18,7 +18,7 @@ public class Chapter07Test { return arg; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop6,arg,(Phi_arg+1));", stop.toString()); + assertEquals("return Phi(Loop7,arg,(Phi_arg+1));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof ProjNode); } @@ -35,7 +35,7 @@ if(arg){}else{ return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region24,1,Phi(Loop13,1,(Phi_a+1)));", stop.toString()); + assertEquals("return Phi(Region25,1,Phi(Loop14,1,(Phi_a+1)));", stop.toString()); } @Test @@ -55,7 +55,7 @@ while(i < arg) { return sum; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop8,0,Phi(Loop20,Phi_sum,(Phi_sum+Phi(Loop,0,(Phi_j+1)))));", stop.toString()); + assertEquals("return Phi(Loop9,0,Phi(Loop21,Phi_sum,(Phi_sum+Phi(Loop,0,(Phi_j+1)))));", stop.toString()); } @Test @@ -71,7 +71,7 @@ while(a < 10) { return b; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop8,2,Phi(Region27,Phi_b,4));", stop.toString()); + assertEquals("return Phi(Loop9,2,Phi(Region28,Phi_b,4));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof ProjNode); } @@ -90,7 +90,7 @@ while(a < 10) { return b; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop8,2,(Phi(Region27,Phi_b,4)+1));", stop.toString()); + assertEquals("return Phi(Loop9,2,(Phi(Region28,Phi_b,4)+1));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof ProjNode); } @@ -107,7 +107,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop7,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop8,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof ProjNode); } @@ -123,7 +123,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop7,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop8,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof ProjNode); } @@ -136,7 +136,7 @@ while(arg) a = 2; return a; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop7,1,2);", stop.toString()); + assertEquals("return Phi(Loop8,1,2);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof ProjNode); } @@ -149,7 +149,7 @@ while(arg) a = 2; return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop7,1,2);", stop.toString()); + assertEquals("return Phi(Loop8,1,2);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof ProjNode); } @@ -165,7 +165,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop7,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop8,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof ProjNode); } @@ -181,7 +181,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop7,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop8,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof ProjNode); } @@ -198,7 +198,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop8,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop9,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof ProjNode); } @@ -215,7 +215,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop8,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop9,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/chapter10a/src/test/java/com/seaofnodes/simple/Chapter08Test.java index 6688d598..4fa08bd9 100644 --- a/chapter10/src/test/java/com/seaofnodes/simple/Chapter08Test.java +++ b/chapter10a/src/test/java/com/seaofnodes/simple/Chapter08Test.java @@ -24,7 +24,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region36,Phi(Region25,Phi(Loop6,arg,(Phi_arg+1)),Add),Add);", stop.toString()); + assertEquals("return Phi(Region37,Phi(Region26,Phi(Loop7,arg,(Phi_arg+1)),Add),Add);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof RegionNode); Assert.assertEquals(5L, Evaluator.evaluate(stop, 1)); Assert.assertEquals(10L, Evaluator.evaluate(stop, 6)); @@ -47,7 +47,7 @@ while(arg < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop7,1,Phi(Region42,Phi_a,(Phi_a+1)));", stop.toString()); + assertEquals("return Phi(Loop8,1,Phi(Region43,Phi_a,(Phi_a+1)));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof ProjNode); } @@ -66,7 +66,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region34,Phi(Loop6,arg,(Phi_arg+1)),Add);", stop.toString()); + assertEquals("return Phi(Region35,Phi(Loop7,arg,(Phi_arg+1)),Add);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof RegionNode); } @@ -82,7 +82,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region25,Phi(Loop6,arg,(Phi_arg+1)),Add);", stop.toString()); + assertEquals("return Phi(Region26,Phi(Loop7,arg,(Phi_arg+1)),Add);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof RegionNode); } @@ -101,7 +101,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop6,arg,(Phi_arg+1));", stop.toString()); + assertEquals("return Phi(Loop7,arg,(Phi_arg+1));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof ProjNode); } @@ -118,7 +118,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop6,arg,(Phi_arg+1));", stop.toString()); + assertEquals("return Phi(Loop7,arg,(Phi_arg+1));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof ProjNode); } @@ -186,7 +186,7 @@ while(arg < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region28,Phi(Loop7,1,(Phi_a+1)),Add);", stop.toString()); + assertEquals("return Phi(Region29,Phi(Loop8,1,(Phi_a+1)),Add);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof RegionNode); } @@ -202,7 +202,7 @@ while(1) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return (Phi(Loop7,1,Add)+1);", stop.toString()); + assertEquals("return (Phi(Loop8,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/chapter10a/src/test/java/com/seaofnodes/simple/Chapter09Test.java index 048a58c7..1b1920e8 100644 --- a/chapter10/src/test/java/com/seaofnodes/simple/Chapter09Test.java +++ b/chapter10a/src/test/java/com/seaofnodes/simple/Chapter09Test.java @@ -70,7 +70,7 @@ while (arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop7,arg,(Phi_arg+2));", stop.toString()); + assertEquals("return Phi(Loop8,arg,(Phi_arg+2));", stop.toString()); Assert.assertEquals(11L, Evaluator.evaluate(stop, 1)); } @@ -87,7 +87,7 @@ while (arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop8,arg,(Phi_arg+4));", stop.toString()); + assertEquals("return Phi(Loop9,arg,(Phi_arg+4));", stop.toString()); Assert.assertEquals(13L, Evaluator.evaluate(stop, 1)); } @@ -116,7 +116,7 @@ while (arg) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop14,arg,(Phi_arg+1));", stop.toString()); + assertEquals("return Phi(Loop15,arg,(Phi_arg+1));", stop.toString()); } @Test @@ -186,7 +186,7 @@ while(arg) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,0,(-(Phi_a+3)));", stop.toString()); + assertEquals("return Phi(Loop10,0,(-(Phi_a+3)));", stop.toString()); } @Test @@ -283,7 +283,7 @@ return v1+v0; public void testFuzz8() { Parser parser = new Parser("while(arg) arg = arg - 1; return arg;"); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop6,arg,(Phi_arg-1));", stop.toString()); + assertEquals("return Phi(Loop7,arg,(Phi_arg-1));", stop.toString()); } @Test diff --git a/chapter10/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter10a/src/test/java/com/seaofnodes/simple/Chapter10Test.java index da9a4c5f..26bbb62a 100644 --- a/chapter10/src/test/java/com/seaofnodes/simple/Chapter10Test.java +++ b/chapter10a/src/test/java/com/seaofnodes/simple/Chapter10Test.java @@ -286,7 +286,9 @@ while( i.x < i.len ) { return sum; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop14,0,(Phi(Loop,0,(Phi_x+1))+Phi_sum));", stop.toString()); + // The preceding len store keeps the initial x load on the bulk chain. + assertEquals("return Phi(Loop13,0,(Phi(Loop,.x,(Phi_x+1))+Phi_sum));", stop.toString()); + assertEquals(10L, com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,5)); } @@ -458,4 +460,63 @@ return 0; assertEquals("return 0;", stop.toString()); } + + @Test public void testReadBeforeStores() { + var stop = new Parser(""" + struct S { int x; int y; } + S a = new S; S b = new S; + a.x = 11; b.x = 22; + S p = a; if (arg) p = b; + int before = p.x; + a.y = 55; b.y = 66; + a.x = 33; b.x = 44; + return before*100 + p.x; + """).parse().iterate(); + assertEquals(1133L, com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,0)); + assertEquals(2244L, com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,1)); + } + + @Test public void testMemoryAcrossNestedLoops() { + var stop = new Parser(""" + struct S { int x; int y; int z; } + S s = new S; + s.x = 5; s.y = 7; s.z = 11; + while (arg > 0) { + s.x = s.x + 1; + int j = 3; + while (j > 0) { + j = j - 1; + if (j == 1) continue; + s.y = s.y + arg; + if (arg == 2) break; + s.z = s.z + 1; + } + arg = arg - 1; + } + return s; + """).parse().iterate(); + for (int arg=0; arg<7; arg++) { + var obj = (com.seaofnodes.simple.evaluator.Evaluator.Obj) + com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,arg); + org.junit.Assert.assertArrayEquals(new Object[] { + 5L+arg, 7L+arg*(arg+1)-(arg>=2 ? 2 : 0), 11L+2*arg-(arg>=2 ? 2 : 0) + }, obj.fields()); + } + } + + @Test public void testMemoryAtEarlyReturns() { + var stop = new Parser(""" + struct S { int x; int y; int z; } + S s = new S; + s.x = 3; s.y = 5; s.z = 7; + if (arg) { s.x = 11; s.z = 13; return s; } + s.y = 17; + return s; + """).parse().iterate(); + for (int arg=0; arg<2; arg++) { + var obj = (com.seaofnodes.simple.evaluator.Evaluator.Obj) + com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,arg); + org.junit.Assert.assertArrayEquals(arg==0 ? new Object[]{3L,17L,7L} : new Object[]{11L,5L,13L}, obj.fields()); + } + } } diff --git a/chapter10/src/test/java/com/seaofnodes/simple/FuzzerWrap.java b/chapter10a/src/test/java/com/seaofnodes/simple/FuzzerWrap.java index 019140ad..019140ad 100644 --- a/chapter10/src/test/java/com/seaofnodes/simple/FuzzerWrap.java +++ b/chapter10a/src/test/java/com/seaofnodes/simple/FuzzerWrap.java diff --git a/chapter10/src/test/java/com/seaofnodes/simple/SchedulerTest.java b/chapter10a/src/test/java/com/seaofnodes/simple/SchedulerTest.java index ad595822..ad595822 100644 --- a/chapter10/src/test/java/com/seaofnodes/simple/SchedulerTest.java +++ b/chapter10a/src/test/java/com/seaofnodes/simple/SchedulerTest.java diff --git a/chapter10a/src/test/java/com/seaofnodes/simple/TypeTest.java b/chapter10a/src/test/java/com/seaofnodes/simple/TypeTest.java new file mode 100644 index 00000000..c9eb2747 --- /dev/null +++ b/chapter10a/src/test/java/com/seaofnodes/simple/TypeTest.java @@ -0,0 +1,132 @@ +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("a", TypeInteger.BOT), + Field.make("b", TypeInteger.BOT) }); + TypeStruct s2 = TypeStruct.make("s2", new Field[]{ + Field.make("a", TypeInteger.BOT), + Field.make("b", TypeInteger.BOT) }); + Assert.assertEquals(s1, s1.glb()); + Assert.assertNotEquals(s1, s1.dual()); + Assert.assertEquals(s1, s1.dual().glb()); + + assertEquals(TypeStruct.BOT, s1.meet(s2)); + assertEquals(TypeMem.BOT, TypeMem.TOP.meet(TypeMem.BOT)); + assertEquals(TypeMem.TOP, TypeMem.BOT.dual()); + assertEquals(TypeMem.BOT, TypeMem.TOP.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/evaluator/Evaluator.java b/chapter10a/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java index 3cf5c57b..eaeb061b 100644 --- a/chapter10/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java +++ b/chapter10a/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java @@ -150,8 +150,8 @@ public class Evaluator { public Object evaluate(long parameter, int loops) { if (start == null) return Status.TIMEOUT; var s = new Object[start.compute()._types.length]; - s[1] = parameter; - for(int i=2;i<s.length;i++) s[i] = MEMORY; + s[1] = MEMORY; + s[2] = parameter; values[start._nid] = s; int i=0; Scheduler.Block block = this.startBlock; diff --git a/chapter10/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java b/chapter10a/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java index 674d12f5..674d12f5 100644 --- a/chapter10/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java +++ b/chapter10a/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java diff --git a/chapter10/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java b/chapter10a/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java index 7dcb88e5..7dcb88e5 100644 --- a/chapter10/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java +++ b/chapter10a/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java diff --git a/chapter10/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java b/chapter10a/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java index f947a234..f947a234 100644 --- a/chapter10/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java +++ b/chapter10a/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java diff --git a/chapter10/src/test/java/com/seaofnodes/simple/fuzzer/Reducer.java b/chapter10a/src/test/java/com/seaofnodes/simple/fuzzer/Reducer.java index 3731b40c..3731b40c 100644 --- a/chapter10/src/test/java/com/seaofnodes/simple/fuzzer/Reducer.java +++ b/chapter10a/src/test/java/com/seaofnodes/simple/fuzzer/Reducer.java diff --git a/chapter10/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java b/chapter10a/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java index 155063ab..155063ab 100644 --- a/chapter10/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java +++ b/chapter10a/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java diff --git a/chapter10b/#README.md# b/chapter10b/#README.md# new file mode 100644 index 00000000..6380b79e --- /dev/null +++ b/chapter10b/#README.md# @@ -0,0 +1,174 @@ +# Chapter 10b: Splitting Memory + +[Previous: Chapter 10a](../chapter10a/README.md) | +[Next: Chapter 11](../chapter11/README.md) + +Chapter 10a made memory effects explicit with one SSA memory value. This +chapter keeps exactly that parser model and recovers independent memory chains +through graph rewrites. There is no new language syntax; the +[grammar](docs/10-grammar.md), pointer types, and null checks are unchanged. + +## Why split memory? + +```java +struct Point { int x; int y; } +Point p = new Point; +p.x = 42; +while (arg > 0) { + p.y = p.y + arg; + arg = arg - 1; +} +return p.x; +``` + +The loop changes `y`, so it cannot affect `x`. With one memory chain the load +of `x` follows the loop's stores. Splitting the fields lets ordinary +load-after-store forwarding turn the return value into 42. + + + +Control, pointer, and value edges are omitted when they do not explain memory. + +## Equivalence-class aliasing + +Each struct field has an integer alias identity. Two different fields receive +different identities, even if they have the same spelling in different structs. +Two instances of the same struct share their field aliases. Thus different +classes never alias; accesses in the same class **may alias**. Same-class does +not mean same-address: forwarding still checks pointer identity. + +The parser assigns field identities at struct declarations, beginning at 2. +These identities are independent of Start's output indices. The default slot +of a memory aggregate is 1; precise slots are greater than 1. + +We do not allocate a Start projection, scope binding, or loop Phi for every +declared field. Start supplies one memory value, Scope holds one `$mem` binding, +and Return consumes one complete memory value. The optimizer introduces slices +only where graph operations require them. + +## Three memory nodes + +| Node | Meaning | +|---|---| +| `MemMerge` | A complete memory state: a default plus precise alias overrides | +| `MemPhi` | One alias selected according to the incoming control path | +| `BulkMemPhi` | All aliases except an explicit exclusion set, selected by control | + +`MemMerge` combines independent pieces of memory at one program point. It is +not a control-flow merge. For example, after storing to alias `x`: + +```text +prior = current memory +store = Store_x(prior, pointer, value) +current memory = MemMerge(default=prior, x=store) +``` + +The Store selects its precise input from any incoming MemMerge. Loads and +precise memory Phis do the same. A missing precise entry falls back to the +default, provided that default still covers the requested alias. + +The explicit entries override the default: the semantic slices are disjoint, +and their union covers all memory. Flattening stacked aggregates preserves +entries that the outer aggregate has not replaced. This allows an `x` store +and a later `y` store to remain visible together at Return. + + + +## Splitting a memory Phi + +Scope initially creates a BulkMemPhi wherever the ordinary SSA algorithm needs +to merge memory. Its empty exclusion set means that it covers every alias. +After the Region is complete, the Phi inspects its memory inputs and users. +When they require a precise alias, it splits out that alias: + +```text +BulkPhi(R, left, right) + becomes +MemMerge( + default = BulkPhi_except{x}(R, left, right), + x = MemPhi_x(R, slice(left,x), slice(right,x))) +``` + +The new precise Phi and the remaining bulk Phi share a Region and matching +control-path indices. A loop uses the same transformation, with the backedge +among those inputs. Each further split preserves all previously extracted +slices; replacing a bulk Phi must never drop their parallel precise Phis. + + + +A bulk Phi can bypass an incoming aggregate only when all of that aggregate's +explicit slices are already excluded from the Phi. A precise consumer can +select one entry. A whole-memory consumer cannot just select the default: +that would lose the explicit updates. + +## Types and the worklist + +The memory type lattice adds a flat element `MEM#N` for each precise alias. +Meeting different aliases gives `TypeMem.BOT`. It does not track field values, +private objects, or escape information. Bulk exclusion sets belong to the +graph nodes, not to an expanded type lattice. + + + +Scalar Phis remain ordinary PhiNodes. The scope's Phi factory chooses a +BulkMemPhi for whole memory; splitting constructs MemPhis explicitly. Generic +arithmetic factoring through Phis must not factor a memory aggregate. + +Memory splitting can increase node count. Progress comes from extracting an +alias from a bulk Phi and simplifying its precise chain, rather than from +requiring every rewrite to shrink the graph. Nodes must have conservative +initial types before introducing cyclic inputs. New bulk and precise Phis are +queued, so a recursive peephole cannot start another split halfway through the +first one. + +This also extends the worklist lesson from Chapter 9. A bulk Phi inspects its +users, so changes to a user's partition must wake the producer. Selecting a +slice from an aggregate can expose a new bulk-Phi user; that selected definition +must be queued too. The optimizer's worklist completeness assertion remains +enabled and checks these dependencies. + +## Evaluation and ordering + +MemMerge is a dependency node; it emits no heap operation. Its evaluator result +is a memory token, while Store mutates the object's fields. The scheduler must +distinguish aggregation from overwriting memory. A MemMerge is not a clobber, +and stores in different alias classes do not impose load/store anti-dependencies. + + + +Tests cover independent-field folding, parallel memory Phis, multiple field +updates surviving at Return, early returns, nested loops with break/continue, +and reads before subsequent stores through potentially aliased pointers. + +## What remains for later chapters? + +Chapter 10b knows every access's alias while parsing. What is lazy here is the +construction of memory partitions. Chapter 25 also allows an existing access's +alias to become known later, using the same representation. Its private +constructor memory, escape tracking, incomplete types, and module alias +remapping are separate extensions and are not needed here. + +Chapters 11-24 currently retain their older parser-managed memory chains. +Forwarding this representation through those snapshots is tracked in the +[backport queue](../docs/chapter-backports.md). Chapter 11's scheduling lesson +therefore still shows the older graph construction, with the same language. + +## Build and test + +With JDK 21 or newer, from this directory: + +```sh +make lib +make tests +make release +make view +``` + +Make builds this complete snapshot without Maven. `make release` writes +`build/release/simple.jar`; `make view` launches the shared graph viewer. +Both the root Maven reactor and the supplied IDEA module descriptor include +the chapter. From the repository root, test both halves with: + +```sh +make tests CHAPTERS="chapter10a chapter10b" +``` diff --git a/chapter10b/.#README.md b/chapter10b/.#README.md new file mode 100644 index 00000000..c657b199 --- /dev/null +++ b/chapter10b/.#README.md @@ -0,0 +1 @@ +cliffc@DESKTOP-DAD.21980:1790387027
\ No newline at end of file diff --git a/chapter10b/Makefile b/chapter10b/Makefile new file mode 100644 index 00000000..5ed21084 --- /dev/null +++ b/chapter10b/Makefile @@ -0,0 +1,7 @@ +# Standalone compiler snapshot; shared rules also work in linearized checkouts. +include $(firstword $(wildcard ../build-support/early-chapter.mk build-support/early-chapter.mk)) +test_cp += com.seaofnodes.simple.FuzzerWrap + +.PHONY: fuzzer +fuzzer: $(test_classes) + java -ea -cp "$(CLZDIR)/main$(SEP)$(CLZDIR)/test$(SEP)$(jars)" org.junit.runner.JUnitCore com.seaofnodes.simple.FuzzerWrap diff --git a/chapter10b/README.md b/chapter10b/README.md new file mode 100644 index 00000000..795d2233 --- /dev/null +++ b/chapter10b/README.md @@ -0,0 +1,190 @@ +# Chapter 10b: Splitting Memory + +[Previous: Chapter 10a](../chapter10a/README.md) | +[Next: Chapter 11](../chapter11/README.md) + +Chapter 10a made memory effects explicit with one SSA memory value. This +chapter keeps exactly that parser model and recovers independent memory chains +through graph rewrites. There is no new language syntax; the +[grammar](docs/10-grammar.md), pointer types, and null checks are unchanged. + +## Why split memory? + +```java +struct Point { int x; int y; } +Point p = new Point; +p.x = 42; +while (arg > 0) { + p.y = p.y + arg; + arg = arg - 1; +} +return p.x; +``` + +The loop changes `y`, so it cannot affect `x`. With one memory chain the load +of `x` follows the loop's stores. Splitting the fields lets ordinary +load-after-store forwarding turn the return value into 42. + + + +These are schematic dependency graphs, with arrows from uses to their definitions. +In horizontal diagrams, definitions appear on the left and uses on the right; +in vertical diagrams, definitions appear above uses. +Control, pointer, and value edges are omitted when they do not explain memory. + +## Equivalence-class aliasing + +Each struct field has an integer alias identity. Two different fields receive +different identities, even if they have the same spelling in different structs. +Two instances of the same struct share their field aliases. Thus different +classes never alias; accesses in the same class **may alias**. Same-class does +not mean same-address: forwarding still checks pointer identity. + +The parser assigns field identities at struct declarations, beginning at 2. +These identities are independent of Start's output indices. The default slot +of a memory aggregate is 1; precise slots are greater than 1. + +We do not allocate a Start projection, scope binding, or loop Phi for every +declared field. Start supplies one memory value, Scope holds one `$mem` binding, +and Return consumes one complete memory value. The optimizer introduces slices +only where graph operations require them. + +## Three memory nodes + +| Node | Meaning | +|---|---| +| `MemMerge` | A complete memory state: a default plus precise alias overrides | +| `MemPhi` | One alias selected according to the incoming control path | +| `BulkMemPhi` | All aliases except an explicit exclusion set, selected by control | + +`MemMerge` combines independent pieces of memory at one program point. It is +not a control-flow merge. For example, after storing to alias `x`: + +```text +prior = current memory +store = Store_x(prior, pointer, value) +current memory = MemMerge(default=prior, x=store) +``` + +The Store selects its precise input from any incoming MemMerge. Loads and +precise memory Phis do the same. A missing precise entry falls back to the +default, provided that default still covers the requested alias. + +The explicit entries override the default: the semantic slices are disjoint, +and their union covers all memory. Flattening stacked aggregates preserves +entries that the outer aggregate has not replaced. This allows an `x` store +and a later `y` store to remain visible together at Return. + + + +## Splitting a memory Phi + +Scope initially creates a BulkMemPhi wherever the ordinary SSA algorithm needs +to merge memory. Its empty exclusion set means that it covers every alias. +After the Region is complete, the Phi inspects its memory inputs and users. +When they require a precise alias, it splits out that alias: + +```text +BulkPhi(R, left, right) + becomes +MemMerge( + default = BulkPhi_except{x}(R, left, right), + x = MemPhi_x(R, slice(left,x), slice(right,x))) +``` + +The new precise Phi and the remaining bulk Phi share a Region and matching +control-path indices. A loop uses the same transformation, with the backedge +among those inputs. Each further split preserves all previously extracted +slices; replacing a bulk Phi must never drop their parallel precise Phis. + + + +A bulk Phi can bypass an incoming aggregate only when all of that aggregate's +explicit slices are already excluded from the Phi. A precise consumer can +select one entry. A whole-memory consumer cannot just select the default: +that would lose the explicit updates. + +## Types and the worklist + +The type lattice retains Chapter 10a's six domains, colors, and notation: +`⊤` means top, `⊥` means bottom, and a suffix names the domain. The other five +domains are unchanged; memory now adds a flat element `MEM#N` for each precise +alias between memory top (`TypeMem.TOP`) and memory bottom (`TypeMem.BOT`). + + + +The parallel `MEM#2`, `MEM#3`, and further alias elements describe independent +field slices. For the first `Point` declaration above, these two aliases identify +`Point.x` and `Point.y`. Meeting different aliases gives memory bottom; joining +them gives memory top. Each precise alias is its own dual. The ellipsis stands +for more parallel aliases, not a type representing a set of aliases. + +Memory bottom covers all memory. The default aggregate slot numbered 1 is a +graph input position, not a precise field alias in this diagram. Types do not +track field values, private objects, or escape information. Bulk exclusion sets +and the default-plus-overrides structure of MemMerge belong to graph nodes, +not to an expanded type lattice. As in Chapter 10a, diagram edges show lattice +order, with top above bottom; field and tuple-element type lattices are elided. + +Scalar Phis remain ordinary PhiNodes. The scope's Phi factory chooses a +BulkMemPhi for whole memory; splitting constructs MemPhis explicitly. Generic +arithmetic factoring through Phis must not factor a memory aggregate. + +Memory splitting can increase node count. Progress comes from extracting an +alias from a bulk Phi and simplifying its precise chain, rather than from +requiring every rewrite to shrink the graph. Nodes must have conservative +initial types before introducing cyclic inputs. New bulk and precise Phis are +queued, so a recursive peephole cannot start another split halfway through the +first one. + +This also extends the worklist lesson from Chapter 9. A bulk Phi inspects its +users, so changes to a user's partition must wake the producer. Selecting a +slice from an aggregate can expose a new bulk-Phi user; that selected definition +must be queued too. The optimizer's worklist completeness assertion remains +enabled and checks these dependencies. + +## Evaluation and ordering + +MemMerge is a dependency node; it emits no heap operation. Its evaluator result +is a memory token, while Store mutates the object's fields. The scheduler must +distinguish aggregation from overwriting memory. A MemMerge is not a clobber, +and stores in different alias classes do not impose load/store anti-dependencies. + + + +Tests cover independent-field folding, parallel memory Phis, multiple field +updates surviving at Return, early returns, nested loops with break/continue, +and reads before subsequent stores through potentially aliased pointers. + +## What remains for later chapters? + +Chapter 10b knows every access's alias while parsing. What is lazy here is the +construction of memory partitions. Chapter 25 also allows an existing access's +alias to become known later, using the same representation. Its private +constructor memory, escape tracking, incomplete types, and module alias +remapping are separate extensions and are not needed here. + +Chapters 11-24 currently retain their older parser-managed memory chains. +Forwarding this representation through those snapshots is tracked in the +[backport queue](../docs/chapter-backports.md). Chapter 11's scheduling lesson +therefore still shows the older graph construction, with the same language. + +## Build and test + +With JDK 21 or newer, from this directory: + +```sh +make lib +make tests +make release +make view +``` + +Make builds this complete snapshot without Maven. `make release` writes +`build/release/simple.jar`; `make view` launches the shared graph viewer. +Both the root Maven reactor and the supplied IDEA module descriptor include +the chapter. From the repository root, test both halves with: + +```sh +make tests CHAPTERS="chapter10a chapter10b" +``` diff --git a/chapter10b/chapter10b.iml b/chapter10b/chapter10b.iml new file mode 100644 index 00000000..9d1eeeb2 --- /dev/null +++ b/chapter10b/chapter10b.iml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module type="JAVA_MODULE" version="4"> + <component name="NewModuleRootManager" inherit-compiler-output="false"> + <output url="file://$MODULE_DIR$/../build/idea/production/chapter10b" /> + <output-test url="file://$MODULE_DIR$/../build/idea/test/chapter10b" /> + <exclude-output /> + <content url="file://$MODULE_DIR$"> + <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" /> + <excludeFolder url="file://$MODULE_DIR$/build" /> + <excludeFolder url="file://$MODULE_DIR$/target" /> + </content> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + <orderEntry type="module" module-name="graph" /> + <orderEntry type="module-library" scope="TEST"> + <library name="JUnit4"> + <CLASSES> + <root url="jar://$MODULE_DIR$/lib/junit-4.12.jar!/" /> + <root url="jar://$MODULE_DIR$/lib/hamcrest-core-1.3.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES /> + </library> + </orderEntry> + </component> +</module> diff --git a/chapter10b/docs/10-grammar.md b/chapter10b/docs/10-grammar.md new file mode 100644 index 00000000..9a80d6ea --- /dev/null +++ b/chapter10b/docs/10-grammar.md @@ -0,0 +1,125 @@ +# Grammar for Chapter 10b + +```antlrv4 +grammar SimpleLanguage; + +program + : statement+ EOF + ; + +statement + : returnStatement + | structDeclaration + | declStatement + | blockStatment + | expressionStatement + | ifStatement + | whileStatement + | breakStatement + | continueStatment + ; + +field + : 'int' IDENTIFIER ';' + ; + +fields + : field+ + ; + +structDeclaration + : 'struct' IDENTIFIER '{' fields '}' + ; + +whileStatement + : 'while' '(' expression ')' statement + ; + +breakStatement + : 'break' ';' + ; + +continueStatement + : 'continue' ';' + ; + +ifStatement + : 'if' '(' expression ')' statement ('else' statement)? + ; + + +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 + | '!' 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]; +``` diff --git a/chapter10b/docs/Makefile b/chapter10b/docs/Makefile new file mode 100644 index 00000000..055882b2 --- /dev/null +++ b/chapter10b/docs/Makefile @@ -0,0 +1,14 @@ +GV := $(wildcard *.gv) +SVG := $(patsubst %.gv,%.svg,$(GV)) +.DELETE_ON_ERROR: + +.PHONY: all +all: $(SVG) + +%.svg: %.gv + dot -Tsvg $< > $@ + @sed -i 's/\r$$//' $@ + +.PHONY: clean +clean: + @rm -f $(SVG) diff --git a/chapter10b/docs/example1.gv b/chapter10b/docs/example1.gv new file mode 100644 index 00000000..0b0a5feb --- /dev/null +++ b/chapter10b/docs/example1.gv @@ -0,0 +1,13 @@ +digraph Memory { + rankdir=BT; + node [shape=box]; + M [label="Initial memory"]; + X [label="Store x = 42"]; + Y [label="Loop: Store y"]; + L [label="Load x (42)"]; + L -> X -> M [color=blue]; Y -> M [color=green]; + MemMerge -> X [color=blue]; MemMerge -> Y [color=green]; + Return -> MemMerge; Return -> L; + { rank=same; MemMerge -> L [style=invis]; } + Y -> X [style=invis]; // Layout only: keep the stores in code order. +} diff --git a/chapter10b/docs/example1.svg b/chapter10b/docs/example1.svg new file mode 100644 index 00000000..384b6e12 --- /dev/null +++ b/chapter10b/docs/example1.svg @@ -0,0 +1,93 @@ +<?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 12.2.0 (20241103.1931) + --> +<!-- Title: Memory Pages: 1 --> +<svg width="206pt" height="332pt" + viewBox="0.00 0.00 205.75 332.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 328)"> +<title>Memory</title> +<polygon fill="white" stroke="none" points="-4,4 -4,-328 201.75,-328 201.75,4 -4,4"/> +<!-- M --> +<g id="node1" class="node"> +<title>M</title> +<polygon fill="none" stroke="black" points="131.75,-324 33.25,-324 33.25,-288 131.75,-288 131.75,-324"/> +<text text-anchor="middle" x="82.5" y="-300.95" font-family="Times New Roman,serif" font-size="14.00">Initial memory</text> +</g> +<!-- X --> +<g id="node2" class="node"> +<title>X</title> +<polygon fill="none" stroke="black" points="160.62,-252 76.38,-252 76.38,-216 160.62,-216 160.62,-252"/> +<text text-anchor="middle" x="118.5" y="-228.95" font-family="Times New Roman,serif" font-size="14.00">Store x = 42</text> +</g> +<!-- X->M --> +<g id="edge2" class="edge"> +<title>X->M</title> +<path fill="none" stroke="blue" d="M109.6,-252.3C105.63,-260.02 100.87,-269.29 96.44,-277.89"/> +<polygon fill="blue" stroke="blue" points="93.45,-276.06 91.99,-286.55 99.67,-279.26 93.45,-276.06"/> +</g> +<!-- Y --> +<g id="node3" class="node"> +<title>Y</title> +<polygon fill="none" stroke="black" points="91,-180 0,-180 0,-144 91,-144 91,-180"/> +<text text-anchor="middle" x="45.5" y="-156.95" font-family="Times New Roman,serif" font-size="14.00">Loop: Store y</text> +</g> +<!-- Y->M --> +<g id="edge3" class="edge"> +<title>Y->M</title> +<path fill="none" stroke="green" d="M49.63,-180.44C53.93,-198.41 60.94,-227.23 67.5,-252 69.64,-260.07 72.07,-268.81 74.33,-276.81"/> +<polygon fill="green" stroke="green" points="70.96,-277.75 77.07,-286.4 77.69,-275.82 70.96,-277.75"/> +</g> +<!-- Y->X --> +<!-- L --> +<g id="node4" class="node"> +<title>L</title> +<polygon fill="none" stroke="black" points="197.75,-108 117.25,-108 117.25,-72 197.75,-72 197.75,-108"/> +<text text-anchor="middle" x="157.5" y="-84.95" font-family="Times New Roman,serif" font-size="14.00">Load x (42)</text> +</g> +<!-- L->X --> +<g id="edge1" class="edge"> +<title>L->X</title> +<path fill="none" stroke="blue" d="M152.87,-108.41C148.09,-126.37 140.36,-155.17 133.5,-180 131.28,-188.05 128.82,-196.78 126.56,-204.78"/> +<polygon fill="blue" stroke="blue" points="123.2,-203.8 123.83,-214.37 129.93,-205.71 123.2,-203.8"/> +</g> +<!-- MemMerge --> +<g id="node5" class="node"> +<title>MemMerge</title> +<polygon fill="none" stroke="black" points="99.5,-108 17.5,-108 17.5,-72 99.5,-72 99.5,-108"/> +<text text-anchor="middle" x="58.5" y="-84.95" font-family="Times New Roman,serif" font-size="14.00">MemMerge</text> +</g> +<!-- MemMerge->X --> +<g id="edge4" class="edge"> +<title>MemMerge->X</title> +<path fill="none" stroke="blue" d="M75.59,-108.21C84.32,-117.95 94.37,-130.8 100.5,-144 109.34,-163.03 113.85,-186.32 116.15,-204.31"/> +<polygon fill="blue" stroke="blue" points="112.64,-204.46 117.22,-214.02 119.6,-203.7 112.64,-204.46"/> +</g> +<!-- MemMerge->Y --> +<g id="edge5" class="edge"> +<title>MemMerge->Y</title> +<path fill="none" stroke="green" d="M55.29,-108.3C53.92,-115.68 52.28,-124.48 50.75,-132.75"/> +<polygon fill="green" stroke="green" points="47.34,-131.94 48.95,-142.41 54.22,-133.21 47.34,-131.94"/> +</g> +<!-- MemMerge->L --> +<!-- Return --> +<g id="node6" class="node"> +<title>Return</title> +<polygon fill="none" stroke="black" points="134.5,-36 80.5,-36 80.5,0 134.5,0 134.5,-36"/> +<text text-anchor="middle" x="107.5" y="-12.95" font-family="Times New Roman,serif" font-size="14.00">Return</text> +</g> +<!-- Return->L --> +<g id="edge7" class="edge"> +<title>Return->L</title> +<path fill="none" stroke="black" d="M119.86,-36.3C125.55,-44.27 132.43,-53.9 138.74,-62.74"/> +<polygon fill="black" stroke="black" points="135.74,-64.56 144.4,-70.66 141.44,-60.49 135.74,-64.56"/> +</g> +<!-- Return->MemMerge --> +<g id="edge6" class="edge"> +<title>Return->MemMerge</title> +<path fill="none" stroke="black" d="M95.39,-36.3C89.81,-44.27 83.07,-53.9 76.88,-62.74"/> +<polygon fill="black" stroke="black" points="74.21,-60.46 71.34,-70.66 79.94,-64.47 74.21,-60.46"/> +</g> +</g> +</svg> diff --git a/chapter10b/docs/example2a.gv b/chapter10b/docs/example2a.gv new file mode 100644 index 00000000..d92eea41 --- /dev/null +++ b/chapter10b/docs/example2a.gv @@ -0,0 +1,10 @@ +digraph Memory { + rankdir=RL; + node [shape=box]; + M [label="Default memory"]; + X [label="Store x"]; + Y [label="Store y"]; + X -> M [color=blue]; Y -> M [color=green]; + MemMerge -> M [label="default"]; MemMerge -> X [label="x",color=blue]; + MemMerge -> Y [label="y",color=green]; Return -> MemMerge; +} diff --git a/chapter10b/docs/example2a.svg b/chapter10b/docs/example2a.svg new file mode 100644 index 00000000..97fb144e --- /dev/null +++ b/chapter10b/docs/example2a.svg @@ -0,0 +1,82 @@ +<?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 12.2.0 (20241103.1931) + --> +<!-- Title: Memory Pages: 1 --> +<svg width="423pt" height="126pt" + viewBox="0.00 0.00 422.50 125.93" 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 121.93)"> +<title>Memory</title> +<polygon fill="white" stroke="none" points="-4,4 -4,-121.93 418.5,-121.93 418.5,4 -4,4"/> +<!-- M --> +<g id="node1" class="node"> +<title>M</title> +<polygon fill="none" stroke="black" points="106.75,-90 0,-90 0,-54 106.75,-54 106.75,-90"/> +<text text-anchor="middle" x="53.38" y="-66.95" font-family="Times New Roman,serif" font-size="14.00">Default memory</text> +</g> +<!-- X --> +<g id="node2" class="node"> +<title>X</title> +<polygon fill="none" stroke="black" points="198.75,-90 143.75,-90 143.75,-54 198.75,-54 198.75,-90"/> +<text text-anchor="middle" x="171.25" y="-66.95" font-family="Times New Roman,serif" font-size="14.00">Store x</text> +</g> +<!-- X->M --> +<g id="edge1" class="edge"> +<title>X->M</title> +<path fill="none" stroke="blue" d="M143.37,-72C135.84,-72 127.32,-72 118.6,-72"/> +<polygon fill="blue" stroke="blue" points="118.61,-68.5 108.61,-72 118.61,-75.5 118.61,-68.5"/> +</g> +<!-- Y --> +<g id="node3" class="node"> +<title>Y</title> +<polygon fill="none" stroke="black" points="198.75,-36 143.75,-36 143.75,0 198.75,0 198.75,-36"/> +<text text-anchor="middle" x="171.25" y="-12.95" font-family="Times New Roman,serif" font-size="14.00">Store y</text> +</g> +<!-- Y->M --> +<g id="edge2" class="edge"> +<title>Y->M</title> +<path fill="none" stroke="green" d="M143.37,-30.52C131.65,-35.99 117.53,-42.57 104.01,-48.87"/> +<polygon fill="green" stroke="green" points="102.81,-45.57 95.22,-52.97 105.76,-51.91 102.81,-45.57"/> +</g> +<!-- MemMerge --> +<g id="node4" class="node"> +<title>MemMerge</title> +<polygon fill="none" stroke="black" points="323.5,-90 241.5,-90 241.5,-54 323.5,-54 323.5,-90"/> +<text text-anchor="middle" x="282.5" y="-66.95" font-family="Times New Roman,serif" font-size="14.00">MemMerge</text> +</g> +<!-- MemMerge->M --> +<g id="edge3" class="edge"> +<title>MemMerge->M</title> +<path fill="none" stroke="black" d="M241.39,-88.58C228.08,-93.14 213,-97.3 198.75,-99 174.48,-101.9 167.93,-102.57 143.75,-99 135.07,-97.72 126.06,-95.77 117.26,-93.5"/> +<polygon fill="black" stroke="black" points="118.28,-90.15 107.71,-90.87 116.42,-96.9 118.28,-90.15"/> +<text text-anchor="middle" x="171.25" y="-104.63" font-family="Times New Roman,serif" font-size="14.00">default</text> +</g> +<!-- MemMerge->X --> +<g id="edge4" class="edge"> +<title>MemMerge->X</title> +<path fill="none" stroke="blue" d="M241.13,-72C231.08,-72 220.34,-72 210.4,-72"/> +<polygon fill="blue" stroke="blue" points="210.71,-68.5 200.71,-72 210.71,-75.5 210.71,-68.5"/> +<text text-anchor="middle" x="220.12" y="-75.2" font-family="Times New Roman,serif" font-size="14.00">x</text> +</g> +<!-- MemMerge->Y --> +<g id="edge5" class="edge"> +<title>MemMerge->Y</title> +<path fill="none" stroke="green" d="M244.21,-53.57C232.85,-47.95 220.38,-41.79 209.05,-36.19"/> +<polygon fill="green" stroke="green" points="210.91,-33.2 200.39,-31.91 207.81,-39.48 210.91,-33.2"/> +<text text-anchor="middle" x="220.12" y="-44.97" font-family="Times New Roman,serif" font-size="14.00">y</text> +</g> +<!-- Return --> +<g id="node5" class="node"> +<title>Return</title> +<polygon fill="none" stroke="black" points="414.5,-90 360.5,-90 360.5,-54 414.5,-54 414.5,-90"/> +<text text-anchor="middle" x="387.5" y="-66.95" font-family="Times New Roman,serif" font-size="14.00">Return</text> +</g> +<!-- Return->MemMerge --> +<g id="edge6" class="edge"> +<title>Return->MemMerge</title> +<path fill="none" stroke="black" d="M360.24,-72C352.53,-72 343.82,-72 335.11,-72"/> +<polygon fill="black" stroke="black" points="335.25,-68.5 325.25,-72 335.25,-75.5 335.25,-68.5"/> +</g> +</g> +</svg> diff --git a/chapter10b/docs/example2b.gv b/chapter10b/docs/example2b.gv new file mode 100644 index 00000000..4ddd36b5 --- /dev/null +++ b/chapter10b/docs/example2b.gv @@ -0,0 +1,10 @@ +digraph Memory { + rankdir=RL; + node [shape=box]; + P [label="MemPhi(x)"]; + B [label="BulkMemPhi(All except x)"]; + P -> Left [label="slice x",color=blue]; P -> Right [label="slice x",color=blue]; + B -> Left; B -> Right; + P -> Region [style=dashed]; B -> Region [style=dashed]; + MemMerge -> P [label="x",color=blue]; MemMerge -> B [label="default"]; +} diff --git a/chapter10b/docs/example2b.svg b/chapter10b/docs/example2b.svg new file mode 100644 index 00000000..4e0616f1 --- /dev/null +++ b/chapter10b/docs/example2b.svg @@ -0,0 +1,101 @@ +<?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 12.2.0 (20241103.1931) + --> +<!-- Title: Memory Pages: 1 --> +<svg width="456pt" height="170pt" + viewBox="0.00 0.00 455.75 170.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 166)"> +<title>Memory</title> +<polygon fill="white" stroke="none" points="-4,4 -4,-166 451.75,-166 451.75,4 -4,4"/> +<!-- P --> +<g id="node1" class="node"> +<title>P</title> +<polygon fill="none" stroke="black" points="249.12,-67 169.38,-67 169.38,-31 249.12,-31 249.12,-67"/> +<text text-anchor="middle" x="209.25" y="-43.95" font-family="Times New Roman,serif" font-size="14.00">MemPhi(x)</text> +</g> +<!-- Left --> +<g id="node3" class="node"> +<title>Left</title> +<polygon fill="none" stroke="black" points="54.5,-102 0.5,-102 0.5,-66 54.5,-66 54.5,-102"/> +<text text-anchor="middle" x="27.5" y="-78.95" font-family="Times New Roman,serif" font-size="14.00">Left</text> +</g> +<!-- P->Left --> +<g id="edge1" class="edge"> +<title>P->Left</title> +<path fill="none" stroke="blue" d="M169.09,-45.11C141.55,-43.68 104.19,-44.4 73,-54.5 68.76,-55.87 64.54,-57.77 60.48,-59.95"/> +<polygon fill="blue" stroke="blue" points="58.85,-56.85 52.11,-65.02 62.48,-62.84 58.85,-56.85"/> +<text text-anchor="middle" x="90.62" y="-57.7" font-family="Times New Roman,serif" font-size="14.00">slice x</text> +</g> +<!-- Right --> +<g id="node4" class="node"> +<title>Right</title> +<polygon fill="none" stroke="black" points="54.5,-162 0.5,-162 0.5,-126 54.5,-126 54.5,-162"/> +<text text-anchor="middle" x="27.5" y="-138.95" font-family="Times New Roman,serif" font-size="14.00">Right</text> +</g> +<!-- P->Right --> +<g id="edge2" class="edge"> +<title>P->Right</title> +<path fill="none" stroke="blue" d="M168.93,-64.2C141.32,-75.51 103.94,-92.12 73,-110.5 68.67,-113.07 64.27,-115.98 59.99,-118.98"/> +<polygon fill="blue" stroke="blue" points="58.15,-115.99 52.15,-124.72 62.29,-121.64 58.15,-115.99"/> +<text text-anchor="middle" x="90.62" y="-113.7" font-family="Times New Roman,serif" font-size="14.00">slice x</text> +</g> +<!-- Region --> +<g id="node5" class="node"> +<title>Region</title> +<polygon fill="none" stroke="black" points="55,-36 0,-36 0,0 55,0 55,-36"/> +<text text-anchor="middle" x="27.5" y="-12.95" font-family="Times New Roman,serif" font-size="14.00">Region</text> +</g> +<!-- P->Region --> +<g id="edge5" class="edge"> +<title>P->Region</title> +<path fill="none" stroke="black" stroke-dasharray="5,2" d="M171.76,-30.51C153.24,-22.15 130.1,-13.21 108.25,-9 94.7,-6.39 79.71,-7.15 66.41,-9.05"/> +<polygon fill="black" stroke="black" points="66,-5.57 56.73,-10.71 67.18,-12.47 66,-5.57"/> +</g> +<!-- B --> +<g id="node2" class="node"> +<title>B</title> +<polygon fill="none" stroke="black" points="292.25,-132 126.25,-132 126.25,-96 292.25,-96 292.25,-132"/> +<text text-anchor="middle" x="209.25" y="-108.95" font-family="Times New Roman,serif" font-size="14.00">BulkMemPhi(All except x)</text> +</g> +<!-- B->Left --> +<g id="edge3" class="edge"> +<title>B->Left</title> +<path fill="none" stroke="black" d="M126.03,-119C108.27,-118.2 89.8,-115.9 73,-111 69.91,-110.1 66.82,-108.93 63.78,-107.57"/> +<polygon fill="black" stroke="black" points="65.62,-104.58 55.13,-103.12 62.42,-110.81 65.62,-104.58"/> +</g> +<!-- B->Right --> +<g id="edge4" class="edge"> +<title>B->Right</title> +<path fill="none" stroke="black" d="M126,-127.73C104.92,-131.25 83.28,-134.86 65.75,-137.78"/> +<polygon fill="black" stroke="black" points="65.66,-134.25 56.37,-139.35 66.81,-141.15 65.66,-134.25"/> +</g> +<!-- B->Region --> +<g id="edge6" class="edge"> +<title>B->Region</title> +<path fill="none" stroke="black" stroke-dasharray="5,2" d="M167.07,-95.69C153.92,-89.64 139.38,-82.73 126.25,-76 102.07,-63.61 96.34,-59.9 73,-46 70.37,-44.43 67.67,-42.8 64.95,-41.14"/> +<polygon fill="black" stroke="black" points="66.83,-38.18 56.48,-35.89 63.14,-44.13 66.83,-38.18"/> +</g> +<!-- MemMerge --> +<g id="node6" class="node"> +<title>MemMerge</title> +<polygon fill="none" stroke="black" points="447.75,-94 365.75,-94 365.75,-58 447.75,-58 447.75,-94"/> +<text text-anchor="middle" x="406.75" y="-70.95" font-family="Times New Roman,serif" font-size="14.00">MemMerge</text> +</g> +<!-- MemMerge->P --> +<g id="edge7" class="edge"> +<title>MemMerge->P</title> +<path fill="none" stroke="blue" d="M365.39,-64.23C359.51,-62.81 353.5,-61.5 347.75,-60.5 319.17,-55.53 286.87,-52.69 260.76,-51.07"/> +<polygon fill="blue" stroke="blue" points="261.3,-47.6 251.11,-50.52 260.9,-54.59 261.3,-47.6"/> +<text text-anchor="middle" x="329" y="-63.7" font-family="Times New Roman,serif" font-size="14.00">x</text> +</g> +<!-- MemMerge->B --> +<g id="edge8" class="edge"> +<title>MemMerge->B</title> +<path fill="none" stroke="black" d="M365.72,-83.78C347.65,-87.29 325.5,-91.6 303.63,-95.85"/> +<polygon fill="black" stroke="black" points="303.23,-92.36 294.08,-97.71 304.56,-99.23 303.23,-92.36"/> +<text text-anchor="middle" x="329" y="-97.06" font-family="Times New Roman,serif" font-size="14.00">default</text> +</g> +</g> +</svg> diff --git a/chapter10b/docs/example2c.gv b/chapter10b/docs/example2c.gv new file mode 100644 index 00000000..4fae3bba --- /dev/null +++ b/chapter10b/docs/example2c.gv @@ -0,0 +1,10 @@ +digraph Memory { + rankdir=RL; + node [shape=box]; + M [label="Prior x memory"]; + L [label="Load p.x"]; + S [label="Store p.x"]; + L -> M [color=blue]; S -> M [color=blue]; + S -> L [style=dashed,label="execute load first"]; + Y [label="Store p.y: independent"]; +} diff --git a/chapter10b/docs/example2c.svg b/chapter10b/docs/example2c.svg new file mode 100644 index 00000000..8249d677 --- /dev/null +++ b/chapter10b/docs/example2c.svg @@ -0,0 +1,56 @@ +<?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 12.2.0 (20241103.1931) + --> +<!-- Title: Memory Pages: 1 --> +<svg width="482pt" height="121pt" + viewBox="0.00 0.00 482.25 121.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 117)"> +<title>Memory</title> +<polygon fill="white" stroke="none" points="-4,4 -4,-117 478.25,-117 478.25,4 -4,4"/> +<!-- M --> +<g id="node1" class="node"> +<title>M</title> +<polygon fill="none" stroke="black" points="103.75,-59 0,-59 0,-23 103.75,-23 103.75,-59"/> +<text text-anchor="middle" x="51.88" y="-35.95" font-family="Times New Roman,serif" font-size="14.00">Prior x memory</text> +</g> +<!-- L --> +<g id="node2" class="node"> +<title>L</title> +<polygon fill="none" stroke="black" points="205.5,-36 140.75,-36 140.75,0 205.5,0 205.5,-36"/> +<text text-anchor="middle" x="173.12" y="-12.95" font-family="Times New Roman,serif" font-size="14.00">Load p.x</text> +</g> +<!-- L->M --> +<g id="edge1" class="edge"> +<title>L->M</title> +<path fill="none" stroke="blue" d="M140.48,-24.1C132.62,-25.62 123.92,-27.3 115.15,-28.99"/> +<polygon fill="blue" stroke="blue" points="114.69,-25.51 105.54,-30.84 116.02,-32.39 114.69,-25.51"/> +</g> +<!-- S --> +<g id="node3" class="node"> +<title>S</title> +<polygon fill="none" stroke="black" points="437.5,-59 372,-59 372,-23 437.5,-23 437.5,-59"/> +<text text-anchor="middle" x="404.75" y="-35.95" font-family="Times New Roman,serif" font-size="14.00">Store p.x</text> +</g> +<!-- S->M --> +<g id="edge2" class="edge"> +<title>S->M</title> +<path fill="none" stroke="blue" d="M371.77,-45.13C355.48,-47.03 335.34,-49.06 317.25,-50 248.24,-53.58 169.03,-49.84 115.44,-46.15"/> +<polygon fill="blue" stroke="blue" points="115.81,-42.67 105.58,-45.45 115.31,-49.65 115.81,-42.67"/> +</g> +<!-- S->L --> +<g id="edge3" class="edge"> +<title>S->L</title> +<path fill="none" stroke="black" stroke-dasharray="5,2" d="M371.82,-37.8C331.55,-33.77 262.15,-26.82 217.21,-22.32"/> +<polygon fill="black" stroke="black" points="217.59,-18.84 207.29,-21.32 216.89,-25.8 217.59,-18.84"/> +<text text-anchor="middle" x="270.38" y="-35.44" font-family="Times New Roman,serif" font-size="14.00">execute load first</text> +</g> +<!-- Y --> +<g id="node4" class="node"> +<title>Y</title> +<polygon fill="none" stroke="black" points="474.25,-113 335.25,-113 335.25,-77 474.25,-77 474.25,-113"/> +<text text-anchor="middle" x="404.75" y="-89.95" font-family="Times New Roman,serif" font-size="14.00">Store p.y: independent</text> +</g> +</g> +</svg> diff --git a/chapter10b/docs/lattice.gv b/chapter10b/docs/lattice.gv new file mode 100644 index 00000000..f9fb2b09 --- /dev/null +++ b/chapter10b/docs/lattice.gv @@ -0,0 +1,114 @@ +digraph Lattice { + rankdir=TB; + newrank=true; + // Preserve the same-rank ordering edges through cluster layout. + remincross=false; + nodesep=0.22; + ranksep=0.65; + node [shape=ellipse, style=filled, fontname="DejaVu Sans", fontsize=13]; + edge [color="#555555", arrowsize=0.6]; + + // These arrows are lattice-order edges, not use/definition edges. + TOP [label="⊤", fillcolor=black, fontcolor=white]; + BOT [label="⊥", fillcolor=black, fontcolor=white]; + + subgraph cluster_control { + label="Control"; fontname="Arial"; color="#cccccc"; + node [fillcolor="#ffff00"]; + XCTRL [label="⊤:ctrl"]; + CTRL [label="⊥:ctrl"]; + XCTRL -> CTRL; + } + + subgraph cluster_integer { + label="Integer"; fontname="Arial"; color="#cccccc"; + node [fillcolor="#add8e6"]; + INT_TOP [label="⊤:int"]; + INT_BOT [label="⊥:int"]; + INT_NEG [label="… −2, −1"]; + INT_ZERO [label="0"]; + INT_POS [label="1, 2, …"]; + {rank=same; INT_NEG; INT_ZERO; INT_POS;} + INT_NEG -> INT_ZERO -> INT_POS [style=invis]; + INT_TOP -> {INT_NEG INT_ZERO INT_POS}; + {INT_NEG INT_ZERO INT_POS} -> INT_BOT; + } + + subgraph cluster_pointer { + label="Pointer = nullability × struct type"; + fontname="Arial"; color="#cccccc"; + node [fillcolor="#008000", fontcolor=white]; + PTR_TOP [label="⊤:ptr\n[*⊤:struct]"]; + PTR_BOT [label="⊥:ptr\n[*⊥:struct?]"]; + PTR_NULL [label="null"]; + PTR_ANY [label="*⊥:struct\n[ptr any struct]"]; + PTR_PERSON [label="*Person"]; + PTR_S1 [label="*s1"]; + PTR_MORE [label="*s2, …"]; + PTR_PERSON_NIL [label="*Person?"]; + PTR_S1_NIL [label="*s1?"]; + PTR_MORE_NIL [label="*s2?, …"]; + {rank=same; PTR_PERSON; PTR_S1; PTR_MORE; PTR_NULL;} + {rank=same; PTR_ANY; PTR_PERSON_NIL; PTR_S1_NIL; PTR_MORE_NIL;} + PTR_PERSON -> PTR_S1 -> PTR_MORE -> PTR_NULL [style=invis]; + PTR_ANY -> PTR_PERSON_NIL -> PTR_S1_NIL -> PTR_MORE_NIL [style=invis]; + PTR_TOP -> {PTR_PERSON PTR_S1 PTR_MORE PTR_NULL}; + {PTR_PERSON PTR_S1 PTR_MORE} -> PTR_ANY; + PTR_PERSON -> PTR_PERSON_NIL; + PTR_S1 -> PTR_S1_NIL; + PTR_MORE -> PTR_MORE_NIL; + PTR_NULL -> {PTR_PERSON_NIL PTR_S1_NIL PTR_MORE_NIL}; + {PTR_ANY PTR_PERSON_NIL PTR_S1_NIL PTR_MORE_NIL} -> PTR_BOT; + } + + subgraph cluster_struct { + label="Struct\nnamed collection of named scalar types"; + fontname="Arial"; color="#cccccc"; + node [fillcolor="#90ee90"]; + STRUCT_TOP [label="⊤:struct\n[struct top]"]; + STRUCT_BOT [label="⊥:struct\n[struct bottom]"]; + STRUCT_PERSON [label="Person\nage: int", shape=box]; + STRUCT_S1 [label="s1\nx: int", shape=box]; + STRUCT_S2 [label="s2\nx: int, y: *s1", shape=box]; + STRUCT_MORE [label="…", shape=box]; + {rank=same; STRUCT_PERSON; STRUCT_S1; STRUCT_S2; STRUCT_MORE;} + STRUCT_PERSON -> STRUCT_S1 -> STRUCT_S2 -> STRUCT_MORE [style=invis]; + STRUCT_TOP -> {STRUCT_PERSON STRUCT_S1 STRUCT_S2 STRUCT_MORE}; + {STRUCT_PERSON STRUCT_S1 STRUCT_S2 STRUCT_MORE} -> STRUCT_BOT; + } + + subgraph cluster_memory { + label="Memory\nprecise alias slices"; fontname="Arial"; color="#cccccc"; + node [fillcolor="#00008b", fontcolor=white]; + MEM_TOP [label="⊤:mem"]; + MEM_BOT [label="⊥:mem"]; + MEM_ALIAS2 [label="MEM#2"]; + MEM_ALIAS3 [label="MEM#3"]; + MEM_MORE [label="…"]; + {rank=same; MEM_ALIAS2; MEM_ALIAS3; MEM_MORE;} + MEM_ALIAS2 -> MEM_ALIAS3 -> MEM_MORE [style=invis]; + MEM_TOP -> {MEM_ALIAS2 MEM_ALIAS3 MEM_MORE}; + {MEM_ALIAS2 MEM_ALIAS3 MEM_MORE} -> MEM_BOT; + } + + subgraph cluster_tuple { + label="Tuple\ncollection of types"; + fontname="Arial"; color="#cccccc"; + node [fillcolor="#ffc0cb"]; + TUPLE_TOP [label="⊤:tuple"]; + TUPLE_BOT [label="⊥:tuple"]; + TUPLE1 [label="(type)", shape=box]; + TUPLE2 [label="(type, type)", shape=box]; + TUPLEN [label="(type, …)", shape=box]; + {rank=same; TUPLE1; TUPLE2; TUPLEN;} + TUPLE1 -> TUPLE2 -> TUPLEN [style=invis]; + TUPLE_TOP -> {TUPLE1 TUPLE2 TUPLEN}; + {TUPLE1 TUPLE2 TUPLEN} -> TUPLE_BOT; + } + + TOP -> {XCTRL INT_TOP PTR_TOP STRUCT_TOP MEM_TOP TUPLE_TOP}; + {CTRL INT_BOT PTR_BOT STRUCT_BOT MEM_BOT TUPLE_BOT} -> BOT; + {rank=same; XCTRL; INT_TOP; PTR_TOP; STRUCT_TOP; MEM_TOP; TUPLE_TOP;} + {rank=same; CTRL; INT_BOT; PTR_BOT; STRUCT_BOT; MEM_BOT; TUPLE_BOT;} + XCTRL -> INT_TOP -> PTR_TOP -> STRUCT_TOP -> MEM_TOP -> TUPLE_TOP [style=invis]; +} diff --git a/chapter10b/docs/lattice.svg b/chapter10b/docs/lattice.svg new file mode 100644 index 00000000..a11452b9 --- /dev/null +++ b/chapter10b/docs/lattice.svg @@ -0,0 +1,620 @@ +<?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 12.2.0 (20241103.1931) + --> +<!-- Title: Lattice Pages: 1 --> +<svg width="1827pt" height="533pt" + viewBox="0.00 0.00 1827.00 533.45" 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 529.45)"> +<title>Lattice</title> +<polygon fill="white" stroke="none" points="-4,4 -4,-529.45 1823,-529.45 1823,4 -4,4"/> +<g id="clust1" class="cluster"> +<title>cluster_control</title> +<polygon fill="none" stroke="#cccccc" points="8,-85.99 8,-454.71 98,-454.71 98,-85.99 8,-85.99"/> +<text text-anchor="middle" x="53" y="-437.41" font-family="Arial" font-size="14.00">Control</text> +</g> +<g id="clust2" class="cluster"> +<title>cluster_integer</title> +<polygon fill="none" stroke="#cccccc" points="106,-85.99 106,-454.71 404,-454.71 404,-85.99 106,-85.99"/> +<text text-anchor="middle" x="255" y="-437.41" font-family="Arial" font-size="14.00">Integer</text> +</g> +<g id="clust6" class="cluster"> +<title>cluster_pointer</title> +<polygon fill="none" stroke="#cccccc" points="412,-75 412,-465.7 893,-465.7 893,-75 412,-75"/> +<text text-anchor="middle" x="652.5" y="-448.4" font-family="Arial" font-size="14.00">Pointer = nullability × struct type</text> +</g> +<g id="clust13" class="cluster"> +<title>cluster_struct</title> +<polygon fill="none" stroke="#cccccc" points="901,-75 901,-481.45 1237,-481.45 1237,-75 901,-75"/> +<text text-anchor="middle" x="1069" y="-464.15" font-family="Arial" font-size="14.00">Struct</text> +<text text-anchor="middle" x="1069" y="-448.4" font-family="Arial" font-size="14.00">named collection of named scalar types</text> +</g> +<g id="clust17" class="cluster"> +<title>cluster_memory</title> +<polygon fill="none" stroke="#cccccc" points="1245,-85.99 1245,-470.46 1527,-470.46 1527,-85.99 1245,-85.99"/> +<text text-anchor="middle" x="1386" y="-453.16" font-family="Arial" font-size="14.00">Memory</text> +<text text-anchor="middle" x="1386" y="-437.41" font-family="Arial" font-size="14.00">precise alias slices</text> +</g> +<g id="clust21" class="cluster"> +<title>cluster_tuple</title> +<polygon fill="none" stroke="#cccccc" points="1535,-85.99 1535,-470.46 1811,-470.46 1811,-85.99 1535,-85.99"/> +<text text-anchor="middle" x="1673" y="-453.16" font-family="Arial" font-size="14.00">Tuple</text> +<text text-anchor="middle" x="1673" y="-437.41" font-family="Arial" font-size="14.00">collection of types</text> +</g> +<!-- TOP --> +<g id="node1" class="node"> +<title>TOP</title> +<ellipse fill="black" stroke="black" cx="887" cy="-507.45" rx="27" ry="18"/> +<text text-anchor="middle" x="887" y="-503.35" font-family="DejaVu Sans" font-size="13.00" fill="white">⊤</text> +</g> +<!-- XCTRL --> +<g id="node3" class="node"> +<title>XCTRL</title> +<ellipse fill="#ffff00" stroke="black" cx="53" cy="-404.96" rx="36.51" ry="18"/> +<text text-anchor="middle" x="53" y="-400.86" font-family="DejaVu Sans" font-size="13.00">⊤:ctrl</text> +</g> +<!-- TOP->XCTRL --> +<g id="edge60" class="edge"> +<title>TOP->XCTRL</title> +<path fill="none" stroke="#555555" d="M859.92,-506.29C725.56,-505.4 135.66,-500.43 102,-481.45 82.17,-470.27 69.05,-447.37 61.41,-429.71"/> +<polygon fill="#555555" stroke="#555555" points="63.45,-429.15 59.23,-424.39 59.57,-430.74 63.45,-429.15"/> +</g> +<!-- INT_TOP --> +<g id="node5" class="node"> +<title>INT_TOP</title> +<ellipse fill="#add8e6" stroke="black" cx="353" cy="-404.96" rx="33.44" ry="18"/> +<text text-anchor="middle" x="353" y="-400.86" font-family="DejaVu Sans" font-size="13.00">⊤:int</text> +</g> +<!-- TOP->INT_TOP --> +<g id="edge61" class="edge"> +<title>TOP->INT_TOP</title> +<path fill="none" stroke="#555555" d="M859.8,-506.38C764.27,-505.97 447.72,-502.97 408,-481.45 387.23,-470.19 372.29,-447.31 363.27,-429.67"/> +<polygon fill="#555555" stroke="#555555" points="365.18,-428.79 360.66,-424.32 361.41,-430.63 365.18,-428.79"/> +</g> +<!-- PTR_TOP --> +<g id="node10" class="node"> +<title>PTR_TOP</title> +<ellipse fill="#008000" stroke="black" cx="715" cy="-404.96" rx="61.7" ry="28.99"/> +<text text-anchor="middle" x="715" y="-409.11" font-family="DejaVu Sans" font-size="13.00" fill="white">⊤:ptr</text> +<text text-anchor="middle" x="715" y="-392.61" font-family="DejaVu Sans" font-size="13.00" fill="white">[*⊤:struct]</text> +</g> +<!-- TOP->PTR_TOP --> +<g id="edge62" class="edge"> +<title>TOP->PTR_TOP</title> +<path fill="none" stroke="#555555" d="M867.27,-494.92C841.1,-479.63 794.17,-452.21 759.3,-431.84"/> +<polygon fill="#555555" stroke="#555555" points="760.48,-430.1 754.24,-428.88 758.36,-433.72 760.48,-430.1"/> +</g> +<!-- STRUCT_TOP --> +<g id="node20" class="node"> +<title>STRUCT_TOP</title> +<ellipse fill="#90ee90" stroke="black" cx="1020" cy="-404.96" rx="64.35" ry="28.99"/> +<text text-anchor="middle" x="1020" y="-409.11" font-family="DejaVu Sans" font-size="13.00">⊤:struct</text> +<text text-anchor="middle" x="1020" y="-392.61" font-family="DejaVu Sans" font-size="13.00">[struct top]</text> +</g> +<!-- TOP->STRUCT_TOP --> +<g id="edge63" class="edge"> +<title>TOP->STRUCT_TOP</title> +<path fill="none" stroke="#555555" d="M904.39,-493.31C923.88,-478.58 956.09,-454.25 981.56,-435"/> +<polygon fill="#555555" stroke="#555555" points="982.82,-436.68 986.34,-431.39 980.29,-433.33 982.82,-436.68"/> +</g> +<!-- MEM_TOP --> +<g id="node26" class="node"> +<title>MEM_TOP</title> +<ellipse fill="#00008b" stroke="black" cx="1351" cy="-404.96" rx="44.7" ry="18"/> +<text text-anchor="middle" x="1351" y="-400.86" font-family="DejaVu Sans" font-size="13.00" fill="white">⊤:mem</text> +</g> +<!-- TOP->MEM_TOP --> +<g id="edge64" class="edge"> +<title>TOP->MEM_TOP</title> +<path fill="none" stroke="#555555" d="M914.33,-506.36C986.5,-505.84 1180.8,-502.47 1241,-481.45 1274.58,-469.72 1307.45,-444.9 1328.39,-426.91"/> +<polygon fill="#555555" stroke="#555555" points="1329.69,-428.56 1332.83,-423.03 1326.93,-425.4 1329.69,-428.56"/> +</g> +<!-- TUPLE_TOP --> +<g id="node31" class="node"> +<title>TUPLE_TOP</title> +<ellipse fill="#ffc0cb" stroke="black" cx="1588" cy="-404.96" rx="44.7" ry="18"/> +<text text-anchor="middle" x="1588" y="-400.86" font-family="DejaVu Sans" font-size="13.00">⊤:tuple</text> +</g> +<!-- TOP->TUPLE_TOP --> +<g id="edge65" class="edge"> +<title>TOP->TUPLE_TOP</title> +<path fill="none" stroke="#555555" d="M914.36,-506.76C1030.08,-507.88 1475.84,-510.27 1531,-481.45 1552.39,-470.27 1568.01,-447.05 1577.43,-429.3"/> +<polygon fill="#555555" stroke="#555555" points="1579.08,-430.68 1579.93,-424.38 1575.34,-428.78 1579.08,-430.68"/> +</g> +<!-- BOT --> +<g id="node2" class="node"> +<title>BOT</title> +<ellipse fill="black" stroke="black" cx="884" cy="-18" rx="27" ry="18"/> +<text text-anchor="middle" x="884" y="-13.9" font-family="DejaVu Sans" font-size="13.00" fill="white">⊥</text> +</g> +<!-- CTRL --> +<g id="node4" class="node"> +<title>CTRL</title> +<ellipse fill="#ffff00" stroke="black" cx="53" cy="-111.99" rx="36.51" ry="18"/> +<text text-anchor="middle" x="53" y="-107.89" font-family="DejaVu Sans" font-size="13.00">⊥:ctrl</text> +</g> +<!-- XCTRL->CTRL --> +<g id="edge1" class="edge"> +<title>XCTRL->CTRL</title> +<path fill="none" stroke="#555555" d="M53,-386.63C53,-337.1 53,-194.83 53,-137.43"/> +<polygon fill="#555555" stroke="#555555" points="55.1,-137.74 53,-131.74 50.9,-137.74 55.1,-137.74"/> +</g> +<!-- XCTRL->INT_TOP --> +<!-- CTRL->BOT --> +<g id="edge66" class="edge"> +<title>CTRL->BOT</title> +<path fill="none" stroke="#555555" d="M69.29,-95.39C78.22,-87.83 89.91,-79.45 102,-75 242.26,-23.4 717.31,-19.12 849.57,-18.95"/> +<polygon fill="#555555" stroke="#555555" points="849.19,-21.05 855.19,-18.94 849.18,-16.85 849.19,-21.05"/> +</g> +<!-- INT_NEG --> +<g id="node7" class="node"> +<title>INT_NEG</title> +<ellipse fill="#add8e6" stroke="black" cx="169" cy="-310.97" rx="55.45" ry="18"/> +<text text-anchor="middle" x="169" y="-306.87" font-family="DejaVu Sans" font-size="13.00">… −2, −1</text> +</g> +<!-- INT_TOP->INT_NEG --> +<g id="edge4" class="edge"> +<title>INT_TOP->INT_NEG</title> +<path fill="none" stroke="#555555" d="M328.95,-391.93C297.31,-376.11 241.45,-348.19 204.83,-329.88"/> +<polygon fill="#555555" stroke="#555555" points="205.88,-328.06 199.58,-327.25 204,-331.81 205.88,-328.06"/> +</g> +<!-- INT_ZERO --> +<g id="node8" class="node"> +<title>INT_ZERO</title> +<ellipse fill="#add8e6" stroke="black" cx="267" cy="-310.97" rx="27" ry="18"/> +<text text-anchor="middle" x="267" y="-306.87" font-family="DejaVu Sans" font-size="13.00">0</text> +</g> +<!-- INT_TOP->INT_ZERO --> +<g id="edge5" class="edge"> +<title>INT_TOP->INT_ZERO</title> +<path fill="none" stroke="#555555" d="M338.41,-388.35C323.94,-372.88 301.71,-349.1 285.9,-332.18"/> +<polygon fill="#555555" stroke="#555555" points="287.47,-330.79 281.84,-327.84 284.4,-333.66 287.47,-330.79"/> +</g> +<!-- INT_POS --> +<g id="node9" class="node"> +<title>INT_POS</title> +<ellipse fill="#add8e6" stroke="black" cx="353" cy="-310.97" rx="43.16" ry="18"/> +<text text-anchor="middle" x="353" y="-306.87" font-family="DejaVu Sans" font-size="13.00">1, 2, …</text> +</g> +<!-- INT_TOP->INT_POS --> +<g id="edge6" class="edge"> +<title>INT_TOP->INT_POS</title> +<path fill="none" stroke="#555555" d="M353,-386.65C353,-372.52 353,-352.37 353,-336.56"/> +<polygon fill="#555555" stroke="#555555" points="355.1,-336.7 353,-330.7 350.9,-336.7 355.1,-336.7"/> +</g> +<!-- INT_TOP->PTR_TOP --> +<!-- INT_BOT --> +<g id="node6" class="node"> +<title>INT_BOT</title> +<ellipse fill="#add8e6" stroke="black" cx="289" cy="-111.99" rx="33.44" ry="18"/> +<text text-anchor="middle" x="289" y="-107.89" font-family="DejaVu Sans" font-size="13.00">⊥:int</text> +</g> +<!-- INT_BOT->BOT --> +<g id="edge67" class="edge"> +<title>INT_BOT->BOT</title> +<path fill="none" stroke="#555555" d="M316.06,-101.31C339.95,-93.13 375.88,-81.69 408,-75 571.35,-40.97 770.86,-25.89 849.65,-20.95"/> +<polygon fill="#555555" stroke="#555555" points="849.52,-23.07 855.38,-20.6 849.26,-18.87 849.52,-23.07"/> +</g> +<!-- INT_NEG->INT_BOT --> +<g id="edge7" class="edge"> +<title>INT_NEG->INT_BOT</title> +<path fill="none" stroke="#555555" d="M179.44,-292.83C201.14,-257.2 251.13,-175.15 275.35,-135.4"/> +<polygon fill="#555555" stroke="#555555" points="276.89,-136.9 278.22,-130.68 273.31,-134.72 276.89,-136.9"/> +</g> +<!-- INT_NEG->INT_ZERO --> +<!-- INT_ZERO->INT_BOT --> +<g id="edge8" class="edge"> +<title>INT_ZERO->INT_BOT</title> +<path fill="none" stroke="#555555" d="M268.91,-292.83C272.82,-257.83 281.73,-178.04 286.26,-137.55"/> +<polygon fill="#555555" stroke="#555555" points="288.33,-137.93 286.91,-131.73 284.15,-137.46 288.33,-137.93"/> +</g> +<!-- INT_ZERO->INT_POS --> +<!-- INT_POS->INT_BOT --> +<g id="edge9" class="edge"> +<title>INT_POS->INT_BOT</title> +<path fill="none" stroke="#555555" d="M347.43,-292.83C336.04,-257.76 310.03,-177.71 296.9,-137.29"/> +<polygon fill="#555555" stroke="#555555" points="298.92,-136.72 295.07,-131.67 294.92,-138.02 298.92,-136.72"/> +</g> +<!-- PTR_NULL --> +<g id="node12" class="node"> +<title>PTR_NULL</title> +<ellipse fill="#008000" stroke="black" cx="799" cy="-310.97" rx="27.3" ry="18"/> +<text text-anchor="middle" x="799" y="-306.87" font-family="DejaVu Sans" font-size="13.00" fill="white">null</text> +</g> +<!-- PTR_TOP->PTR_NULL --> +<g id="edge16" class="edge"> +<title>PTR_TOP->PTR_NULL</title> +<path fill="none" stroke="#555555" d="M738.85,-377.84C752,-363.43 768.1,-345.8 780.33,-332.41"/> +<polygon fill="#555555" stroke="#555555" points="781.8,-333.91 784.3,-328.06 778.7,-331.08 781.8,-333.91"/> +</g> +<!-- PTR_PERSON --> +<g id="node14" class="node"> +<title>PTR_PERSON</title> +<ellipse fill="#008000" stroke="black" cx="542" cy="-310.97" rx="45.72" ry="18"/> +<text text-anchor="middle" x="542" y="-306.87" font-family="DejaVu Sans" font-size="13.00" fill="white">*Person</text> +</g> +<!-- PTR_TOP->PTR_PERSON --> +<g id="edge17" class="edge"> +<title>PTR_TOP->PTR_PERSON</title> +<path fill="none" stroke="#555555" d="M674.88,-382.62C644.57,-366.5 603.47,-344.65 575.04,-329.54"/> +<polygon fill="#555555" stroke="#555555" points="576.08,-327.71 569.8,-326.75 574.11,-331.42 576.08,-327.71"/> +</g> +<!-- PTR_S1 --> +<g id="node15" class="node"> +<title>PTR_S1</title> +<ellipse fill="#008000" stroke="black" cx="631" cy="-310.97" rx="27" ry="18"/> +<text text-anchor="middle" x="631" y="-306.87" font-family="DejaVu Sans" font-size="13.00" fill="white">*s1</text> +</g> +<!-- PTR_TOP->PTR_S1 --> +<g id="edge18" class="edge"> +<title>PTR_TOP->PTR_S1</title> +<path fill="none" stroke="#555555" d="M691.15,-377.84C678,-363.43 661.9,-345.8 649.67,-332.41"/> +<polygon fill="#555555" stroke="#555555" points="651.3,-331.08 645.7,-328.06 648.2,-333.91 651.3,-331.08"/> +</g> +<!-- PTR_MORE --> +<g id="node16" class="node"> +<title>PTR_MORE</title> +<ellipse fill="#008000" stroke="black" cx="715" cy="-310.97" rx="40.6" ry="18"/> +<text text-anchor="middle" x="715" y="-306.87" font-family="DejaVu Sans" font-size="13.00" fill="white">*s2, …</text> +</g> +<!-- PTR_TOP->PTR_MORE --> +<g id="edge19" class="edge"> +<title>PTR_TOP->PTR_MORE</title> +<path fill="none" stroke="#555555" d="M715,-375.61C715,-363.22 715,-348.82 715,-336.86"/> +<polygon fill="#555555" stroke="#555555" points="717.1,-336.91 715,-330.91 712.9,-336.91 717.1,-336.91"/> +</g> +<!-- PTR_TOP->STRUCT_TOP --> +<!-- PTR_BOT --> +<g id="node11" class="node"> +<title>PTR_BOT</title> +<ellipse fill="#008000" stroke="black" cx="748" cy="-111.99" rx="66.47" ry="28.99"/> +<text text-anchor="middle" x="748" y="-116.14" font-family="DejaVu Sans" font-size="13.00" fill="white">⊥:ptr</text> +<text text-anchor="middle" x="748" y="-99.64" font-family="DejaVu Sans" font-size="13.00" fill="white">[*⊥:struct?]</text> +</g> +<!-- PTR_BOT->BOT --> +<g id="edge68" class="edge"> +<title>PTR_BOT->BOT</title> +<path fill="none" stroke="#555555" d="M783.38,-87.06C807.3,-70.88 838.29,-49.92 859.52,-35.56"/> +<polygon fill="#555555" stroke="#555555" points="860.46,-37.46 864.25,-32.36 858.1,-33.98 860.46,-37.46"/> +</g> +<!-- PTR_PERSON_NIL --> +<g id="node17" class="node"> +<title>PTR_PERSON_NIL</title> +<ellipse fill="#008000" stroke="black" cx="651" cy="-216.97" rx="50.33" ry="18"/> +<text text-anchor="middle" x="651" y="-212.87" font-family="DejaVu Sans" font-size="13.00" fill="white">*Person?</text> +</g> +<!-- PTR_NULL->PTR_PERSON_NIL --> +<g id="edge26" class="edge"> +<title>PTR_NULL->PTR_PERSON_NIL</title> +<path fill="none" stroke="#555555" d="M779.65,-297.94C754.63,-282.39 710.79,-255.14 681.32,-236.82"/> +<polygon fill="#555555" stroke="#555555" points="682.76,-235.24 676.55,-233.86 680.54,-238.81 682.76,-235.24"/> +</g> +<!-- PTR_S1_NIL --> +<g id="node18" class="node"> +<title>PTR_S1_NIL</title> +<ellipse fill="#008000" stroke="black" cx="748" cy="-216.97" rx="30.37" ry="18"/> +<text text-anchor="middle" x="748" y="-212.87" font-family="DejaVu Sans" font-size="13.00" fill="white">*s1?</text> +</g> +<!-- PTR_NULL->PTR_S1_NIL --> +<g id="edge27" class="edge"> +<title>PTR_NULL->PTR_S1_NIL</title> +<path fill="none" stroke="#555555" d="M789.88,-293.52C781.74,-278.83 769.73,-257.16 760.65,-240.8"/> +<polygon fill="#555555" stroke="#555555" points="762.69,-240.14 757.94,-235.91 759.02,-242.18 762.69,-240.14"/> +</g> +<!-- PTR_MORE_NIL --> +<g id="node19" class="node"> +<title>PTR_MORE_NIL</title> +<ellipse fill="#008000" stroke="black" cx="840" cy="-216.97" rx="45.21" ry="18"/> +<text text-anchor="middle" x="840" y="-212.87" font-family="DejaVu Sans" font-size="13.00" fill="white">*s2?, …</text> +</g> +<!-- PTR_NULL->PTR_MORE_NIL --> +<g id="edge28" class="edge"> +<title>PTR_NULL->PTR_MORE_NIL</title> +<path fill="none" stroke="#555555" d="M806.33,-293.52C812.78,-279.05 822.24,-257.81 829.5,-241.54"/> +<polygon fill="#555555" stroke="#555555" points="831.32,-242.62 831.84,-236.28 827.48,-240.91 831.32,-242.62"/> +</g> +<!-- PTR_ANY --> +<g id="node13" class="node"> +<title>PTR_ANY</title> +<ellipse fill="#008000" stroke="black" cx="502" cy="-216.97" rx="82.38" ry="28.99"/> +<text text-anchor="middle" x="502" y="-221.12" font-family="DejaVu Sans" font-size="13.00" fill="white">*⊥:struct</text> +<text text-anchor="middle" x="502" y="-204.62" font-family="DejaVu Sans" font-size="13.00" fill="white">[ptr any struct]</text> +</g> +<!-- PTR_ANY->PTR_BOT --> +<g id="edge29" class="edge"> +<title>PTR_ANY->PTR_BOT</title> +<path fill="none" stroke="#555555" d="M554.17,-194.13C595.32,-176.91 652.51,-152.97 694.1,-135.56"/> +<polygon fill="#555555" stroke="#555555" points="694.78,-137.55 699.5,-133.29 693.15,-133.67 694.78,-137.55"/> +</g> +<!-- PTR_ANY->PTR_PERSON_NIL --> +<!-- PTR_PERSON->PTR_ANY --> +<g id="edge20" class="edge"> +<title>PTR_PERSON->PTR_ANY</title> +<path fill="none" stroke="#555555" d="M534.66,-293.09C529.74,-281.77 523.08,-266.45 517.05,-252.6"/> +<polygon fill="#555555" stroke="#555555" points="519.08,-251.99 514.76,-247.33 515.23,-253.67 519.08,-251.99"/> +</g> +<!-- PTR_PERSON->PTR_S1 --> +<!-- PTR_PERSON->PTR_PERSON_NIL --> +<g id="edge23" class="edge"> +<title>PTR_PERSON->PTR_PERSON_NIL</title> +<path fill="none" stroke="#555555" d="M560.49,-294.36C578.62,-279.06 606.38,-255.63 626.38,-238.76"/> +<polygon fill="#555555" stroke="#555555" points="627.67,-240.42 630.9,-234.94 624.96,-237.21 627.67,-240.42"/> +</g> +<!-- PTR_S1->PTR_ANY --> +<g id="edge21" class="edge"> +<title>PTR_S1->PTR_ANY</title> +<path fill="none" stroke="#555555" d="M613.06,-297.17C595.19,-284.43 567.11,-264.41 543.66,-247.68"/> +<polygon fill="#555555" stroke="#555555" points="545.14,-246.16 539.04,-244.39 542.7,-249.58 545.14,-246.16"/> +</g> +<!-- PTR_S1->PTR_MORE --> +<!-- PTR_S1->PTR_S1_NIL --> +<g id="edge24" class="edge"> +<title>PTR_S1->PTR_S1_NIL</title> +<path fill="none" stroke="#555555" d="M648.01,-296.59C668.14,-280.76 701.78,-254.32 724.41,-236.52"/> +<polygon fill="#555555" stroke="#555555" points="725.59,-238.26 729.01,-232.91 722.99,-234.96 725.59,-238.26"/> +</g> +<!-- PTR_MORE->PTR_NULL --> +<!-- PTR_MORE->PTR_ANY --> +<g id="edge22" class="edge"> +<title>PTR_MORE->PTR_ANY</title> +<path fill="none" stroke="#555555" d="M686.72,-297.75C654.45,-283.81 600.84,-260.66 559.66,-242.88"/> +<polygon fill="#555555" stroke="#555555" points="560.63,-241.01 554.29,-240.56 558.97,-244.86 560.63,-241.01"/> +</g> +<!-- PTR_MORE->PTR_MORE_NIL --> +<g id="edge25" class="edge"> +<title>PTR_MORE->PTR_MORE_NIL</title> +<path fill="none" stroke="#555555" d="M735.36,-294.98C756.57,-279.37 789.9,-254.84 813.23,-237.67"/> +<polygon fill="#555555" stroke="#555555" points="814.42,-239.4 818.01,-234.16 811.93,-236.02 814.42,-239.4"/> +</g> +<!-- PTR_PERSON_NIL->PTR_BOT --> +<g id="edge30" class="edge"> +<title>PTR_PERSON_NIL->PTR_BOT</title> +<path fill="none" stroke="#555555" d="M666.58,-199.43C680.58,-184.56 701.51,-162.35 718.65,-144.16"/> +<polygon fill="#555555" stroke="#555555" points="719.75,-146.05 722.33,-140.24 716.69,-143.17 719.75,-146.05"/> +</g> +<!-- PTR_PERSON_NIL->PTR_S1_NIL --> +<!-- PTR_S1_NIL->PTR_BOT --> +<g id="edge31" class="edge"> +<title>PTR_S1_NIL->PTR_BOT</title> +<path fill="none" stroke="#555555" d="M748,-198.5C748,-184.97 748,-165.76 748,-148.97"/> +<polygon fill="#555555" stroke="#555555" points="750.1,-148.97 748,-142.97 745.9,-148.97 750.1,-148.97"/> +</g> +<!-- PTR_S1_NIL->PTR_MORE_NIL --> +<!-- PTR_MORE_NIL->PTR_BOT --> +<g id="edge32" class="edge"> +<title>PTR_MORE_NIL->PTR_BOT</title> +<path fill="none" stroke="#555555" d="M825.62,-199.88C812.53,-185.22 792.8,-163.14 776.51,-144.9"/> +<polygon fill="#555555" stroke="#555555" points="778.14,-143.58 772.58,-140.51 775.01,-146.38 778.14,-143.58"/> +</g> +<!-- STRUCT_PERSON --> +<g id="node22" class="node"> +<title>STRUCT_PERSON</title> +<polygon fill="#90ee90" stroke="black" points="976.5,-237.47 909.5,-237.47 909.5,-196.47 976.5,-196.47 976.5,-237.47"/> +<text text-anchor="middle" x="943" y="-221.12" font-family="DejaVu Sans" font-size="13.00">Person</text> +<text text-anchor="middle" x="943" y="-204.62" font-family="DejaVu Sans" font-size="13.00">age: int</text> +</g> +<!-- STRUCT_TOP->STRUCT_PERSON --> +<g id="edge36" class="edge"> +<title>STRUCT_TOP->STRUCT_PERSON</title> +<path fill="none" stroke="#555555" d="M1008.47,-376.11C993.72,-340.49 968.36,-279.23 953.91,-244.33"/> +<polygon fill="#555555" stroke="#555555" points="955.99,-243.86 951.75,-239.12 952.11,-245.47 955.99,-243.86"/> +</g> +<!-- STRUCT_S1 --> +<g id="node23" class="node"> +<title>STRUCT_S1</title> +<polygon fill="#90ee90" stroke="black" points="1047,-237.47 993,-237.47 993,-196.47 1047,-196.47 1047,-237.47"/> +<text text-anchor="middle" x="1020" y="-221.12" font-family="DejaVu Sans" font-size="13.00">s1</text> +<text text-anchor="middle" x="1020" y="-204.62" font-family="DejaVu Sans" font-size="13.00">x: int</text> +</g> +<!-- STRUCT_TOP->STRUCT_S1 --> +<g id="edge37" class="edge"> +<title>STRUCT_TOP->STRUCT_S1</title> +<path fill="none" stroke="#555555" d="M1020,-375.71C1020,-340.38 1020,-280.29 1020,-245.36"/> +<polygon fill="#555555" stroke="#555555" points="1022.1,-245.4 1020,-239.4 1017.9,-245.4 1022.1,-245.4"/> +</g> +<!-- STRUCT_S2 --> +<g id="node24" class="node"> +<title>STRUCT_S2</title> +<polygon fill="#90ee90" stroke="black" points="1159.12,-237.47 1062.88,-237.47 1062.88,-196.47 1159.12,-196.47 1159.12,-237.47"/> +<text text-anchor="middle" x="1111" y="-221.12" font-family="DejaVu Sans" font-size="13.00">s2</text> +<text text-anchor="middle" x="1111" y="-204.62" font-family="DejaVu Sans" font-size="13.00">x: int, y: *s1</text> +</g> +<!-- STRUCT_TOP->STRUCT_S2 --> +<g id="edge38" class="edge"> +<title>STRUCT_TOP->STRUCT_S2</title> +<path fill="none" stroke="#555555" d="M1033.43,-376.51C1050.81,-340.99 1080.89,-279.5 1098.04,-244.46"/> +<polygon fill="#555555" stroke="#555555" points="1099.88,-245.49 1100.63,-239.17 1096.1,-243.64 1099.88,-245.49"/> +</g> +<!-- STRUCT_MORE --> +<g id="node25" class="node"> +<title>STRUCT_MORE</title> +<polygon fill="#90ee90" stroke="black" points="1229,-234.97 1175,-234.97 1175,-198.97 1229,-198.97 1229,-234.97"/> +<text text-anchor="middle" x="1202" y="-212.87" font-family="DejaVu Sans" font-size="13.00">…</text> +</g> +<!-- STRUCT_TOP->STRUCT_MORE --> +<g id="edge39" class="edge"> +<title>STRUCT_TOP->STRUCT_MORE</title> +<path fill="none" stroke="#555555" d="M1045.31,-378.1C1081.09,-341.53 1145.91,-275.3 1179.87,-240.58"/> +<polygon fill="#555555" stroke="#555555" points="1181.3,-242.13 1183.99,-236.38 1178.3,-239.19 1181.3,-242.13"/> +</g> +<!-- STRUCT_TOP->MEM_TOP --> +<!-- STRUCT_BOT --> +<g id="node21" class="node"> +<title>STRUCT_BOT</title> +<ellipse fill="#90ee90" stroke="black" cx="1020" cy="-111.99" rx="82.91" ry="28.99"/> +<text text-anchor="middle" x="1020" y="-116.14" font-family="DejaVu Sans" font-size="13.00">⊥:struct</text> +<text text-anchor="middle" x="1020" y="-99.64" font-family="DejaVu Sans" font-size="13.00">[struct bottom]</text> +</g> +<!-- STRUCT_BOT->BOT --> +<g id="edge69" class="edge"> +<title>STRUCT_BOT->BOT</title> +<path fill="none" stroke="#555555" d="M982.84,-85.85C959.17,-69.85 929.19,-49.57 908.49,-35.56"/> +<polygon fill="#555555" stroke="#555555" points="909.78,-33.9 903.63,-32.28 907.42,-37.38 909.78,-33.9"/> +</g> +<!-- STRUCT_PERSON->STRUCT_BOT --> +<g id="edge40" class="edge"> +<title>STRUCT_PERSON->STRUCT_BOT</title> +<path fill="none" stroke="#555555" d="M957.85,-196.12C968.43,-181.96 982.92,-162.59 995.24,-146.11"/> +<polygon fill="#555555" stroke="#555555" points="996.85,-147.46 998.76,-141.4 993.49,-144.94 996.85,-147.46"/> +</g> +<!-- STRUCT_PERSON->STRUCT_S1 --> +<!-- STRUCT_S1->STRUCT_BOT --> +<g id="edge41" class="edge"> +<title>STRUCT_S1->STRUCT_BOT</title> +<path fill="none" stroke="#555555" d="M1020,-196.12C1020,-182.64 1020,-164.43 1020,-148.49"/> +<polygon fill="#555555" stroke="#555555" points="1022.1,-148.87 1020,-142.87 1017.9,-148.87 1022.1,-148.87"/> +</g> +<!-- STRUCT_S1->STRUCT_S2 --> +<!-- STRUCT_S2->STRUCT_BOT --> +<g id="edge42" class="edge"> +<title>STRUCT_S2->STRUCT_BOT</title> +<path fill="none" stroke="#555555" d="M1093.45,-196.12C1080.83,-181.83 1063.53,-162.25 1048.88,-145.68"/> +<polygon fill="#555555" stroke="#555555" points="1050.58,-144.42 1045.03,-141.32 1047.43,-147.2 1050.58,-144.42"/> +</g> +<!-- STRUCT_S2->STRUCT_MORE --> +<!-- STRUCT_MORE->STRUCT_BOT --> +<g id="edge43" class="edge"> +<title>STRUCT_MORE->STRUCT_BOT</title> +<path fill="none" stroke="#555555" d="M1181.79,-198.68C1177.07,-194.96 1171.97,-191.19 1167,-187.98 1138.36,-169.5 1104.6,-152 1076.65,-138.56"/> +<polygon fill="#555555" stroke="#555555" points="1077.74,-136.75 1071.42,-136.06 1075.93,-140.54 1077.74,-136.75"/> +</g> +<!-- MEM_ALIAS2 --> +<g id="node28" class="node"> +<title>MEM_ALIAS2</title> +<ellipse fill="#00008b" stroke="black" cx="1298" cy="-216.97" rx="45.21" ry="18"/> +<text text-anchor="middle" x="1298" y="-212.87" font-family="DejaVu Sans" font-size="13.00" fill="white">MEM#2</text> +</g> +<!-- MEM_TOP->MEM_ALIAS2 --> +<g id="edge46" class="edge"> +<title>MEM_TOP->MEM_ALIAS2</title> +<path fill="none" stroke="#555555" d="M1346.11,-386.79C1336.64,-353.58 1315.8,-280.42 1304.92,-242.24"/> +<polygon fill="#555555" stroke="#555555" points="1306.96,-241.75 1303.29,-236.55 1302.92,-242.9 1306.96,-241.75"/> +</g> +<!-- MEM_ALIAS3 --> +<g id="node29" class="node"> +<title>MEM_ALIAS3</title> +<ellipse fill="#00008b" stroke="black" cx="1404" cy="-216.97" rx="45.21" ry="18"/> +<text text-anchor="middle" x="1404" y="-212.87" font-family="DejaVu Sans" font-size="13.00" fill="white">MEM#3</text> +</g> +<!-- MEM_TOP->MEM_ALIAS3 --> +<g id="edge47" class="edge"> +<title>MEM_TOP->MEM_ALIAS3</title> +<path fill="none" stroke="#555555" d="M1355.89,-386.79C1365.36,-353.58 1386.2,-280.42 1397.08,-242.24"/> +<polygon fill="#555555" stroke="#555555" points="1399.08,-242.9 1398.71,-236.55 1395.04,-241.75 1399.08,-242.9"/> +</g> +<!-- MEM_MORE --> +<g id="node30" class="node"> +<title>MEM_MORE</title> +<ellipse fill="#00008b" stroke="black" cx="1492" cy="-216.97" rx="27" ry="18"/> +<text text-anchor="middle" x="1492" y="-212.87" font-family="DejaVu Sans" font-size="13.00" fill="white">…</text> +</g> +<!-- MEM_TOP->MEM_MORE --> +<g id="edge48" class="edge"> +<title>MEM_TOP->MEM_MORE</title> +<path fill="none" stroke="#555555" d="M1363.51,-387.45C1389.1,-353.7 1447.4,-276.8 1475.82,-239.32"/> +<polygon fill="#555555" stroke="#555555" points="1477.25,-240.91 1479.2,-234.86 1473.9,-238.37 1477.25,-240.91"/> +</g> +<!-- MEM_TOP->TUPLE_TOP --> +<!-- MEM_BOT --> +<g id="node27" class="node"> +<title>MEM_BOT</title> +<ellipse fill="#00008b" stroke="black" cx="1351" cy="-111.99" rx="44.7" ry="18"/> +<text text-anchor="middle" x="1351" y="-107.89" font-family="DejaVu Sans" font-size="13.00" fill="white">⊥:mem</text> +</g> +<!-- MEM_BOT->BOT --> +<g id="edge70" class="edge"> +<title>MEM_BOT->BOT</title> +<path fill="none" stroke="#555555" d="M1319.14,-99.06C1297.39,-91.29 1267.77,-81.43 1241,-75 1124.06,-46.89 982.06,-29.48 918.11,-22.5"/> +<polygon fill="#555555" stroke="#555555" points="918.4,-20.42 912.21,-21.87 917.95,-24.6 918.4,-20.42"/> +</g> +<!-- MEM_ALIAS2->MEM_BOT --> +<g id="edge49" class="edge"> +<title>MEM_ALIAS2->MEM_BOT</title> +<path fill="none" stroke="#555555" d="M1306.75,-198.97C1315.5,-181.97 1329.02,-155.71 1338.81,-136.68"/> +<polygon fill="#555555" stroke="#555555" points="1340.66,-137.66 1341.54,-131.37 1336.93,-135.74 1340.66,-137.66"/> +</g> +<!-- MEM_ALIAS2->MEM_ALIAS3 --> +<!-- MEM_ALIAS3->MEM_BOT --> +<g id="edge50" class="edge"> +<title>MEM_ALIAS3->MEM_BOT</title> +<path fill="none" stroke="#555555" d="M1395.25,-198.97C1386.5,-181.97 1372.98,-155.71 1363.19,-136.68"/> +<polygon fill="#555555" stroke="#555555" points="1365.07,-135.74 1360.46,-131.37 1361.34,-137.66 1365.07,-135.74"/> +</g> +<!-- MEM_ALIAS3->MEM_MORE --> +<!-- MEM_MORE->MEM_BOT --> +<g id="edge51" class="edge"> +<title>MEM_MORE->MEM_BOT</title> +<path fill="none" stroke="#555555" d="M1475.42,-202.39C1469.65,-197.74 1463.09,-192.57 1457,-187.98 1430.93,-168.37 1400.47,-146.96 1378.9,-132.06"/> +<polygon fill="#555555" stroke="#555555" points="1380.17,-130.38 1374.03,-128.71 1377.78,-133.84 1380.17,-130.38"/> +</g> +<!-- TUPLE1 --> +<g id="node33" class="node"> +<title>TUPLE1</title> +<polygon fill="#ffc0cb" stroke="black" points="1598.88,-328.97 1543.12,-328.97 1543.12,-292.97 1598.88,-292.97 1598.88,-328.97"/> +<text text-anchor="middle" x="1571" y="-306.87" font-family="DejaVu Sans" font-size="13.00">(type)</text> +</g> +<!-- TUPLE_TOP->TUPLE1 --> +<g id="edge54" class="edge"> +<title>TUPLE_TOP->TUPLE1</title> +<path fill="none" stroke="#555555" d="M1584.8,-386.65C1582.19,-372.52 1578.47,-352.37 1575.54,-336.56"/> +<polygon fill="#555555" stroke="#555555" points="1577.61,-336.2 1574.46,-330.68 1573.48,-336.96 1577.61,-336.2"/> +</g> +<!-- TUPLE2 --> +<g id="node34" class="node"> +<title>TUPLE2</title> +<polygon fill="#ffc0cb" stroke="black" points="1709,-328.97 1615,-328.97 1615,-292.97 1709,-292.97 1709,-328.97"/> +<text text-anchor="middle" x="1662" y="-306.87" font-family="DejaVu Sans" font-size="13.00">(type, type)</text> +</g> +<!-- TUPLE_TOP->TUPLE2 --> +<g id="edge55" class="edge"> +<title>TUPLE_TOP->TUPLE2</title> +<path fill="none" stroke="#555555" d="M1601.23,-387.51C1613,-372.87 1630.35,-351.31 1643.5,-334.97"/> +<polygon fill="#555555" stroke="#555555" points="1645.06,-336.38 1647.18,-330.39 1641.79,-333.74 1645.06,-336.38"/> +</g> +<!-- TUPLEN --> +<g id="node35" class="node"> +<title>TUPLEN</title> +<polygon fill="#ffc0cb" stroke="black" points="1802.75,-328.97 1725.25,-328.97 1725.25,-292.97 1802.75,-292.97 1802.75,-328.97"/> +<text text-anchor="middle" x="1764" y="-306.87" font-family="DejaVu Sans" font-size="13.00">(type, …)</text> +</g> +<!-- TUPLE_TOP->TUPLEN --> +<g id="edge56" class="edge"> +<title>TUPLE_TOP->TUPLEN</title> +<path fill="none" stroke="#555555" d="M1614.35,-390.18C1643.39,-375.01 1690.42,-350.42 1724.01,-332.87"/> +<polygon fill="#555555" stroke="#555555" points="1724.85,-334.8 1729.2,-330.16 1722.91,-331.07 1724.85,-334.8"/> +</g> +<!-- TUPLE_BOT --> +<g id="node32" class="node"> +<title>TUPLE_BOT</title> +<ellipse fill="#ffc0cb" stroke="black" cx="1625" cy="-111.99" rx="44.7" ry="18"/> +<text text-anchor="middle" x="1625" y="-107.89" font-family="DejaVu Sans" font-size="13.00">⊥:tuple</text> +</g> +<!-- TUPLE_BOT->BOT --> +<g id="edge71" class="edge"> +<title>TUPLE_BOT->BOT</title> +<path fill="none" stroke="#555555" d="M1596.44,-97.66C1578.14,-89.72 1553.65,-80.21 1531,-75 1301.56,-22.24 1015.96,-18.29 918.46,-18.63"/> +<polygon fill="#555555" stroke="#555555" points="918.73,-16.53 912.74,-18.66 918.75,-20.73 918.73,-16.53"/> +</g> +<!-- TUPLE1->TUPLE_BOT --> +<g id="edge57" class="edge"> +<title>TUPLE1->TUPLE_BOT</title> +<path fill="none" stroke="#555555" d="M1575.79,-292.49C1585.46,-257.21 1607.33,-177.44 1618.37,-137.19"/> +<polygon fill="#555555" stroke="#555555" points="1620.33,-137.95 1619.9,-131.61 1616.28,-136.84 1620.33,-137.95"/> +</g> +<!-- TUPLE1->TUPLE2 --> +<!-- TUPLE2->TUPLE_BOT --> +<g id="edge58" class="edge"> +<title>TUPLE2->TUPLE_BOT</title> +<path fill="none" stroke="#555555" d="M1658.72,-292.49C1652.12,-257.37 1637.24,-178.17 1629.65,-137.74"/> +<polygon fill="#555555" stroke="#555555" points="1631.73,-137.43 1628.56,-131.92 1627.6,-138.2 1631.73,-137.43"/> +</g> +<!-- TUPLE2->TUPLEN --> +<!-- TUPLEN->TUPLE_BOT --> +<g id="edge59" class="edge"> +<title>TUPLEN->TUPLE_BOT</title> +<path fill="none" stroke="#555555" d="M1751.66,-292.49C1726.44,-256.74 1668.97,-175.3 1640.95,-135.59"/> +<polygon fill="#555555" stroke="#555555" points="1642.76,-134.52 1637.59,-130.83 1639.33,-136.94 1642.76,-134.52"/> +</g> +</g> +</svg> diff --git a/chapter10b/pom.xml b/chapter10b/pom.xml new file mode 100644 index 00000000..fb7c947f --- /dev/null +++ b/chapter10b/pom.xml @@ -0,0 +1,17 @@ +<?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>chapter10b</artifactId> + <packaging>jar</packaging> + <name>Chapter 10b</name> + <properties> + <simple.graph.directory>${project.basedir}/../graph</simple.graph.directory> + </properties> +</project> diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/IRPrinter.java b/chapter10b/src/main/java/com/seaofnodes/simple/IRPrinter.java new file mode 100644 index 00000000..1ac32b1a --- /dev/null +++ b/chapter10b/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.isDead() ) { + 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.isDead() ) { + 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/chapter10b/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter10b/src/main/java/com/seaofnodes/simple/IterPeeps.java new file mode 100644 index 00000000..7075d526 --- /dev/null +++ b/chapter10b/src/main/java/com/seaofnodes/simple/IterPeeps.java @@ -0,0 +1,208 @@ +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) { + 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 + var obs = Parser.PARSER == null ? null : Parser.PARSER._obs; + if( obs != null ) obs.before(n); + Node x = n.peepholeOpt(); + if( x != null ) { + if( x.isDead() ) { + if( obs != null ) obs.after(n, x, true); + 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( obs != null ) obs.after(n, x, true); + } + + 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/chapter10b/src/main/java/com/seaofnodes/simple/Parser.java index ddbbdf82..3b5f2721 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter10b/src/main/java/com/seaofnodes/simple/Parser.java @@ -33,6 +33,10 @@ public class Parser { public StopNode STOP; + // Field identities are independent of the memory graph and Start's tuple. + private final HashMap<String,Integer> _aliases = new HashMap<>(); + private int _alias = 2; + // The Lexer. Thin wrapper over a byte[] buffer with a cursor. private final Lexer _lexer; @@ -89,7 +93,7 @@ public class Parser { _lexer = new Lexer(source); _scope = new ScopeNode(); _continueScope = _breakScope = null; - START = new StartNode(new Type[]{ Type.CONTROL, arg }); + START = new StartNode(new Type[]{ Type.CONTROL, TypeMem.BOT, arg }); STOP = new StopNode(source); } @@ -114,7 +118,8 @@ public class Parser { // 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()); + _scope.define(ScopeNode.ARG0, TypeInteger.BOT, new ProjNode(START, 2, ScopeNode.ARG0).peephole()); + _scope.define("$mem", TypeMem.BOT, new ProjNode(START, 1, "$mem").peephole()); parseBlock(); _scope.pop(); _xScopes.pop(); @@ -199,8 +204,9 @@ public class Parser { require("}"); // Build and install the TypeStruct TypeStruct ts = TypeStruct.make(typeName, fields); + _aliases.put(typeName,_alias); + _alias += ts._fields.length; OBJS.put(typeName, ts); // Insert the struct name in the collection of all struct names - START.addMemProj(ts, _scope); // Insert memory edges return parseStatement(); } @@ -589,21 +595,24 @@ public class Parser { private Node newStruct( TypeStruct obj ) { Node n = new NewNode(TypeMemPtr.make(obj), ctrl()).peephole().keep(); Node initValue = new ConstantNode(TypeInteger.constant(0)).peephole(); - int alias = START._aliasStarts.get(obj._name); + int alias = _aliases.get(obj._name); for( Field field : obj._fields ) { - memAlias(alias, new StoreNode(field._fname, alias, memAlias(alias), n, initValue).peephole()); + store(field._fname,alias,n,initValue); alias++; } 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(int alias ) { return _scope.lookup(memName(alias) ); } - private Node memAlias(int alias, Node st) { return _scope.update(memName(alias), st); } - public static String memName(int alias) { return ("$"+alias).intern(); } + // Memory is one hidden SSA variable, including across branches and loops. + private Node mem() { return _scope.lookup("$mem"); } + private Node mem(Node n) { return _scope.update("$mem",n); } + + private void store(String name, int alias, Node ptr, Node val) { + Node prior = mem().keep(); + Node st = new StoreNode(name,alias,prior,ptr,val).peephole(); + mem(new MemMergeNode(prior,alias,st).peephole()); + prior.unkeep(); + } /** * Parse postfix expression. For now this is just a field @@ -622,20 +631,20 @@ public class Parser { String name = requireId().intern(); int idx = ptr._obj==null ? -1 : ptr._obj.find(name); if( idx == -1 ) throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'"); - int alias = START._aliasStarts.get(ptr._obj._name)+idx; + int alias = _aliases.get(ptr._obj._name)+idx; if( match("=") ) { // Disambiguate "obj.fld==x" boolean test from "obj.fld=x" field assignment if( peek('=') ) _lexer._position--; else { Node val = parseExpression(); - memAlias(alias, new StoreNode(name, alias, memAlias(alias), expr, val).peephole()); + store(name,alias,expr,val); return expr; // "obj.a = expr" returns the expression while updating memory } } Type declaredType = ptr._obj._fields[idx]._type; - return parsePostfix(new LoadNode(name, alias, declaredType, memAlias(alias), expr).peephole()); + return parsePostfix(new LoadNode(name, alias, declaredType, mem(), expr).peephole()); } /** diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/Utils.java b/chapter10b/src/main/java/com/seaofnodes/simple/Utils.java new file mode 100644 index 00000000..4f2af769 --- /dev/null +++ b/chapter10b/src/main/java/com/seaofnodes/simple/Utils.java @@ -0,0 +1,65 @@ +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); } + + public static boolean eq(String s0, String s1) { + if( s0==s1 ) return true; + assert s0==s0.intern(); + assert s1==s1.intern(); + return false; + } +} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/AddNode.java new file mode 100644 index 00000000..de5044d7 --- /dev/null +++ b/chapter10b/src/main/java/com/seaofnodes/simple/node/AddNode.java @@ -0,0 +1,176 @@ +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 + 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() { + Type t1 = in(1)._type, t2 = in(2)._type; + if( t1==Type.TOP || t1==TypeInteger.TOP || + t2==Type.TOP || t2==TypeInteger.TOP ) + return TypeInteger.TOP; + if( t1 instanceof TypeInteger i0 && + t2 instanceof TypeInteger i1 ) { + if (i0.isConstant() && i1.isConstant()) + return TypeInteger.constant(i0.value()+i1.value()); + } + return TypeInteger.BOT; + } + + @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/chapter10b/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/BoolNode.java new file mode 100644 index 00000000..bf0d3a84 --- /dev/null +++ b/chapter10b/src/main/java/com/seaofnodes/simple/node/BoolNode.java @@ -0,0 +1,76 @@ +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 + 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() { + Type t1 = in(1)._type, t2 = in(2)._type; + if( t1==Type.TOP || t1==TypeInteger.TOP || + t2==Type.TOP || t2==TypeInteger.TOP ) + return TypeInteger.TOP; + if( t1 instanceof TypeInteger i0 && + t2 instanceof TypeInteger i1 ) { + if (i0.isConstant() && i1.isConstant()) + return TypeInteger.constant(doOp(i0.value(), i1.value()) ? 1 : 0); + } + return TypeInteger.BOT; + } + + 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 ((phi cons) cmp con) ? + // Do we have ((phi cons) cmp (phi cons)) ? + // Push the compare up through the phi: (phi con0 cmp con, con1 cmp con...) + 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/chapter10b/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java new file mode 100644 index 00000000..8d512f21 --- /dev/null +++ b/chapter10b/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java @@ -0,0 +1,237 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.IterPeeps; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeMem; +import java.util.BitSet; + +/** + * A Phi for bulk memory. It covers all aliases except {@link #_aliases}; + * those aliases must be represented by parallel precise MemPhis at the same + * program slice. + */ +public class BulkMemPhiNode extends PhiNode { + + private static final BitSet EMPTY = new BitSet(); + + // Excluded Aliases; empty means NO exclusions. + // Each split clones the exclusions; published sets are never mutated. + public final BitSet _aliases; + + public BulkMemPhiNode(String label, Node... inputs) { + this(label,EMPTY,inputs); + } + + private BulkMemPhiNode(String label, BitSet aliases, Node... inputs) { + super(label,TypeMem.BOT,inputs); + _aliases = aliases; + } + + public boolean isSplit(int alias) { return _aliases.get(alias); } + + @Override public String label() { return "BulkPhi"+_aliases; } + + @Override + public Type compute() { + Type t = super.compute(); + return t instanceof TypeMem mem ? mem : + t==Type.BOTTOM ? TypeMem.BOT : + TypeMem.TOP; + } + + @Override + public Node idealize() { + Node progress = super.idealize(); + if( progress != null ) return progress; + if( ((RegionNode)region()).inProgress() || region().nIns()<=1 ) + return null; // Input is in-progress + + // "Peek through" a MemMerge that covers this alias set on its default + for( int i=1; i<nIns(); i++ ) + if( in(i) instanceof MemMergeNode mmm && canPeek(mmm) ) { + setDef(i,IterPeeps.add(mmm.in(1))); + return this; + } + + // If any input or output uses or defines a specific alias we cover, + // make a private MemPhi for it, and add it to the excluded list. + for( int i=1; i<nIns(); i++ ) { + int alias = inputAlias(in(i)); + if( alias!=0 ) + return slice(alias); + } + + for( int i=0; i<_outputs.size(); i++ ) { + int alias = outputAlias(_outputs.get(i)); + if( alias!=0 ) + return slice(alias); + } + + return null; + } + + static public boolean checkMem(Node n, int alias) { + for( Node m : n._inputs ) + // Bulk memory supplies this alias + if( m instanceof BulkMemPhiNode bulk ) { + if( bulk._aliases.get(alias) ) + return false; + } else if( m instanceof MemMergeNode mmm && + mmm.in(1) instanceof BulkMemPhiNode bulk && + bulk._aliases.get(alias) && + (alias >= mmm.nIns() || mmm.in(alias)==null) ) + return false; + return true; + } + + + // True if the MemMerge has no precise slice still covered by this Phi. + private boolean canPeek(MemMergeNode mmm) { + return missingAlias(mmm,false)==0; + } + + // Return an alias required by an input but still covered by this Phi, or 0. + private int inputAlias(Node n) { + return switch(n) { + case ProjNode proj -> 0; + case ConstantNode con -> 0; + case MemMergeNode mmm -> missingAlias(mmm,false); + case MemOpNode mem -> unsplit(mem._alias); + case BulkMemPhiNode bulk -> missingAlias(bulk); + default -> throw new AssertionError("Unexpected memory input "+n); + }; + } + + // Return an alias required by a user but still covered by this Phi, or 0. + private int outputAlias(Node use) { + // This decision depends backwards on an immediate user's alias/set. + // Normal graph-neighbor enqueueing flows from defs to uses, so retain + // an explicit forward dependency to revisit this Phi when the user + // sharpens. + use.addDepForwards(this); + return switch(use) { + case ScopeNode scope -> 0; + case MemMergeNode mmm -> missingAlias(mmm,true); + case BulkMemPhiNode bulk -> missingAlias(bulk); + case MemOpNode mem -> unsplit(mem._alias); + case MemPhiNode phi -> unsplit(phi._alias); + default -> { + assert use instanceof ReturnNode + : "Unexpected bulk-memory user "+use; + yield 0; + } + }; + } + + // Alias zero/one is bulk, and an already excluded precise alias cannot + // trigger another split of this Phi. + private int unsplit(int alias) { + if( alias <= 1 ) return 0; + assert !_aliases.get(alias); + return alias; + } + + // First alias excluded by the neighboring bulk Phi but not by this one. + private int missingAlias(BulkMemPhiNode bulk) { + for( int alias = bulk._aliases.nextSetBit(0); alias >= 0; alias = bulk._aliases.nextSetBit(alias+1) ) + if( !_aliases.get(alias) ) + return alias; + return 0; + } + + // First precise MemMerge slice still covered by this Phi. When inspecting + // a user, ignore slots which point back to this Phi. + private int missingAlias(MemMergeNode mmm, boolean user) { + for( int alias=2; alias<mmm.nIns(); alias++ ) + if( mmm.in(alias)!=null && + mmm.alias(alias)!=mmm.in(1) && + !_aliases.get(alias) ) + { assert (!user || mmm.in(alias)!=this); return alias; } + return 0; + } + + // Slice out given alias + private MemMergeNode slice( int alias ) { + assert !_aliases.get(alias); + // Inputs are the same; MemPhi will sharpen his own inputs. If a + // parallel precise Phi already exists, reuse it instead of building a + // duplicate for the same Region/alias pair. + Node mem = precisePhi(alias); + + BitSet aliases = ((BitSet)_aliases.clone()); + aliases.set(alias); + BulkMemPhiNode bphi = new BulkMemPhiNode(_label,aliases); + for( int i=0; i<nIns(); i++ ) + bphi.addDef(in(i)); + // Installing bphi creates a new bulk user with a different exclusion + // set. Queue the original memory inputs before bphi itself can peep + // away and hide those neighbor relationships. + for( int i=1; i<bphi.nIns(); i++ ) + IterPeeps.add(bphi.in(i)); + // Do not peephole bphi here, as BulkMemPhi can recursively start a + // second bulk rewrite before this one has finished. Let the worklist + // discover any further splits + bphi.setType(TypeMem.BOT); + Node bulk = bphi; + IterPeeps.add(bulk); + IterPeeps.add(mem); + return aggregate(bulk,alias,mem); + } + + // Build aggregate memory over a bulk Phi, preserving every precise slice + // which the bulk has already excluded. The named alias is newly supplied + // (or replaces its old slice). + MemMergeNode aggregate(Node bulk, int alias, Node precise) { + MemMergeNode mmm = new MemMergeNode(bulk); + for( int old = _aliases.nextSetBit(0); old >= 0; old = _aliases.nextSetBit(old+1) ) + { assert old != alias; mmm.alias(old,precisePhi(old));} + mmm.alias(alias,precise); + assert checkMerge(mmm); + return mmm; + } + + // Find or make the precise Phi parallel to this bulk Phi at the same merge. + private Node precisePhi(int alias) { + MemPhiNode mphi = _findPhi(alias); + if( mphi!=null ) return mphi; + + mphi = new MemPhiNode("$"+alias,alias); + mphi.addDef(region()); + // Due to cycles, must set before calling peephole + mphi.setType(TypeMem.make(alias)); + for( int i=1; i<nIns(); i++ ) + mphi.addDef(IterPeeps.add(preciseInput(in(i),alias))); + return IterPeeps.add(mphi); + } + + MemPhiNode _findPhi(int alias) { + for( Node use : region()._outputs ) + if( use instanceof MemPhiNode mphi && mphi._alias==alias ) + return mphi; + return null; + } + + // Select an alias already split out of a predecessor's bulk memory. + private Node preciseInput(Node mem, int alias) { + if( mem instanceof MemMergeNode mmm ) + return mmm.alias(alias); + if( mem instanceof BulkMemPhiNode bulk && bulk.isSplit(alias) ) + return bulk.precisePhi(alias); + return mem; + } + + // Every alias excluded by a bulk default must be supplied explicitly. + static boolean checkMerge(MemMergeNode mmm) { + if( !(mmm.in(1) instanceof BulkMemPhiNode bulk) ) return true; + for( int alias = bulk._aliases.nextSetBit(0); alias >= 0; alias = bulk._aliases.nextSetBit(alias+1) ) + if( alias >= mmm.nIns() || mmm.in(alias)==null ) + return false; + return true; + } + + @Override public boolean eq(Node n) { + return _aliases.equals(((BulkMemPhiNode)n)._aliases) && super.eq(n); + } + + @Override int hash() { return _aliases.hashCode(); } +} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/node/CastNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/CastNode.java new file mode 100644 index 00000000..fec540f7 --- /dev/null +++ b/chapter10b/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/chapter10b/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/ConstantNode.java new file mode 100644 index 00000000..d760c0df --- /dev/null +++ b/chapter10b/src/main/java/com/seaofnodes/simple/node/ConstantNode.java @@ -0,0 +1,54 @@ +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 { + final 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(); } +} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/DivNode.java new file mode 100644 index 00000000..6cee90ad --- /dev/null +++ b/chapter10b/src/main/java/com/seaofnodes/simple/node/DivNode.java @@ -0,0 +1,45 @@ +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 + 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() { + Type t1 = in(1)._type, t2 = in(2)._type; + if( t1==Type.TOP || t1==TypeInteger.TOP || + t2==Type.TOP || t2==TypeInteger.TOP ) + return TypeInteger.TOP; + if (t1 instanceof TypeInteger i0 && + t2 instanceof TypeInteger i1) { + if (i0.isConstant() && i1.isConstant()) + return i1.value() == 0 + ? TypeInteger.ZERO + : TypeInteger.constant(i0.value()/i1.value()); + } + return TypeInteger.BOT; + } + + @Override + public Node idealize() { + // Div of 1. + if( in(2)._type.isConstant() && in(2)._type instanceof TypeInteger i && i.value()==1 ) + return in(1); + return null; + } + + @Override Node copy(Node lhs, Node rhs) { return new DivNode(lhs,rhs); } +} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/node/IfNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/IfNode.java new file mode 100644 index 00000000..1cf4ddaf --- /dev/null +++ b/chapter10b/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/chapter10b/src/main/java/com/seaofnodes/simple/node/LoadNode.java index ece27d86..55822722 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter10b/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -1,6 +1,7 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.Field; @@ -38,10 +39,15 @@ public class LoadNode extends MemOpNode { @Override public Node idealize() { + if( mem() instanceof MemMergeNode merge ) { + setDef(1,IterPeeps.add(merge.alias(_alias))); + return this; + } + // Simple Load-after-Store on same address. if( mem() instanceof StoreNode st && - ptr() == st.ptr() ) { // Must check same object + ptr() == st.ptr() && _alias==st._alias ) { // Must check same object assert Utils.eq(_name,st._name); // Equiv class aliasing is perfect return st.val(); } @@ -53,7 +59,7 @@ public class LoadNode extends MemOpNode { // if( pred ) ptr.x = e0; val = pred ? e0 // else ptr.x = e1; : e1; // val = ptr.x; ptr.x = val; - if( mem() instanceof PhiNode memphi && memphi.region()._type == Type.CONTROL && memphi.nIns()== 3 ) { + if( mem() instanceof MemPhiNode memphi && memphi.region()._type == Type.CONTROL && memphi.nIns()== 3 ) { // Profit on RHS/Loop backedge if( profit(memphi,2) || // Else must not be a loop to count profit on LHS. @@ -70,6 +76,6 @@ public class LoadNode extends MemOpNode { // Profitable if we find a matching Store on this Phi arm. private boolean profit(PhiNode phi, int idx) { Node px = phi.in(idx); - return px!=null && px.addDep(this) instanceof StoreNode st1 && ptr()==st1.ptr(); + return px!=null && px.addDep(this) instanceof StoreNode st1 && ptr()==st1.ptr() && _alias==st1._alias; } } diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/node/LoopNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/LoopNode.java new file mode 100644 index 00000000..a0ba973e --- /dev/null +++ b/chapter10b/src/main/java/com/seaofnodes/simple/node/LoopNode.java @@ -0,0 +1,31 @@ +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(); + } + + // Bypass Region idom, same as the default idom() using use entry in(1) instead of in(0) + @Override int idepth() { return _idepth!=0 ? _idepth : cacheIDepth(entry().idepth()+1); } + @Override Node idom() { return entry(); } +} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java new file mode 100644 index 00000000..d7fed402 --- /dev/null +++ b/chapter10b/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java @@ -0,0 +1,60 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.IterPeeps; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeMem; +import java.util.BitSet; + +/** All memory: a default slice and explicit overrides for individual aliases. */ +public class MemMergeNode extends Node { + public MemMergeNode(Node bulk) { super(null,bulk); _type = TypeMem.BOT; } + public MemMergeNode(Node bulk, int alias, Node precise) { + this(bulk); + alias(alias,precise); + } + + public Node alias(int alias) { + Node n = alias < nIns() ? in(alias) : null; + assert n!=null || !(in(1) instanceof BulkMemPhiNode bulk) || !bulk.isSplit(alias); + return n==null ? in(1) : n; + } + + public void alias(int alias, Node mem) { + assert alias > 1; + while( nIns()<=alias ) addDef(null); + setDef(alias,mem); + } + + @Override public String label() { return "MemMerge"; } + @Override public boolean isMem() { return true; } + @Override public Type compute() { return TypeMem.BOT; } + @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { + sb.append("MEM["); + for( int i=1; i<nIns(); i++ ) + if( in(i)!=null ) { + sb.append(i==1 ? "default:" : i+":"); + in(i)._print0(sb,visited).append(" "); + } + return sb.append("]"); + } + + @Override public Node idealize() { + boolean progress=false, allDefault=true; + for( int i=2; i<nIns(); i++ ) { + if( in(i)==in(1) ) { setDef(i,null); progress=true; } + if( in(i)!=null ) allDefault=false; + } + if( allDefault ) return in(1); + + if( in(1) instanceof MemMergeNode mem ) { + for( int i=2; i<mem.nIns(); i++ ) + if( mem.in(i)!=null && (i>=nIns() || in(i)==null) ) + alias(i,mem.in(i)); + IterPeeps.add(mem.in(1)); + setDef(1,mem.in(1)); + return this; + } + assert BulkMemPhiNode.checkMerge(this); + return progress ? this : null; + } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/MemOpNode.java index 08e08bbf..08e08bbf 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/MemOpNode.java +++ b/chapter10b/src/main/java/com/seaofnodes/simple/node/MemOpNode.java diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java new file mode 100644 index 00000000..c806bede --- /dev/null +++ b/chapter10b/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java @@ -0,0 +1,36 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.IterPeeps; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeMem; + +/** A Phi for one field alias, parallel to a bulk Phi at the same Region. */ +public class MemPhiNode extends PhiNode { + public final int _alias; + + public MemPhiNode(String label, int alias, Node... inputs) { + super(label,TypeMem.make(alias),inputs); + assert alias > 1; + _alias = alias; + } + + @Override public String label() { return "MemPhi_"+_alias; } + + @Override public Type compute() { + assert BulkMemPhiNode.checkMem(this,_alias); + Type t = super.compute(); + return t==Type.TOP || t==TypeMem.TOP ? TypeMem.TOP : TypeMem.make(_alias); + } + + @Override public Node idealize() { + for( int i=1; i<nIns(); i++ ) + if( in(i) instanceof MemMergeNode mem ) { + setDef(i,IterPeeps.add(mem.alias(_alias))); + return this; + } + return super.idealize(); + } + + @Override boolean eq(Node n) { return _alias==((MemPhiNode)n)._alias && super.eq(n); } + @Override int hash() { return _alias; } +} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/MinusNode.java new file mode 100644 index 00000000..8b1b73bf --- /dev/null +++ b/chapter10b/src/main/java/com/seaofnodes/simple/node/MinusNode.java @@ -0,0 +1,34 @@ +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 + 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 in(1)._type==Type.TOP ? TypeInteger.TOP : TypeInteger.BOT; + } + + @Override + public Node idealize() { + // -(-x) is x + if( in(1) instanceof MinusNode minus ) + return minus.in(1); + + return null; + } +} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/MulNode.java new file mode 100644 index 00000000..97721ce3 --- /dev/null +++ b/chapter10b/src/main/java/com/seaofnodes/simple/node/MulNode.java @@ -0,0 +1,59 @@ +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 + 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() { + Type t1 = in(1)._type, t2 = in(2)._type; + if( t1==Type.TOP || t1==TypeInteger.TOP || + t2==Type.TOP || t2==TypeInteger.TOP ) + return TypeInteger.TOP; + if (t1 instanceof TypeInteger i0 && + t2 instanceof TypeInteger i1) { + if (i0.isConstant() && i1.isConstant()) + return TypeInteger.constant(i0.value()*i1.value()); + } + return TypeInteger.BOT; + } + + @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/chapter10b/src/main/java/com/seaofnodes/simple/node/MultiNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/MultiNode.java new file mode 100644 index 00000000..2743bd19 --- /dev/null +++ b/chapter10b/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/chapter10b/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/NewNode.java new file mode 100644 index 00000000..b13462f5 --- /dev/null +++ b/chapter10b/src/main/java/com/seaofnodes/simple/node/NewNode.java @@ -0,0 +1,37 @@ +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 + 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/chapter10b/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/Node.java new file mode 100644 index 00000000..f4972f96 --- /dev/null +++ b/chapter10b/src/main/java/com/seaofnodes/simple/node/Node.java @@ -0,0 +1,641 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.Parser; + +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; + + + /** + * 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 debugging, e.g. "Add12" or "Region30" or "EQ99" + public String uniqueName() { + return label() + _nid; + } + + // ------------------------------------------------------------------------ + + // 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); + moveDepsToWorklist(); + // 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> void addUse(N n) { _outputs.add(n); } + + // 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() { addUse(null); return (N)this; } + // Remove bogus null. + public <N extends Node> N unkeep() { delUse(null); return (N)this; } + // Test "keep" status + public boolean iskeep() { return Utils.find(_outputs,null) != -1; } + + + // 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 + + /** + * Try to peephole at this node and return a better replacement Node. + * Always returns some not-null Node (often this). + */ + public final Node peephole() { + var obs = IterPeeps.midAssert() ? null : Parser.PARSER == null ? null : Parser.PARSER._obs; + if( obs != null ) obs.before(this); + Node n = peepholeOpt(); + Node rez = n == null ? this : deadCodeElim(n.peephole()); + if( obs != null ) obs.after(this, n == null ? null : rez, false); + return rez; + } + + /** + * 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() && !isDead() ) { + // 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; + public int nDeps() { return _deps == null ? 0 : _deps.size(); } + public Node dep(int idx) { return _deps.get(idx); } + + /** + * Add a node to the list of 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 ) { + return addDep(dep,false); + } + + // A bulk Phi inspects its users' partitions. Def-to-use propagation does + // not revisit that producer when an immediate user's partition changes. + Node addDepForwards(Node dep) { return addDep(dep,true); } + + private Node addDep(Node dep, boolean forwards) { + // Running peepholes during the big assert cannot have side effects + // like adding dependencies. + if( IterPeeps.midAssert() ) return this; + var obs = IterPeeps.midAssert() ? null : Parser.PARSER == null ? null : Parser.PARSER._obs; + if( obs != null ) obs.dep(this, dep); + if( _deps==null ) _deps = new ArrayList<>(); + if( Utils.find(_deps ,dep) != -1 ) return this; // Already on list + if( !forwards && Utils.find(_inputs,dep) != -1 ) return this; + if( !forwards && 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; + } + + + /** + * Immediate dominator tree depth, used to approximate a real IDOM depth + * 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>} + */ + public char _idepth; // IDOM depth approx; Zero is unset; non-zero is cached legit + + // Find the lowest common ancestor in the current dominator tree. + Node domLCA(Node rhs) { + if( rhs==null ) return this; + Node lhs = this; + while( lhs != rhs ) { + if( lhs==null || rhs==null ) return null; + int comp = lhs.idepth() - rhs.idepth(); + if( comp >= 0 ) lhs = lhs.idom(); + if( comp <= 0 ) rhs = rhs.idom(); + } + return lhs; + } + + int idepth() { return _idepth!=0 ? _idepth : cacheIDepth(idom().idepth()+1); } + // Zero depth means uncached. Check before narrowing so overflow cannot wrap. + final int cacheIDepth(int depth) { + assert 0 <= depth && depth <= Character.MAX_VALUE : "Dominator depth exceeds 65535"; + return _idepth = (char)depth; + } + + + // Return the immediate dominator of this Node. + Node idom() { return in(0); } + + // 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; + 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/chapter10b/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/NotNode.java new file mode 100644 index 00000000..83141836 --- /dev/null +++ b/chapter10b/src/main/java/com/seaofnodes/simple/node/NotNode.java @@ -0,0 +1,42 @@ +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 + 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 ? TypeInteger.TOP : TypeInteger.BOT; + } + } + + @Override + public Node idealize() { return null; } +} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/PhiNode.java new file mode 100644 index 00000000..650edd6b --- /dev/null +++ b/chapter10b/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -0,0 +1,161 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeMem; +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; } + + public static PhiNode make(String label, Type type, Node... inputs) { + return type instanceof TypeMem ? new BulkMemPhiNode(label,inputs) : new PhiNode(label,type,inputs); + } + + @Override public String label() { 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 boolean isMem() { return _declaredType instanceof TypeMem; } + + @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( !isMem() && 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().addDep(this)==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/chapter10b/src/main/java/com/seaofnodes/simple/node/ProjNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/ProjNode.java new file mode 100644 index 00000000..735fcdf3 --- /dev/null +++ b/chapter10b/src/main/java/com/seaofnodes/simple/node/ProjNode.java @@ -0,0 +1,59 @@ +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() instanceof IfNode iff ) { + if ( iff._type instanceof TypeTuple tt && tt._types[1 - _idx] == Type.XCONTROL ) + return iff.in(0); // We become our input control + + // Flip a negating if-test, to remove the not + if( 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/chapter10b/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/RegionNode.java new file mode 100644 index 00000000..f7a56503 --- /dev/null +++ b/chapter10b/src/main/java/com/seaofnodes/simple/node/RegionNode.java @@ -0,0 +1,118 @@ +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; + // Delete dead paths into a Region + 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); + } + } + return isDead() ? new ConstantNode(Type.XCONTROL) : delDef(path); + } + // If down to a single input, become that input + if( nIns()==2 && !hasPhi() ) + return in(1); // Collapse if no Phis; 1-input Phis will collapse on their own + + // If a CFG diamond with no merging, delete: "if( pred ) {} else {};" + if( !hasPhi() && // No Phi users, just a control user + in(1) instanceof ProjNode p1 && + in(2) instanceof ProjNode p2 && + p1.in(0)==p2.in(0) && + p1.in(0) instanceof IfNode iff ) + return iff.ctrl(); + + 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. + @Override int idepth() { + if( _idepth!=0 ) return _idepth; + int depth=0; + for( Node n : _inputs ) + if( n!=null ) + depth = Math.max(depth,n.idepth()+1); + return cacheIDepth(depth); + } + + @Override Node idom() { + Node lca = null; + // Recompute from predecessors: CFG edits can change the dominator. + for( int i=1; i<nIns(); i++ ) + lca = in(i).domLCA(lca); + return lca; + } + + // 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/chapter10b/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/ReturnNode.java new file mode 100644 index 00000000..f13f3708 --- /dev/null +++ b/chapter10b/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -0,0 +1,40 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.*; + +import java.util.BitSet; + +/** Keeps the return value and all preceding memory effects alive. */ +public class ReturnNode extends Node { + + public ReturnNode(Node ctrl, Node data, ScopeNode scope) { + super(ctrl, data, scope.lookup("$mem")); + } + + 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/chapter10b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java new file mode 100644 index 00000000..b931e862 --- /dev/null +++ b/chapter10b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -0,0 +1,304 @@ +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,PhiNode.make(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, PhiNode.make(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 ) { + if( !phi.iskeep() ) // Keeping phi around for parser elsewhere + 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; + // A single negation is handled below. For !!p (including p != null), + // also refine the underlying p without changing the Boolean result. + if( pred instanceof NotNode not && not.in(1) instanceof NotNode ) { + pred.keep(); // Recursive peepholes may rediscover this Not. + upcast(ctrl,not.in(1),!invert); + pred.unkeep(); + } + // Invert the If conditional + if( invert ) + pred = pred instanceof NotNode not ? not.in(1) : IterPeeps.add(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/chapter10b/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/StartNode.java new file mode 100644 index 00000000..1ca52af0 --- /dev/null +++ b/chapter10b/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -0,0 +1,35 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.*; +import java.util.BitSet; + +/** The entry tuple: control, initial whole-memory state, and argument. */ +public class StartNode extends MultiNode { + final TypeTuple _args; + + public StartNode(Type[] args) { + super(); + _type = _args = TypeTuple.make(args); + } + + @Override + public String label() { return "Start"; } + + @Override + StringBuilder _print1(StringBuilder sb, BitSet visited) { + return sb.append(label()); + } + + @Override public boolean isCFG() { return true; } + @Override public boolean isMultiHead() { return true; } + + @Override + public TypeTuple compute() { return _args; } + + @Override + public Node idealize() { return null; } + + // No immediate dominator, and idepth==0 + @Override int idepth() { return 0; } + @Override Node idom() { return null; } +} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/node/StopNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/StopNode.java new file mode 100644 index 00000000..cb4126f5 --- /dev/null +++ b/chapter10b/src/main/java/com/seaofnodes/simple/node/StopNode.java @@ -0,0 +1,74 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.Parser; + +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; + } + + @Override public Node idom() { return null; } + + public Node addReturn(Node node) { + return addDef(node); + } + + public StopNode iterate() { return IterPeeps.iterate(this).typeCheck(); } + public StopNode typeCheck() { + var obs = Parser.PARSER == null ? null : Parser.PARSER._obs; + String err = walk(n -> { + String msg = n.err(); + if( msg != null && obs != null ) obs.error(n, msg); + return msg; + }); + if( err != null ) throw new RuntimeException(err); + if( obs != null ) obs.phase("TypeCheck"); + return this; + } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/StoreNode.java index cf586d6e..b752aad5 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter10b/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -1,6 +1,7 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.type.*; import java.util.BitSet; @@ -37,11 +38,16 @@ public class StoreNode extends MemOpNode { @Override public Node idealize() { + if( mem() instanceof MemMergeNode merge ) { + setDef(1,IterPeeps.add(merge.alias(_alias))); + return this; + } + // Simple store-after-store on same address. Should pick up the // required init-store being stomped by a first user store. if( mem() instanceof StoreNode st && - ptr()==st.ptr() && // Must check same object + ptr()==st.ptr() && _alias==st._alias && // Must check same object ptr()._type instanceof TypeMemPtr && // No bother if weird dead pointers // Must have exactly one use of "this" or you get weird // non-serializable memory effects in the worse case. diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter10b/src/main/java/com/seaofnodes/simple/node/SubNode.java new file mode 100644 index 00000000..eeec5e1d --- /dev/null +++ b/chapter10b/src/main/java/com/seaofnodes/simple/node/SubNode.java @@ -0,0 +1,63 @@ +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 + 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() { + Type t1 = in(1)._type, t2 = in(2)._type; + if( t1==Type.TOP || t1==TypeInteger.TOP || + t2==Type.TOP || t2==TypeInteger.TOP ) + return TypeInteger.TOP; + // Sub of same is 0 + if( in(1)==in(2) ) + return TypeInteger.ZERO; + + if (t1 instanceof TypeInteger i0 && + t2 instanceof TypeInteger i1) { + if (i0.isConstant() && i1.isConstant()) + return TypeInteger.constant(i0.value()-i1.value()); + } + return TypeInteger.BOT; + } + + @Override + public Node idealize() { + Node lhs = in(1); + Node rhs = in(2); + Type t1 = lhs._type; + Type t2 = rhs._type; + + // Keep invalid operands for type checking. + if( t1 instanceof TypeInteger x && t2 instanceof TypeInteger y ) { + if( y.isConstant() && y.value()==0 ) return lhs; + if( x.isConstant() && x.value()==0 ) return new MinusNode(rhs); + } + + // x - (-y) is x+y + if( rhs instanceof MinusNode minus ) + return new AddNode(lhs,minus.in(1)); + + // (-x) - y is -(x+y) + if( lhs instanceof MinusNode minus ) + return new MinusNode(new AddNode(minus.in(1),rhs).peephole()); + + return null; + } + + @Override Node copy(Node lhs, Node rhs) { return new SubNode(lhs,rhs); } +} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter10b/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java new file mode 100644 index 00000000..5e21c852 --- /dev/null +++ b/chapter10b/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -0,0 +1,62 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphAdapter; +import com.seaofnodes.graph.GraphSnapshot; +import com.seaofnodes.graph.GraphSnapshot.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.type.TypeMem; +import java.util.ArrayList; + +/** Chapter 10's view of the IR; browser, transport and layout are shared. */ +public class SimpleGraphAdapter extends GraphAdapter<Node> { + @Override protected boolean dead(Node n) { return n.isDead(); } + @Override protected int id(Node n) { return n._nid; } + @Override protected int nIns(Node n) { return n.nIns(); } + @Override protected Node in(Node n, int idx) { return n.in(idx); } + @Override protected int nOuts(Node n) { return n.nOuts(); } + @Override protected Node out(Node n, int idx) { return n.out(idx); } + @Override protected int nDeps(Node n) { return n.nDeps(); } + @Override protected Node dep(Node n, int idx) { return n.dep(idx); } + + private ArrayList<Edge> edges(Node n) { + var edges = new ArrayList<Edge>(); + String[] names = n instanceof ScopeNode scope && n.nIns() != 0 ? scope.reverseNames() : null; + for( int i = 0; i < n.nIns(); i++ ) { + String name = names == null || i >= names.length ? null : names[i]; + edges.add(new Edge(i, ref(n.in(i)), role(n, i), name)); + } + return edges; + } + + @Override protected GraphSnapshot.Node desc(Node n) { + Projection proj = n instanceof ProjNode p ? new Projection(ref(n.in(0)), p._idx) : null; + String label = n.label(); + return new GraphSnapshot.Node(n._nid, label == null ? n.getClass().getSimpleName() : label, + n._type == null ? null : n._type.toString(), kind(n), edges(n), proj); + } + + private Kind kind(Node n) { + if( n instanceof StopNode ) return Kind.STOP; + if( n instanceof ScopeNode ) return Kind.SCOPE; + if( n instanceof StartNode ) return Kind.CTRL; + if( n instanceof LoopNode ) return Kind.LOOP; + if( n instanceof RegionNode ) return Kind.REGION; + if( n instanceof PhiNode ) return Kind.PHI; + return n.isCFG() ? Kind.CTRL : isMem(n) ? Kind.MEM : Kind.DATA; + } + + private Role role(Node n, int i) { + if( n instanceof ScopeNode || n instanceof ConstantNode ) return Role.ASSOC; + if( n instanceof PhiNode ) return i == 0 ? Role.ASSOC : isMem(n) ? Role.MEM : Role.DATA; + if( n instanceof ProjNode ) return n.isCFG() ? Role.CTRL : isMem(n) ? Role.MEM : Role.DATA; + if( n instanceof RegionNode && i == 0 ) return Role.ASSOC; + if( i == 0 || n instanceof RegionNode || n instanceof StopNode ) return Role.CTRL; + if( i == 1 && n instanceof MemOpNode || i == 2 && n instanceof ReturnNode ) return Role.MEM; + Node def = n.in(i); + if( def != null && isMem(def) ) return Role.MEM; + return Role.DATA; + } + + private boolean isMem(Node n) { return n.isMem() || n._type instanceof TypeMem; } +} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter10b/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java new file mode 100644 index 00000000..1980659e --- /dev/null +++ b/chapter10b/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java @@ -0,0 +1,45 @@ +package com.seaofnodes.simple.print; + +import com.seaofnodes.graph.GraphCapture; +import com.seaofnodes.graph.GraphViewer; +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.node.Node; +import java.util.ArrayList; +import java.io.IOException; + +/** Chapter 10's compilation context; capture and event bookkeeping are shared. */ +public class SimpleGraphObserver extends GraphCapture<Node> { + private Parser _parser; + private Node _ret; + private String _phase; + public SimpleGraphObserver() { super(new SimpleGraphAdapter()); } + public static void main(String[] args) throws IOException { + GraphViewer.run(new SimpleGraphObserver()); + } + @Override protected void compile(String src) { + _parser = new Parser(src); + _parser._obs = this; + _phase = "Parse"; + _ret = _parser.parse(); + phase(_phase); + _phase = "Iter"; + com.seaofnodes.simple.IterPeeps.iterate(_parser.STOP); + phase(_phase); + _phase = "TypeCheck"; + _parser.STOP.typeCheck(); + } + @Override protected String phase() { return _phase; } + @Override protected int pos() { return "Parse".equals(_phase) ? _parser.pos() : -1; } + @Override protected void detach() { + if( _parser != null ) _parser._obs = null; + _parser = null; + _ret = null; + } + @Override protected Node scope() { return _parser._scope; } + @Override protected void roots(ArrayList<Node> roots) { + roots.add(Parser.START); + roots.add(_parser.STOP); + roots.addAll(_parser._xScopes); + roots.add(_ret); + } +} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/type/Field.java b/chapter10b/src/main/java/com/seaofnodes/simple/type/Field.java new file mode 100644 index 00000000..cdc58215 --- /dev/null +++ b/chapter10b/src/main/java/com/seaofnodes/simple/type/Field.java @@ -0,0 +1,59 @@ +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 { + + // The pair {fieldName,type} uniquely identifies a field. + + // Field name + public final String _fname; + // Type of the field + public final Type _type; + + private Field(String fname, Type type ) { + super(TFLD); + _fname = fname; + _type = type; + } + public static Field make( String fname, Type type ) { + return new Field(fname,type).intern(); + } + + public static final Field TEST = make("test",TypeInteger.ZERO); + public static void gather(ArrayList<Type> ts) { ts.add(TEST); } + + @Override Field xmeet( Type that ) { + Field fld = (Field)that; // Invariant + assert _fname.equals(fld._fname); + return make(_fname,_type.meet(fld._type)); + } + + @Override + public Field dual() { return make(_fname,_type.dual()); } + + @Override + public Field glb() { + return make(_fname,_type.glb()); + } + + // Override in subclasses + int hash() { return _fname.hashCode() ^ _type.hashCode(); } + + boolean eq(Type t) { + Field f = (Field)t; + return Utils.eq(_fname,f._fname) && _type==f._type; + } + + + @Override + public StringBuilder print( StringBuilder sb ) { + return _type.print(sb.append(_fname).append(":")); + } + + @Override public String str() { return _fname; } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/type/Type.java b/chapter10b/src/main/java/com/seaofnodes/simple/type/Type.java index 93e27c76..93e27c76 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/type/Type.java +++ b/chapter10b/src/main/java/com/seaofnodes/simple/type/Type.java diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/chapter10b/src/main/java/com/seaofnodes/simple/type/TypeInteger.java new file mode 100644 index 00000000..8c77672b --- /dev/null +++ b/chapter10b/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/chapter10b/src/main/java/com/seaofnodes/simple/type/TypeMem.java index b6fc8824..b6fc8824 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/type/TypeMem.java +++ b/chapter10b/src/main/java/com/seaofnodes/simple/type/TypeMem.java diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java b/chapter10b/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java new file mode 100644 index 00000000..6c874a93 --- /dev/null +++ b/chapter10b/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java @@ -0,0 +1,82 @@ +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 final TypeMemPtr BOT = make(TypeStruct.BOT,true); + public static final TypeMemPtr TOP = BOT.dual(); + public static final TypeMemPtr NULLPTR = make(TypeStruct.TOP,true); + public static final TypeMemPtr VOIDPTR = NULLPTR.dual(); // A bottom mix of not-null ptrs, like C's void* but not null + public static final 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==BOT ) return sb.append("PtrBot"); + if( this==TOP ) return sb.append("PtrTop"); + 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==BOT ) return "PtrBot"; + if( this==TOP ) return "PtrTop"; + if( this== NULLPTR) return "null"; + if( this== VOIDPTR) return "*void"; + return "*"+_obj.str()+(_nil ? "?" : ""); + } + +} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/type/TypeStruct.java b/chapter10b/src/main/java/com/seaofnodes/simple/type/TypeStruct.java new file mode 100644 index 00000000..44741def --- /dev/null +++ b/chapter10b/src/main/java/com/seaofnodes/simple/type/TypeStruct.java @@ -0,0 +1,127 @@ +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; + } + + 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 int find(String fname) { + for( int i=0; i<_fields.length; i++ ) + if( _fields[i]._fname.equals(fname) ) + return i; + return -1; + } + + @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) { + if( this==BOT ) return sb.append("StructBot"); + if( this==TOP ) return sb.append("StructTop"); + 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() { + if( this==BOT ) return "StructBot"; + if( this==TOP ) return "StructTop"; + return _name; + } +} diff --git a/chapter10b/src/main/java/com/seaofnodes/simple/type/TypeTuple.java b/chapter10b/src/main/java/com/seaofnodes/simple/type/TypeTuple.java new file mode 100644 index 00000000..0aa019a7 --- /dev/null +++ b/chapter10b/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/chapter10b/src/test/java/com/seaofnodes/simple/Chapter01Test.java b/chapter10b/src/test/java/com/seaofnodes/simple/Chapter01Test.java new file mode 100644 index 00000000..89882b2f --- /dev/null +++ b/chapter10b/src/test/java/com/seaofnodes/simple/Chapter01Test.java @@ -0,0 +1,100 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class 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 testBad1() { + try { + new Parser("ret").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Undefined name 'ret'",e.getMessage()); + } + } + + @Test + public void testBad2() { + try { + new Parser("return 0123;").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Syntax error: integer values cannot start with '0'",e.getMessage()); + } + } + + @Test + public void testNotBad3() { + // this test used to fail in chapter 1 + assertEquals("return 12;", new Parser("return --12;").parse().print()); + } + + @Test + public void testBad4() { + try { + new Parser("return 100").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Syntax error, expected ;: ",e.getMessage()); + } + } + + @Test + public void testNotBad5() { + // this test used to fail in chapter 1 + assertEquals("return -100;", new Parser("return -100;").parse().print()); + } + + @Test + public void testBad6() { + try { + new Parser("return100").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Undefined name 'return100'",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/chapter10b/src/test/java/com/seaofnodes/simple/Chapter02Test.java b/chapter10b/src/test/java/com/seaofnodes/simple/Chapter02Test.java new file mode 100644 index 00000000..a6075b75 --- /dev/null +++ b/chapter10b/src/test/java/com/seaofnodes/simple/Chapter02Test.java @@ -0,0 +1,60 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class Chapter02Test { + + @Test + public void testParseGrammar() { + Parser parser = new Parser("return 1+2*3+-5;"); + StopNode ret = parser.parse(); + assertEquals("return 2;", ret.print()); + } + + @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()); + } + +} diff --git a/chapter10b/src/test/java/com/seaofnodes/simple/Chapter03Test.java b/chapter10b/src/test/java/com/seaofnodes/simple/Chapter03Test.java new file mode 100644 index 00000000..609dff76 --- /dev/null +++ b/chapter10b/src/test/java/com/seaofnodes/simple/Chapter03Test.java @@ -0,0 +1,69 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class 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 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); "); + 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 testRedeclareVar() { + try { + new Parser("int a=1; int b=2; int c=0; int b=3; c=a+b;").parse(); + fail(); + } catch( RuntimeException e ) { + assertEquals("Redefining name 'b'",e.getMessage()); + } + } + + @Test + public void testBad1() { + 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/chapter10b/src/test/java/com/seaofnodes/simple/Chapter04Test.java b/chapter10b/src/test/java/com/seaofnodes/simple/Chapter04Test.java new file mode 100644 index 00000000..f1603cc4 --- /dev/null +++ b/chapter10b/src/test/java/com/seaofnodes/simple/Chapter04Test.java @@ -0,0 +1,162 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class Chapter04Test { + + @Test + public void testPeephole() { + Parser parser = new Parser("return 1+arg+2; "); + 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; "); + 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 testSub0() { + Parser parser = new Parser("return arg-0;"); + StopNode ret = parser.parse(); + assertEquals("return arg;", ret.print()); + } + + @Test + public void test0Sub() { + Parser parser = new Parser("return 0-arg;"); + StopNode ret = parser.parse(); + assertEquals("return (-arg);", ret.print()); + } + + @Test + public void testSubNeg() { + Parser parser = new Parser("return 3--arg;"); + StopNode ret = parser.parse(); + assertEquals("return (arg+3);", ret.print()); + } + + @Test + public void testNegSub() { + Parser parser = new Parser("return -arg-3;"); + StopNode ret = parser.parse(); + assertEquals("return (-(arg+3));", ret.print()); + } + + @Test + public void testVarArg() { + Parser parser = new Parser("return arg; "); + 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; ", TypeInteger.constant(2)); + StopNode ret = parser.parse(); + assertEquals("return 2;", ret.print()); + } + + @Test + public void testCompEq() { + Parser parser = new Parser("return 3==3; "); + StopNode ret = parser.parse(); + assertEquals("return 1;", ret.print()); + } + + @Test + public void testCompEq2() { + Parser parser = new Parser("return 3==4; "); + StopNode ret = parser.parse(); + assertEquals("return 0;", ret.print()); + } + + @Test + public void testCompNEq() { + Parser parser = new Parser("return 3!=3; "); + StopNode ret = parser.parse(); + assertEquals("return 0;", ret.print()); + } + + @Test + public void testCompNEq2() { + Parser parser = new Parser("return 3!=4; "); + 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; "); + 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; "); + 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()); + } + + @Test + public void testBug5() { + Parser parser = new Parser("return arg--2;"); + StopNode ret = parser.parse(); + assertEquals("return (arg--2);", ret.print()); + } +} diff --git a/chapter10b/src/test/java/com/seaofnodes/simple/Chapter05Test.java b/chapter10b/src/test/java/com/seaofnodes/simple/Chapter05Test.java new file mode 100644 index 00000000..aefb3a5c --- /dev/null +++ b/chapter10b/src/test/java/com/seaofnodes/simple/Chapter05Test.java @@ -0,0 +1,213 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class Chapter05Test { + + @Test + public void testIfStmt() { + Parser parser = new Parser( +""" +int a = 1; +if (arg == 1) + a = arg+2; +else { + a = arg-3; + +} + +return a;"""); + StopNode ret = parser.parse().iterate(); + assertEquals("return Phi(Region19,(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(); + assertEquals("return Phi(Region17,4,3);", ret.toString()); + } + + @Test + public void testReturn2() { + Parser parser = new Parser( +""" +if( arg==1 ) + return 3; +else + return 4; +""", 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(); + assertEquals("return ((arg*2)+Phi(Region22,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(); + assertEquals("return ((Phi(Region33,(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; +""", TypeInteger.BOT); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Region37,Phi(Region23,2,3),Phi(Region35,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; +""", TypeInteger.BOT); + StopNode stop = parser.parse(); + assertEquals("return ((arg+Phi(Region14,1,0))+Phi(Region29,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(Region17,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()); + } + } + +} diff --git a/chapter10b/src/test/java/com/seaofnodes/simple/Chapter06Test.java b/chapter10b/src/test/java/com/seaofnodes/simple/Chapter06Test.java new file mode 100644 index 00000000..920e26e3 --- /dev/null +++ b/chapter10b/src/test/java/com/seaofnodes/simple/Chapter06Test.java @@ -0,0 +1,294 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class Chapter06Test { + + @Test + public void testPeepholeReturn() { + Parser parser = new Parser( +""" +if( true ) return 2; +return 1; +"""); + StopNode stop = parser.parse().iterate(); + 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(Region13,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(); + 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(); + assertEquals("return Phi(Region38,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(); + assertEquals("return Phi(Region31,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; +""", 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(); + 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(); + assertEquals("return Phi(Region42,3,Phi(Region40,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(); + 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(); + 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(); + assertEquals("return Phi(Region25,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(); + 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(); + 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(); + assertEquals("return (Phi(Region37,Phi(Region24,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(); + 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(); + assertEquals("return 5;", ret.toString()); + } + +} diff --git a/chapter10b/src/test/java/com/seaofnodes/simple/Chapter07Test.java b/chapter10b/src/test/java/com/seaofnodes/simple/Chapter07Test.java new file mode 100644 index 00000000..ff0ca599 --- /dev/null +++ b/chapter10b/src/test/java/com/seaofnodes/simple/Chapter07Test.java @@ -0,0 +1,222 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class Chapter07Test { + + @Test + public void testExample() { + Parser parser = new Parser( + """ + while(arg < 10) { + arg = arg + 1; + } + return arg; + """); + StopNode stop = parser.parse(); + assertEquals("return Phi(Loop7,arg,(Phi_arg+1));", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof ProjNode); + } + + @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(); + assertEquals("return Phi(Region25,1,Phi(Loop14,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(); + assertEquals("return Phi(Loop9,0,Phi(Loop21,Phi_sum,(Phi_sum+Phi(Loop,0,(Phi_j+1)))));", stop.toString()); + } + + @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; +"""); + StopNode stop = parser.parse(); + assertEquals("return Phi(Loop9,2,Phi(Region28,Phi_b,4));", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof ProjNode); + } + + @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(); + assertEquals("return Phi(Loop9,2,(Phi(Region28,Phi_b,4)+1));", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof ProjNode); + } + + + @Test + public void testWhile() { + Parser parser = new Parser( +""" +int a = 1; +while(a < 10) { + a = a + 1; + a = a + 2; +} +return a; +"""); + StopNode stop = parser.parse(); + assertEquals("return Phi(Loop8,1,(Phi_a+3));", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof ProjNode); + } + + @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(); + assertEquals("return Phi(Loop8,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; +"""); + StopNode stop = parser.parse(); + assertEquals("return Phi(Loop8,1,2);", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof ProjNode); + } + + @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(Loop8,1,2);", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof ProjNode); + } + + @Test + public void testWhile3() { + Parser parser = new Parser( +""" +int a = 1; +while(a < 10) { + int b = a + 1; + a = b + 2; +} +return a; +"""); + StopNode stop = parser.parse(); + assertEquals("return Phi(Loop8,1,(Phi_a+3));", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof ProjNode); + } + + @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(); + assertEquals("return Phi(Loop8,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; +"""); + StopNode stop = parser.parse(); + assertEquals("return Phi(Loop9,1,(Phi_a+3));", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof ProjNode); + } + + @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(); + assertEquals("return Phi(Loop9,1,(Phi_a+3));", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof ProjNode); + } + +} diff --git a/chapter10b/src/test/java/com/seaofnodes/simple/Chapter08Test.java b/chapter10b/src/test/java/com/seaofnodes/simple/Chapter08Test.java new file mode 100644 index 00000000..4fa08bd9 --- /dev/null +++ b/chapter10b/src/test/java/com/seaofnodes/simple/Chapter08Test.java @@ -0,0 +1,209 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.evaluator.Evaluator; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import org.junit.Assert; +import org.junit.Test; + +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(); + assertEquals("return Phi(Region37,Phi(Region26,Phi(Loop7,arg,(Phi_arg+1)),Add),Add);", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof RegionNode); + Assert.assertEquals(5L, Evaluator.evaluate(stop, 1)); + Assert.assertEquals(10L, Evaluator.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(); + assertEquals("return Phi(Loop8,1,Phi(Region43,Phi_a,(Phi_a+1)));", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof ProjNode); + } + + + @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(); + assertEquals("return Phi(Region35,Phi(Loop7,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(); + assertEquals("return Phi(Region26,Phi(Loop7,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(); + assertEquals("return Phi(Loop7,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(); + assertEquals("return Phi(Loop7,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(); + assertEquals("return arg;", stop.toString()); + } + + @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(); + 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(); + 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(); + assertEquals("return Phi(Region29,Phi(Loop8,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(); + assertEquals("return (Phi(Loop8,1,Add)+1);", stop.toString()); + assertTrue(stop.ret().ctrl() instanceof ProjNode prj && prj._idx==1 ); + } + +} diff --git a/chapter10b/src/test/java/com/seaofnodes/simple/Chapter09Test.java b/chapter10b/src/test/java/com/seaofnodes/simple/Chapter09Test.java new file mode 100644 index 00000000..1b1920e8 --- /dev/null +++ b/chapter10b/src/test/java/com/seaofnodes/simple/Chapter09Test.java @@ -0,0 +1,301 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.evaluator.Evaluator; +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(); + } + + @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(); + assertEquals("Stop[ return (arg*2); return (Mul+1); ]", stop.toString()); + Assert.assertEquals(2L, Evaluator.evaluate(stop, 1)); + Assert.assertEquals(23L, Evaluator.evaluate(stop, 11)); + } + + @Test + public void testGVN2() { + Parser parser = new Parser( + """ +return arg*arg-arg*arg; + """); + StopNode stop = parser.parse(); + assertEquals("return 0;", stop.toString()); + Assert.assertEquals(0L, Evaluator.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().iterate(); + assertEquals("return Phi(Loop8,arg,(Phi_arg+2));", stop.toString()); + Assert.assertEquals(11L, Evaluator.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().iterate(); + assertEquals("return Phi(Loop9,arg,(Phi_arg+4));", stop.toString()); + Assert.assertEquals(13L, Evaluator.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(); + assertEquals("return Phi(Loop15,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(); + assertEquals("Stop[ ]", stop.toString()); + } + + @Test + public void testWhile0() { + Parser parser = new Parser("while(0) continue; if(0) arg=0;"); + StopNode stop = parser.parse().iterate(); + 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(); + assertEquals("Stop[ ]", stop.toString()); + } + + + @Test + public void testPrecedence() { + Parser parser = new Parser("return 3-1+2;"); + StopNode stop = parser.parse().iterate(); + assertEquals("return 4;", stop.toString()); + } + + @Test + public void testSwap2() { + Parser parser = new Parser("return 1+(1+1);"); + StopNode stop = parser.parse().iterate(); + 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(); + assertEquals("return Phi(Loop10,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(); + assertEquals("Stop[ ]", stop.toString()); + } + + + @Test + public void testFuzz2() { + Parser parser = new Parser("return 0+-0;"); + StopNode stop = parser.parse().iterate(); + 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(); + 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(); + 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(); + 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(); + 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(); + assertEquals("Stop[ ]", stop.toString()); + } + + @Test + public void testFuzz8() { + Parser parser = new Parser("while(arg) arg = arg - 1; return arg;"); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Loop7,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/chapter10b/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter10b/src/test/java/com/seaofnodes/simple/Chapter10Test.java new file mode 100644 index 00000000..b59eee93 --- /dev/null +++ b/chapter10b/src/test/java/com/seaofnodes/simple/Chapter10Test.java @@ -0,0 +1,522 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.node.StopNode; +import com.seaofnodes.simple.type.*; +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 testDeadReferenceReturn() { + for( String body : new String[] { + "return new S; return 0;", + "if(1) return new S; else return 0;" + } ) { + String src = "struct S { int x; }; " + body; + var stop = new Parser(src).parse().iterate(); + org.junit.Assert.assertTrue(body, + stop.ret().expr()._type instanceof com.seaofnodes.simple.type.TypeMemPtr); + } + } + + + + // Issue #246: null-check guards start in Chapter 10; arrays arrive in Chapter 15. + private static final String NULLABLE_POINT_SOURCE = """ + struct Point { int x; }; + Point point = new Point; + point.x = 42; + Point? p = null; + if (arg) p = point; + """; + + @Test + public void testNullGuards() { + for( String body : new String[] { + "if (p != null) return p.x; return -1;", + "if (null != p) return p.x; return -1;", + "if (!!!!p) return p.x; return -1;", + "if (!!!p) return -1; else return p.x;", + "int b = !!p; if (b) return p.x + b - 1; return -1;" + } ) { + StopNode stop = new Parser(NULLABLE_POINT_SOURCE+body).parse().iterate(); + assertEquals(body,-1L,com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,0)); + assertEquals(body,42L,com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,1)); + } + } + + @Test + public void testNullGuardErrors() { + for( String body : new String[] { + "return p.x;", + "if (p == null) return p.x; return -1;", + "if (!!point) return p.x; return -1;", + "if (!!p) { int x = p.x; } return p.x;" + } ) { + try { + new Parser(NULLABLE_POINT_SOURCE+body).parse().iterate(); + fail(body); + } catch( RuntimeException e ) { + // Known null is rejected during parsing in these chapters. + String expected = body.startsWith("if (p == null)") + ? "Accessing unknown field 'x' from 'null'" : "Might be null accessing 'x'"; + assertEquals(body,expected,e.getMessage()); + } + } + } + + @Test + public void testFuzzer() { + Parser parser = new Parser( +""" +int a = arg/3; +int b = arg*5; +int x = arg*7; +int y = arg/11; +int p; int g; int h; +if( (arg/13)==0 ) { + p = x + y; + g = x; + h = y; +} else { + p = a + b; + g = a; + h = b; +} +int r = g+h; +return p-r; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return 0;", stop.toString()); + } + + @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().iterate(); + assertEquals("return 2;", 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().iterate(); + 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(); 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(); 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().iterate(); + assertEquals("return Phi(Loop11,0,(Phi_a+2));", 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().iterate(); 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().iterate(); 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(); 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().iterate(); + assertEquals("return Phi(Region16,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().iterate(); + assertEquals("return Phi(Region34,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().iterate(); 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(); fail(); } + catch( Exception e ) { assertEquals("Redefining name 'v1'", e.getMessage()); } + } + + @Test + public void testIter() { + // Build and use an iterator + Parser parser = new Parser( +""" +struct Iter { + int x; + int len; +} +Iter i = new Iter; +i.len = arg; +int sum=0; +while( i.x < i.len ) { + sum = sum + i.x; + i.x = i.x + 1; +} +return sum; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Loop16,0,(Phi(Loop,0,(Phi_x+1))+Phi_sum));", stop.toString()); + } + + + + + @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; +} +return ret; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Loop11,new s0,Phi(Region34,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; +} +return ret; +"""); + StopNode stop = parser.parse().iterate(); + assertEquals("return Phi(Loop15,new s0,Phi(Region35,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().iterate(); + assertEquals("return Phi(Loop11,new s0,Phi(Region32,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(); 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().iterate(); + 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().iterate(); + 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(); + } + + @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(); + 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(); + 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(); + assertEquals("return 0;", stop.toString()); + } + + + @Test public void testReadBeforeStores() { + var stop = new Parser(""" + struct S { int x; int y; } + S a = new S; S b = new S; + a.x = 11; b.x = 22; + S p = a; if (arg) p = b; + int before = p.x; + a.y = 55; b.y = 66; + a.x = 33; b.x = 44; + return before*100 + p.x; + """).parse().iterate(); + assertEquals(1133L, com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,0)); + assertEquals(2244L, com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,1)); + } + + static final String NESTED_MEMORY = """ + struct S { int x; int y; int z; } + S s = new S; + s.x = 5; s.y = 7; s.z = 11; + while (arg > 0) { + s.x = s.x + 1; + int j = 3; + while (j > 0) { + j = j - 1; + if (j == 1) continue; + s.y = s.y + arg; + if (arg == 2) break; + s.z = s.z + 1; + } + arg = arg - 1; + } + return s; + """; + + @Test public void testMemoryAcrossNestedLoops() { + var stop = new Parser(NESTED_MEMORY).parse().iterate(); + for (int arg=0; arg<7; arg++) { + var obj = (com.seaofnodes.simple.evaluator.Evaluator.Obj) + com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,arg); + org.junit.Assert.assertArrayEquals(new Object[] { + 5L+arg, 7L+arg*(arg+1)-(arg>=2 ? 2 : 0), 11L+2*arg-(arg>=2 ? 2 : 0) + }, obj.fields()); + } + } + + @Test public void testMemoryAtEarlyReturns() { + var stop = new Parser(""" + struct S { int x; int y; int z; } + S s = new S; + s.x = 3; s.y = 5; s.z = 7; + if (arg) { s.x = 11; s.z = 13; return s; } + s.y = 17; + return s; + """).parse().iterate(); + for (int arg=0; arg<2; arg++) { + var obj = (com.seaofnodes.simple.evaluator.Evaluator.Obj) + com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,arg); + org.junit.Assert.assertArrayEquals(arg==0 ? new Object[]{3L,17L,7L} : new Object[]{11L,5L,13L}, obj.fields()); + } + } +} diff --git a/chapter10b/src/test/java/com/seaofnodes/simple/Chapter10bTest.java b/chapter10b/src/test/java/com/seaofnodes/simple/Chapter10bTest.java new file mode 100644 index 00000000..6f094cfb --- /dev/null +++ b/chapter10b/src/test/java/com/seaofnodes/simple/Chapter10bTest.java @@ -0,0 +1,64 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.evaluator.Evaluator; +import org.junit.Test; +import static org.junit.Assert.*; + +public class Chapter10bTest { + @Test public void testIndependentField() { + var stop = new Parser(""" + struct S { int x; int y; } + S s = new S; + s.x = 42; + while (arg > 0) { s.y = s.y + arg; arg = arg - 1; } + return s.x; + """).parse().iterate(); + assertEquals("return 42;",stop.toString()); + assertEquals(42L,Evaluator.evaluate(stop,5)); + assertNull(stop.walk(n -> n instanceof LoadNode ? n : null)); + } + + @Test public void testParallelMemoryPhis() { + var stop = new Parser(""" + struct S { int x; int y; int z; } + S a = new S; S b = new S; + S p = a; if (arg) p = b; + while (arg > 0) { + a.x = p.x + arg; + a.y = p.y + 2; + a.z = p.z + 3; + arg = arg - 1; + } + return a; + """).parse().iterate(); + int[] phis = {0}; + stop.walk(n -> { + if (n instanceof MemPhiNode) phis[0]++; + if (n instanceof MemMergeNode m && m.in(1) instanceof BulkMemPhiNode b) + for(int a=b._aliases.nextSetBit(0); a>=0; a=b._aliases.nextSetBit(a+1)) + assertTrue(a<m.nIns() && m.in(a)!=null); + assertFalse(n instanceof PhiNode && n.isMem() && + !(n instanceof MemPhiNode) && !(n instanceof BulkMemPhiNode)); + return null; + }); + assertTrue(phis[0]>=3); + assertArrayEquals(new Object[]{1L,2L,3L},((Evaluator.Obj)Evaluator.evaluate(stop,4)).fields()); + } + + @Test public void testLateSliceWorklist() throws ReflectiveOperationException { + // Seed 97 exposed a store selecting a bulk predecessor without queuing + // it. Keep both the worklist assertion and the final heap check. + for (int seed : new int[]{0,97,123,456}) { + var parser = new Parser(Chapter10Test.NESTED_MEMORY); + var field = IterPeeps.class.getDeclaredField("WORK"); + field.setAccessible(true); + Object work = field.get(null); + field = work.getClass().getDeclaredField("_R"); + field.setAccessible(true); + ((java.util.Random)field.get(work)).setSeed(seed); + var stop = parser.parse().iterate(); + assertArrayEquals(new Object[]{9L,25L,17L},((Evaluator.Obj)Evaluator.evaluate(stop,4)).fields()); + } + } +} diff --git a/chapter10b/src/test/java/com/seaofnodes/simple/FuzzerWrap.java b/chapter10b/src/test/java/com/seaofnodes/simple/FuzzerWrap.java new file mode 100644 index 00000000..019140ad --- /dev/null +++ b/chapter10b/src/test/java/com/seaofnodes/simple/FuzzerWrap.java @@ -0,0 +1,50 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.fuzzer.Fuzzer; +import org.junit.Test; +import org.junit.Ignore; + + +import static org.junit.Assert.assertTrue; + +/** + * Normal tests run chapter-local regression seeds. Open failures and exploratory + * fuzzing are opt-in (run directly in the IDE or temporarily remove Ignore). + * Seeds depend on this chapter's generator; do not copy them between chapters. + */ +public class FuzzerWrap { + + private static final long[] REGRESSION_SEEDS = { + }; + + private static final long[] OPEN_FAILING_SEEDS = { + }; + + @Test public void fuzzPeepsRegression () { fuzzPeepsSeeds(REGRESSION_SEEDS); } + @Test @Ignore public void fuzzPeepsOpenFailures() { fuzzPeepsSeeds(OPEN_FAILING_SEEDS); } + + private static void fuzzPeepsSeeds(long... seeds) { + var fuzzer = new Fuzzer(); + for (long seed : seeds) + fuzzer.fuzzPeepsRegression(seed); + } + + @Test + @Ignore + public void fuzzPeepsLarge() { + 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/chapter10b/src/test/java/com/seaofnodes/simple/SchedulerTest.java b/chapter10b/src/test/java/com/seaofnodes/simple/SchedulerTest.java new file mode 100644 index 00000000..ad595822 --- /dev/null +++ b/chapter10b/src/test/java/com/seaofnodes/simple/SchedulerTest.java @@ -0,0 +1,73 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.evaluator.Evaluator; +import com.seaofnodes.simple.node.StopNode; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class SchedulerTest { + + private static void assertObj(Object obj, String name, Object... fields) { + assertEquals(Evaluator.Obj.class, obj.getClass()); + var o = (Evaluator.Obj)obj; + assertEquals(name, o.struct()._name); + Assert.assertArrayEquals(fields, o.fields()); + } + + @Test + @Ignore + public void testJig() { + Parser parser = new Parser(""" +struct S { + int f; +} +S v=new S; +v.f = 2; +int i=v.f; +if (arg) v.f=1; +return i; +"""); + StopNode stop = parser.parse();//.iterate(); + var eval = new Evaluator(stop); + assertEquals(2L, eval.evaluate(1, 10)); + } + + @Test + public void testStoreInIf() { + Parser parser = new Parser(""" +struct S { + int f; +} +S v0=new S; +if(arg) v0.f=1; +return v0; +"""); + StopNode stop = parser.parse().iterate(); + var eval = new Evaluator(stop); + assertObj(eval.evaluate(0, 10), "S", 0L); + assertObj(eval.evaluate(1, 10), "S", 1L); + } + + @Test + public void testStoreInIf2() { + Parser parser = new Parser(""" +struct S { + int f; +} +S v=new S; +v.f = 2; +int i=new S.f; +i=v.f; +if (arg) v.f=1; +return i; +"""); + StopNode stop = parser.parse().iterate(); + var eval = new Evaluator(stop); + assertEquals(2L, eval.evaluate(0, 10)); + assertEquals(2L, eval.evaluate(1, 10)); + } + +} diff --git a/chapter10/src/test/java/com/seaofnodes/simple/TypeTest.java b/chapter10b/src/test/java/com/seaofnodes/simple/TypeTest.java index f9c580cc..f9c580cc 100644 --- a/chapter10/src/test/java/com/seaofnodes/simple/TypeTest.java +++ b/chapter10b/src/test/java/com/seaofnodes/simple/TypeTest.java diff --git a/chapter10b/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/chapter10b/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java new file mode 100644 index 00000000..e49e0303 --- /dev/null +++ b/chapter10b/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java @@ -0,0 +1,210 @@ +package com.seaofnodes.simple.evaluator; + +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Field; +import com.seaofnodes.simple.type.TypeInteger; +import com.seaofnodes.simple.type.TypeMemPtr; +import com.seaofnodes.simple.type.TypeStruct; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Objects; + +public class Evaluator { + + private static final Object MEMORY = new Object(); + + public record Obj(TypeStruct struct, Object[] fields) { + @Override + public String toString() { + var sb = new StringBuilder(); + sb.append("Obj<").append(struct._name).append("> {"); + for(int i=0; i<struct._fields.length; i++) { + sb.append("\n ").append(struct._fields[i]._fname).append("=").append(fields[i]); + } + return sb.append("\n}").toString(); + } + } + + private static int getFieldIndex(TypeStruct struct, MemOpNode memop) { + int idx = struct.find(memop._name); + if( idx >= 0 ) return idx; + throw new AssertionError("Field "+memop._name+" not found in struct " + struct._name); + } + + public enum Status { + TIMEOUT, + FALLTHROUGH + } + + /** + * 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( visit.get(node._nid) ) return null; + visit.set(node._nid); + StartNode start = node instanceof StartNode ? (StartNode) node : null; + for( Node def : node._inputs ) { + var res = findStart(visit,def); + if( res != null ) start = res; + } + for( Node use : node._outputs ) { + var res = findStart(visit,use); + if( res != null ) start = res; + } + return start; + } + + private final Object[] values; + private final ArrayList<Object> phiCache = new ArrayList<>(); + private final StartNode start; + private final Scheduler.Block startBlock; + + public Evaluator(Node graph) { + var visited = new BitSet(); + this.start = findStart(visited, graph); + if (this.start == null) { + this.values = null; + this.startBlock = null; + return; + } + this.startBlock = Scheduler.schedule(this.start); + this.values = new Object[visited.length()]; + } + + private long div(DivNode div) { + long in2 = vall(div.in(2)); + return in2 == 0 ? 0 : vall(div.in(1)) / in2; + } + + private Object alloc(NewNode alloc) { + var type = ((TypeMemPtr)alloc.compute())._obj; + return new Obj(type, new Object[type._fields.length]); + } + + private Object load(LoadNode load) { + var from = (Obj)val(load.in(2)); + var idx = getFieldIndex(from.struct, load); + return from.fields[idx]; + } + + private Object store(StoreNode store) { + var to = (Obj)val(store.in(2)); + var val = val(store.in(3)); + var idx = getFieldIndex(to.struct, store); + to.fields[idx] = val; + return null; + } + + private static boolean isTrue(Object obj) { + if (obj == null) return false; + if (obj instanceof Number n) return n.longValue() != 0; + return true; + } + + private Object val(Node node) { + return values[node._nid]; + } + + private long vall(Node node) { + var v = val(node); + if (v instanceof Number n) return n.longValue(); + throw new AssertionError("Not a long " + v); + } + + private Object cons(ConstantNode cons) { + var type = cons.compute(); + if (type instanceof TypeInteger i) return i.value(); + assert type instanceof TypeMemPtr; + return null; + } + + private Object exec(Node node) { + return switch (node) { + case ConstantNode cons -> cons(cons); + case AddNode add -> vall(add.in(1)) + vall(add.in(2)); + case BoolNode.EQ eq -> Objects.equals(val(eq.in(1)), val(eq.in(2))) ? 1L : 0L; + case BoolNode.LE le -> vall(le.in(1)) <= vall(le.in(2)) ? 1L : 0L; + case BoolNode.LT lt -> vall(lt.in(1)) < vall(lt.in(2)) ? 1L : 0L; + case DivNode div -> div(div); + case MinusNode minus -> -vall(minus.in(1)); + case MulNode mul -> vall(mul.in(1)) * vall(mul.in(2)); + case NotNode not -> isTrue(val(not.in(1))) ? 0L : 1L; + case SubNode sub -> vall(sub.in(1)) - vall(sub.in(2)); + case CastNode cast -> val(cast.in(1)); + case LoadNode load -> load(load); + case StoreNode store -> store(store); + case MemMergeNode merge -> MEMORY; + case NewNode alloc -> alloc(alloc); + case ProjNode proj -> ((Object[])val(proj.ctrl()))[proj._idx]; + default -> throw new AssertionError("Unexpected node " + node); + }; + } + + /** + * Run the graph until either a return is found or the number of loop iterations are done. + */ + public Object evaluate(long parameter, int loops) { + if (start == null) return Status.TIMEOUT; + var s = new Object[start.compute()._types.length]; + s[1] = MEMORY; + s[2] = parameter; + values[start._nid] = s; + int i=0; + Scheduler.Block block = this.startBlock; + for(;;) { + for(; i<block.nodes().length;i++) values[block.nodes()[i]._nid] = exec(block.nodes()[i]); + i=0; + switch (block.exit()) { + case null: + return Status.FALLTHROUGH; + case ReturnNode ret: + return val(ret.in(1)); + case IfNode ifn: + block = block.next()[isTrue(val(ifn.in(1))) ? 0 : 1]; + if (block == null) return Status.FALLTHROUGH; + break; + case RegionNode region: + if (loops-- <= 0) return Status.TIMEOUT; + int exit = block.exitId(); + assert exit > 0 && region.nIns() > exit; + block = block.next()[0]; + assert block != null; + for (; i < block.nodes().length; i++) { + if (!(block.nodes()[i] instanceof PhiNode)) break; + phiCache.add(val(block.nodes()[i].in(exit))); + } + for (i=0; i<phiCache.size(); i++) { + values[block.nodes()[i]._nid] = phiCache.get(i); + } + phiCache.clear(); + break; + default: + throw Utils.TODO("Unexpected control node " + block.exit()); + } + } + } + + public static Object evaluate(StopNode graph) { + return evaluate(graph, 0); + } + + public static Object evaluate(StopNode graph, long parameter) { + return evaluate(graph, parameter, 1000); + } + + public static Object evaluate(StopNode graph, long parameter, int loops) { + var res = evaluateWithResult(graph, parameter, loops); + if (res == Status.TIMEOUT) throw new RuntimeException("Timeout"); + return res; + } + + public static Object evaluateWithResult(StopNode graph, long parameter, int loops) { + return new Evaluator(graph).evaluate(parameter, loops); + } + +} diff --git a/chapter10b/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java b/chapter10b/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java new file mode 100644 index 00000000..48ac0562 --- /dev/null +++ b/chapter10b/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java @@ -0,0 +1,599 @@ +package com.seaofnodes.simple.evaluator; + +import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; + +import java.util.*; + +/** + * A late scheduler for test cases. + * <br> + * <br> + * This scheduler is used for tests and is not the final scheduler. + * <br> + * <br> + * The scheduler schedules not as late as possible. Since it is just for test cases this is fine. + * A better scheduler will try to place nodes outside of loops. Due to the schedule late strategy + * this scheduler might place initialization code in loops. + * <br> + * We schedule late here since memory edges have some implicit information. There can only be one + * memory edge (for a field) at a time. However, before <code>if</code> statements this might split. + * When scheduling early a store might be moved out of the loop but then there are two edges alive. + * The one through the store node and the adjacent one. This is an invalid schedule and instead of + * handling this case a late scheduler does not have this problem as memory edges are combined + * through phi nodes. + * <br> + * <br> + * A bit about this scheduler. It first discovers all alive nodes and allocates side data for them. + * Then the control flow graph is build. Then, all phis are placed and finally all the remaining nodes + * in an order where a node is placed when all alive uses are placed. Finally, the output is produced. + */ +public class Scheduler { + + /** + * A basic block with a schedule of the containing nodes. + * + * @param nodes The nodes in the final schedule of this block + * @param exit The exit node of this block. + * @param exitId In case the next block is a region this gives the entry id into the region. + * @param next The next blocks. + */ + public record Block(Node[] nodes, Node exit, int exitId, Block[] next) {} + + /** + * Definition of a block in the building process. + */ + private static class BasicBlock { + + /** + * Dominator of this block + */ + final BasicBlock dom; + /** + * Id of this block where <code>a.depth < b.depth</code> means that a is before b or + * both are in different branches. + */ + final int depth; + /** + * Previous blocks + */ + final BasicBlock[] prev; + /** + * Entry to this block + */ + final Node entry; + /** + * Schedules node in reverse order. + */ + final ArrayList<Node> reverseSchedule = new ArrayList<>(); + + /** + * Initialize the block. + * @param entry The node that started this block + * @param depth The depth of this node. All previous nodes need to have a lower value. + * @param prev The previous blocks. + */ + BasicBlock(Node entry, int depth, BasicBlock... prev) { + BasicBlock dom = null; + int max = -1; + for(var bb: prev) { + max = Math.max(max, bb.depth); + dom = dom==null?bb:dom(dom, bb); + } + assert depth > max; + this.dom = dom; + this.depth = depth; + this.entry = entry; + this.prev = prev; + } + + } + + /** + * Ancillary data for nodes used during the scheduling process. + */ + private static class NodeData { + + /** + * The node this ancillary data is used for. + */ + final Node node; + + /** + * Number of alive users not yet scheduled. + */ + int users = 1; + + /** + * Block into which the node should be placed so far. + * This will be updated with every placed use. + */ + BasicBlock block; + + /** + * Initialize ancillary data for a node + * @param node The node this ancillary data is associated with. + */ + NodeData(Node node) { + this.node = node; + } + + } + + /** + * Ancillary data for nodes. + */ + private final IdentityHashMap<Node, NodeData> data = new IdentityHashMap<>(); + /** + * List of nodes which can be placed since all users are placed. + */ + private final Stack<NodeData> scheduleQueue = new Stack<>(); + + /** + * Get the ancillary data for an alive node. + * @param node The node for which the ancillary data is requested. + * @return The ancillary data for the node. + */ + NodeData d(Node node) { + return od(node).orElseThrow(AssertionError::new); + } + + /** + * Get the ancillary data for a potentially dead node. + * @param node The node for which the ancillary data is requested. + * @return The ancillary data for the node an alive node or + * <code>Optional.empty()</code> for <code>null</code> or dead nodes. + */ + Optional<NodeData> od(Node node) { + return Optional.ofNullable(data.get(node)); + } + + /** + * Checks a node for validity + * @param data The node to check. + * @return true if all placed inputs are before this node. + */ + private boolean isValid(NodeData data) { + return data.node._inputs.stream().map(i->i==null?null:d(i)).map(d->d==null||d.users>0?null:d.block).allMatch(d->d==null||dom(d, data.block)==d); + } + + /** + * Checks that node is not XCtrl. + * @param node The node to check. + * @return true if the node is not XCtrl. + */ + private static boolean isNotXCtrl(Node node) { + return !(node instanceof ConstantNode c && c.compute() == Type.XCONTROL); + } + + /** + * Return the dominator of <code>a</code> and <code>b</code> + * @param a Block a + * @param b Block b + * @return The dominator of <code>a</code> and <code>b</code> + */ + private static BasicBlock dom(BasicBlock a, BasicBlock b) { + while (a!=b) { + if (a.depth >= b.depth) a=a.dom; + if (b.depth > a.depth) b=b.dom; + } + return a; + } + + /** + * Return if a is before b. + * @param a Block a + * @param b Block b + * @return true is a is before b. + */ + private static boolean isBefore(BasicBlock a, BasicBlock b) { + while (b.depth > a.depth) { + while (b.dom.depth > a.depth) b = b.dom; + for (int i=0; i<b.prev.length-1; i++) { + if (isBefore(a, b.prev[i])) return true; + } + b = b.prev[b.prev.length-1]; + } + return a == b; + } + + /** + * Is node placed during the control flow graph build. + * @param data Node to check. + * @return true if node is placed during the control flow graph build. + */ + private static boolean isPinnedNode(NodeData data) { + return data.node.isCFG() || data.node instanceof PhiNode; + } + + /** + * Refine placement of a node. + * @param data The node to refine. + * @param block The block before the node should be scheduled. + */ + private void refinePlacement(NodeData data, BasicBlock block) { + assert !isPinnedNode(data); + data.block = data.block == null ? block : dom(data.block, block); + assert isValid(data) : data.node.uniqueName()+" at "+data.block.entry.uniqueName(); + } + + /** + * Refine placement if before is happening before data + * @param data The node for which the placement should be refined. + * @param before The node before which the data node should be placed. + */ + private void optionalRefinePlacement(NodeData data, Node before) { + od(before).ifPresent(d->{ + // before might be in a different branch. So check this case with isBefore + // and only refine the placement if it is not in a different branch. + if (isBefore(d.block, data.block)) refinePlacement(data, d.block); + }); + } + + /** + * Schedule all nodes not yet scheduled. + */ + private void doSchedule() { + while (!scheduleQueue.isEmpty()) { + var data = scheduleQueue.pop(); + assert !isPinnedNode(data); + + assert data.block != null; + assert data.users == 0; + + if (data.node instanceof LoadNode l) { + assert isValid(data); + // Handle anti-deps of load nodes. + // At this point all anti-dep nodes are scheduled, + // but they did not refine the placement + // so do that now. + var mem = l.in(1); + for(var out : mem._outputs) { + // Aggregates describe partitions; they do not overwrite + // memory. Only users of this alias constrain the load. + if (out instanceof MemMergeNode || out instanceof ReturnNode) continue; + if (out instanceof StoreNode s && s._alias != l._alias) continue; + if (out instanceof MemPhiNode p && p._alias != l._alias) continue; + if (out instanceof BulkMemPhiNode p && p.isSplit(l._alias)) continue; + if (out instanceof PhiNode p) { + var r = p.in(0); + for (int i = 1; i < p.nIns(); i++) { + if (p.in(i) == mem) optionalRefinePlacement(data, r.in(i)); + } + } else if (!(out instanceof LoadNode)) { + optionalRefinePlacement(data, out); + } + } + } + + assert isValid(data); + data.block.reverseSchedule.add(data.node); + + for(var in:data.node._inputs) { + if (in!=null) update(d(in), data.block); + } + if (data.node instanceof StoreNode s) { + // Store nodes have anti-deps to load nodes. + // So decrease the uses of these loads when the store is placed. + for (var out: s.in(1)._outputs) { + if (out instanceof LoadNode l && l._alias==s._alias) od(out).ifPresent(this::decUsers); + } + } + } + + // Now all nodes should be placed and have a block assigned + assert this.data.values().stream().noneMatch(d->d.block==null); + } + + /** + * Decrement the not-yet-placed users of a block. + * @param data The node for which the users should be decremented. + */ + private void decUsers(NodeData data) { + assert data.users > 0; + if (--data.users == 0) { + assert data.block != null; + assert isValid(data); + + // When all users are gone this node can be scheduled. + scheduleQueue.push(data); + } + } + + /** + * Update the placement of a node + * @param data The node to update + * @param block The block before which the node should happen. + */ + private void update(NodeData data, BasicBlock block) { + assert block != null; + if (data.users == 0) { + assert isPinnedNode(data); + return; + } + refinePlacement(data, block); + decUsers(data); + } + + /** + * Checks if a CFG node is ready to be placed. This is used for regions joining branches. + * @param node The CFG node to check + * @return true if all parents of a CFG node are placed. + */ + private boolean isCFGNodeReady(Node node) { + if (node instanceof LoopNode l) { + assert d(l.in(1)).block != null; + } else if (node instanceof RegionNode r) { + for(int i=1; i<r.nIns(); i++) { + if (od(r.in(i)).map(d->d.block==null).orElse(false)) return false; + } + } else { + assert d(node.in(0)).block != null; + } + return true; + } + + /** + * Schedule all phi nodes. + * @param phiQueue List of all the phi nodes to schedule. + */ + private void schedulePhis(Stack<NodeData> phiQueue) { + while(!phiQueue.empty()) { + var data = phiQueue.pop(); + var phi = (PhiNode) data.node; + var r = phi.in(0); + data.block = d(r).block; + for(int i=1; i<phi.nIns(); i++) { + var in = phi.in(i); + od(r.in(i)).ifPresent(d->update(d(in), d.block)); + } + } + } + + /** + * Build the control flow graph. + * @param start The start node. + */ + private void doBuildCTF(StartNode start) { + var queue = new Stack<NodeData>(); + var phiQueue = new Stack<NodeData>(); + queue.push(d(start)); + int depth = 0; + while (!queue.isEmpty()) { + var data = queue.pop(); + var node = data.node; + assert node.isCFG(); + BasicBlock block; + switch (node) { + case StartNode s: + block = new BasicBlock(s, depth++); + break; + case LoopNode l: + block = new BasicBlock(l, depth++, d(l.in(1)).block); + break; + case RegionNode r: + var prev = new ArrayList<BasicBlock>(); + for(int i=1; i<r.nIns(); i++) { + od(r.in(i)).ifPresent(d->prev.add(d.block)); + } + block = new BasicBlock(r, depth++, prev.toArray(BasicBlock[]::new)); + break; + case IfNode i: + block = d(i.in(0)).block; + break; + case ReturnNode r: + block = d(r.in(0)).block; + break; + default: + block = new BasicBlock(node, depth++, d(node.in(0)).block); + } + data.block = block; + if (node instanceof RegionNode r) { + // Regions might have phis which need to be scheduled. + // Put them on a list for later scheduling. + for (var out:r._outputs) { + if (out instanceof PhiNode p) { + var d = this.data.get(p); + if (d!=null) { + d.users=0; + phiQueue.push(d); + } + } + } + } + if (!(node instanceof ReturnNode)) + for (Node n:node._outputs) if (n.isCFG() && isCFGNodeReady(n) && d(n).block == null) queue.push(d(n)); + var b = block; + for(var in:data.node._inputs) od(in).ifPresent(d->update(d, b)); + } + + // Schedule all phi nodes. + schedulePhis(phiQueue); + } + + /** + * Mark a node alive and create ancillary data for it. + * @param queue List into which this node should be placed if it needs to be visited. + * @param node Node which should be marked alive. + * @param cfg If this node is a CFG node. + */ + private void markAlive(Stack<NodeData> queue, Node node, boolean cfg) { + var nd = od(node).orElse(null); + if (nd != null) { + // Node was already visited, just increase the users. + if (nd.users>0) nd.users++; + return; + } + assert node.isCFG() == cfg; + assert isNotXCtrl(node); + nd = new NodeData(node); + if (cfg) nd.users=0; + data.put(node, nd); + queue.push(nd); + } + + /** + * Mark all alive nodes. + * First all CFG nodes are marked, then all other nodes. + * @param node THe start node. + */ + private void doMarkAlive(Node node) { + var cfgQueue = new Stack<NodeData>(); + var dataQueue = new Stack<NodeData>(); + var mem = new Stack<NodeData>(); + markAlive(cfgQueue, node, true); + // Mark all CFG nodes. + while (!cfgQueue.isEmpty()) { + var data = cfgQueue.pop(); + node = data.node; + assert node.isCFG(); + if (!(node instanceof ReturnNode)) { + for (var out : node._outputs) if (out.isCFG() && isNotXCtrl(out)) markAlive(cfgQueue, out, true); + } + for (var in : node._inputs) if(in!=null && !in.isCFG() && isNotXCtrl(in)) markAlive(dataQueue, in, false); + } + // Mark all other nodes. + while (!dataQueue.isEmpty()) { + var data = dataQueue.pop(); + node = data.node; + assert !node.isCFG(); + if (node instanceof PhiNode phi) { + var r = phi.in(0); + for (int i=1; i<phi.nIns(); i++) { + if (od(r.in(i)).isPresent()) markAlive(dataQueue, phi.in(i), false); + } + } else { + for (var in : node._inputs) if (in != null && !in.isCFG()) markAlive(dataQueue, in, false); + } + if (node instanceof StoreNode) mem.push(data); + } + // Handle store nodes and increase load with an anti-dep to the store. + while (!mem.isEmpty()) { + var data = mem.pop(); + node = data.node; + for(var out:node.in(1)._outputs) { + if (out instanceof LoadNode l && l._alias==((StoreNode)node)._alias) + od(out).ifPresent(d->d.users++); + } + } + } + + /** + * Helper function to append the nodes of a block to an array of nodes + * @param arr The array the nodes of a block should be appended to. + * @param node A potentially separator node + * @param block The block from which the nodes should be appended. + * @return The combined new array. + */ + private static Node[] appendNodes(Node[] arr, Node node, BasicBlock block) { + var idx = arr.length; + arr = Arrays.copyOf(arr, arr.length+block.reverseSchedule.size()+(node==null?0:1)); + if (node != null) arr[idx++] = node; + for(int i=block.reverseSchedule.size()-1;i>=0;i--) { + arr[idx++] = block.reverseSchedule.get(i); + } + return arr; + } + + /** + * Find the CFG output of a node + * @param node The node + * @return THe CFG output of the node. + */ + private static Node findSingleCFGOut(Node node) { + if (node instanceof StartNode) { + for(var n:node._outputs) if (n instanceof ProjNode p && p._idx==0) return p; + return null; + } + assert node._outputs.stream().filter(Node::isCFG).limit(2).count()<=1; + for(var n:node._outputs) if(n.isCFG()) return n; + return null; + } + + /** + * Build the final data structure after all the scheduling happened. + * @param start The start node + * @return The final schedule. + */ + private Block build(Node start) { + var blocks = new IdentityHashMap<Node, Block>(); + var queue = new Stack<NodeData>(); + queue.push(d(start)); + + // Visit all CFG nodes and create blocks for them. + // This can combine blocks. + while(!queue.isEmpty()) { + var data = queue.pop(); + var first = data.node; + Node prev; + var last = first; + Node[] arr = new Node[0]; + if (first instanceof RegionNode) { + var nodes = new ArrayList<Node>(); + for(var out: first._outputs) { + if (out instanceof PhiNode) { + if(od(out).isPresent()) nodes.add(out); + } + } + arr = nodes.toArray(arr); + } + arr = appendNodes(arr, null, data.block); + assert !(first instanceof IfNode); + for(;;) { + prev = last; + last = findSingleCFGOut(last); + if (last == null) break; + if (last instanceof RegionNode) { + if (blocks.get(last) == null && last != first) + queue.push(d(last)); + break; + } + if (last instanceof ReturnNode) break; + if (last instanceof IfNode if_) { + for(var out:if_._outputs) if (out.isCFG() && blocks.get(out) == null) queue.push(d(out)); + break; + } + data = d(last); + arr = appendNodes(arr, data.node, data.block); + } + blocks.put(first, switch(last) { + case null -> new Block(arr, null, -1, new Block[0]); + case IfNode i -> new Block(arr, i, -1, new Block[2]); + case RegionNode r -> new Block(arr, r, Utils.find(r._inputs, prev), new Block[1]); + case ReturnNode r -> new Block(arr, r, -1, new Block[0]); + default -> throw new AssertionError("Unexpected block exit node"); + }); + } + // Update the next pointer of all blocks. + for (var block: blocks.values()) { + switch(block.exit) { + case null: break; + case IfNode i: + for(var out:i._outputs) if (out instanceof ProjNode p) block.next[p._idx] = blocks.get(p); + break; + case RegionNode r: + block.next[0] = blocks.get(r); + break; + default: + assert block.exit instanceof ReturnNode; + } + } + // And return the block for the start node. + return blocks.get(start); + } + + /** + * Create a schedule for the program reachable from start. + * @param start The start node + * @return The final schedule. + */ + public static Scheduler.Block schedule(StartNode start) { + var scheduler = new Scheduler(); + scheduler.doMarkAlive(start); + scheduler.doBuildCTF(start); + scheduler.doSchedule(); + return scheduler.build(start); + } + +} diff --git a/chapter10b/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java b/chapter10b/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java new file mode 100644 index 00000000..7dcb88e5 --- /dev/null +++ b/chapter10b/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java @@ -0,0 +1,199 @@ +package com.seaofnodes.simple.fuzzer; + +import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.evaluator.Evaluator; +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.node.StopNode; + +import java.util.ArrayList; +import java.util.Objects; +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 graphs optimized with different worklist seeds are 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(); + } + + private static boolean neq(Object a, Object b) { + if (Objects.equals(a, b)) return false; + if (a instanceof Evaluator.Obj ea && b instanceof Evaluator.Obj eb) { + if (ea.struct() != eb.struct()) return true; + for(int i=0;i<ea.fields().length;i++) { + if (neq(ea.fields()[i], eb.fields()[i])) return true; + } + return false; + } + return true; + } + + /** + * Check that two graphs result in the same output when supplied with a value + * @param e1 Evaluator for the first graph + * @param e2 Evaluator for the second graph + * @param in The input value to both graphs to test for an equal output + */ + private static void checkGraphs(Evaluator e1, Evaluator e2, long in) { + var r1 = e1.evaluate(in, EVAL_TIMEOUT); + var r2 = e2.evaluate(in, EVAL_TIMEOUT); + if (r1 == Evaluator.Status.TIMEOUT || r2 == Evaluator.Status.TIMEOUT) return; + if (neq(r1, r2)) + throw new RuntimeException("Different calculations values " + r1 + " vs " + r2); + } + + /** + * Run checks for script. Compile the script with two different optimizer worklist seeds. + * 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, 123); + } catch (RuntimeException e1) { + try { + FuzzerUtils.parse(script, 456); + } 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, 456); + var e1 = new Evaluator(stop1); + var e2 = new Evaluator(stop2); + checkGraphs(e1, e2, 0); + checkGraphs(e1, e2, 1); + checkGraphs(e1, e2, 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(); + } + + /** Run a fixed chapter-local seed without reducing or swallowing failures. */ + public void fuzzPeepsRegression(long seed) { + var rand = new Random(seed); + var sb = new StringBuilder(); + var valid = new ScriptGenerator(rand, sb, true).genProgram(); + try { + runCheck(sb.toString(), valid); + } catch (Throwable e) { + AssertionError ae = new AssertionError("Fuzzer regression seed failed: " + seed); + ae.initCause(e); + throw ae; + } + } +} diff --git a/chapter10b/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java b/chapter10b/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java new file mode 100644 index 00000000..f947a234 --- /dev/null +++ b/chapter10b/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java @@ -0,0 +1,142 @@ +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; + private static final java.util.Random WorkRandom; + + /** + * 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"); + WorkRandom = getFieldValue(getFieldValue(IterPeeps.class, "WORK"), "_R"); + 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 and optimize using the given worklist seed + */ + public static StopNode parse(String script, long workListSeed) { + 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); + WorkRandom.setSeed(workListSeed); + var stop = parser.parse(); + return stop.iterate(); + } finally { + NodeWalkVisit.clear(); + System.setErr(err); + System.setOut(out); + } + } + +} diff --git a/chapter10b/src/test/java/com/seaofnodes/simple/fuzzer/Reducer.java b/chapter10b/src/test/java/com/seaofnodes/simple/fuzzer/Reducer.java new file mode 100644 index 00000000..3731b40c --- /dev/null +++ b/chapter10b/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/chapter10b/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java b/chapter10b/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java new file mode 100644 index 00000000..155063ab --- /dev/null +++ b/chapter10b/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); + } + +} diff --git a/chapter11/README.md b/chapter11/README.md index 2a1c3903..c2f329bc 100644 --- a/chapter11/README.md +++ b/chapter11/README.md @@ -1,5 +1,11 @@ # Chapter 11: Global Code Motion +The preceding memory lesson is now split into [10a](../chapter10a/README.md) +and [10b](../chapter10b/README.md). This snapshot still constructs the older +per-alias memory chains in the parser. The language is unchanged; forwarding +10b's bulk-memory representation through Chapters 11-24 is a separate migration. + + # Table of Contents 1. [High Level Overview](#high-level-overview) @@ -16,7 +22,7 @@ 12. [Video Walk Through](#video-walk-through) -You can also read [this chapter](https://github.com/SeaOfNodes/Simple/tree/linear-chapter11) in a linear Git revision history on the [linear](https://github.com/SeaOfNodes/Simple/tree/linear) branch and [compare](https://github.com/SeaOfNodes/Simple/compare/linear-chapter10...linear-chapter11) it to the previous chapter. +You can also read [this chapter](https://github.com/SeaOfNodes/Simple/tree/linear-chapter11) in a linear Git revision history on the [linear](https://github.com/SeaOfNodes/Simple/tree/linear) branch and [compare](https://github.com/SeaOfNodes/Simple/compare/linear-chapter10b...linear-chapter11) it to the previous chapter. The original input source program defines a sequence in which things happen. As we parse the program into Sea of Nodes representation diff --git a/docs/chapter-backports.md b/docs/chapter-backports.md index c2a4b56c..f76f92de 100644 --- a/docs/chapter-backports.md +++ b/docs/chapter-backports.md @@ -7,9 +7,9 @@ are disposable build artifacts. Reusable rules live in Introduce independent fixes in the earliest applicable chapter and propagate them through every affected snapshot. Testing Chapter 25's inherited tests does -not validate the compilers in the earlier chapter directories. Keep building -SSA with incomplete types in Chapter 25 for now; moving that architecture or -renumbering chapters is deferred. Cliff wants to revisit splitting Chapter 25 +not validate the compilers in the earlier chapter directories. Chapter 10 is +now split into 10a (bulk memory) and 10b (lazy memory partitioning). Keep the +rest of SSA construction with incomplete types in Chapter 25 for now. Cliff wants to revisit splitting Chapter 25 into smaller chapters; escape analysis remains in 25 until that larger review. Reset checkpoint (2026-09-28): Cliff committed the completed FunPtr work as @@ -22,6 +22,13 @@ Splitting Chapter 25 is deferred, not an instruction to renumber. ## Pending corrections +- **Forward Chapter 10b memory partitioning through 11-24.** The 10a/10b split + introduces whole-memory SSA first, then `MemMerge`, `MemPhi`, and `BulkMemPhi`. + Later snapshots retain their original memory representation. Migration + boundaries include scheduling in 11, array/allocation memory in 15, the + parser's `ScopeMinNode` in 16, and call/return aggregation in 18. Keep private + constructor memory, escape tracking, and incomplete-type inference in 25. + - **Chapter 24 Load BOTTOM-on-error backport, unwound/deferred (2026-09-28).** Cliff requested starting with 24 and analyzing failures before proceeding. Literal global BOTTOM memory/pointer inputs already propagate BOTTOM. The @@ -210,6 +217,20 @@ The top-level runner accepts explicit chapter lists, e.g. ## Validation record +- **Chapter 10a/10b split (2026-09-29).** Replaced `chapter10` with two + independently buildable snapshots: whole-memory SSA in 10a, then lazy + `MemMerge`/`MemPhi`/`BulkMemPhi` partitioning in 10b. Updated Make discovery, + Maven modules, IDEA descriptors/local module list, chapter navigation, + linear-history link handling, and regenerated the memory diagrams. + Fresh Make builds pass **156 tests in 10a and 159 in 10b**, including the + chapter-local fuzzer regression wrapper. Both release jars and tags build. + Offline Maven tests pass in both modules. Five memory programs each pass + seven inputs across 100 optimizer seeds in each compiler (**3,500 executions + per chapter**). Seed 97's missed bulk-Phi revisit is retained as a regression. + Logs: `build/chapter10-clean-build.log`, `build/chapter10-maven.log`, + `build/10a-seeds.log`, and `build/10b-seeds.log`. Chapters 11-24 have not yet + adopted the new memory representation; that forward migration is queued above. + This is a condensed completion record, not a list of current suite counts for old revisions. Earlier detailed traces are in Git history; durable invariants have been consolidated into the [AI notes](../skills/chapter25-codex-notes.md). diff --git a/graph/README.md b/graph/README.md index cb9ae1f1..812a7318 100644 --- a/graph/README.md +++ b/graph/README.md @@ -40,7 +40,7 @@ Frame numbers start at 1. Jumps lay out only their destination. When the slider moves during layout, the viewer finishes that calculation and then handles the latest request, skipping intermediate requests. Already visited layouts stay cached. -Chapters 10–25 run their existing TypeCheck pass after optimization. A successful +Chapters 10a, 10b, and 11–25 run their existing TypeCheck pass after optimization. A successful check adds a TypeCheck phase-complete frame. An error adds a final graph frame with the compiler's diagnostic; stepping or jumping to it centers the error node at a readable scale, outlines it in red, and shows the message on the phase line diff --git a/graph/graph.iml b/graph/graph.iml new file mode 100644 index 00000000..8e9a0e43 --- /dev/null +++ b/graph/graph.iml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module type="JAVA_MODULE" version="4"> + <component name="NewModuleRootManager" inherit-compiler-output="false"> + <output url="file://$MODULE_DIR$/../build/idea/production/graph" /> + <output-test url="file://$MODULE_DIR$/../build/idea/test/graph" /> + <exclude-output /> + <content url="file://$MODULE_DIR$"> + <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" /> + <excludeFolder url="file://$MODULE_DIR$/build" /> + <excludeFolder url="file://$MODULE_DIR$/target" /> + </content> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + </component> +</module> @@ -28,7 +28,8 @@ <module>chapter07</module> <module>chapter08</module> <module>chapter09</module> - <module>chapter10</module> + <module>chapter10a</module> + <module>chapter10b</module> <module>chapter11</module> <module>chapter12</module> <module>chapter13</module> diff --git a/skills/chapter25-codex-notes.md b/skills/chapter25-codex-notes.md index 5cb7dc53..4faf5bdd 100644 --- a/skills/chapter25-codex-notes.md +++ b/skills/chapter25-codex-notes.md @@ -37,6 +37,10 @@ For shared viewer work, read [the graph viewer notes](graph-viewer-codex-notes.m ## Tutorial backports +- Chapter 10 is split into `chapter10a` (one bulk memory chain) and `chapter10b` + (lazy graph partitioning). Both are standalone snapshots. Chapters 11-24 + still use their prior memory representation; forwarding 10b is pending. + - The preferred direction is: introduce a fix in the earliest applicable chapter, then use the same implementation in later snapshots where practical. Tutorial progression takes priority over importing the fully general Chapter 25 solution. @@ -48,7 +52,7 @@ For shared viewer work, read [the graph viewer notes](graph-viewer-codex-notes.m port it into the same test class in every later affected chapter directory. - Every directory contains its own compiler snapshot. Testing Chapter 25's inherited `Chapter10Test` does not validate the Chapter 10 compiler. Use the - top-level runner, e.g. `make -k tests CHAPTERS="chapter10 chapter11"`, with all + top-level runner, e.g. `make -k tests CHAPTERS="chapter10a chapter10b chapter11"`, with all affected directories explicitly listed. Establish the destination baseline before changing it; older Makefiles may need a forced rebuild after API changes. - Keep unrelated discoveries separate in `docs/chapter-backports.md`. Completed |
