diff options
664 files changed, 30553 insertions, 9640 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,20 @@ 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 now runs through Chapter 24, including constructors, +mutability, whole-memory function calls, machine-code execution, cyclic field +types, and SCCP. Chapters 22-25 are ready for review. Chapter 25 includes the +related Load-search, Phi-factoring, and scheduling corrections while retaining +its separate constructor-memory and incomplete-type SSA design. + +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/ReturnNode.java b/chapter10/src/main/java/com/seaofnodes/simple/node/ReturnNode.java deleted file mode 100644 index 92bda5bf..00000000 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.type.*; - -import java.util.BitSet; - -/** - * The Return node has two inputs. The first input is a control node and the - * second is the data node that supplies the return value. - * <p> - * In this presentation, Return functions as a Stop node, since multiple <code>return</code> statements are not possible. - * The Stop node will be introduced in Chapter 6 when we implement <code>if</code> statements. - * <p> - * The Return's output is the value from the data node. - */ -public class ReturnNode extends Node { - - public ReturnNode(Node ctrl, Node data, ScopeNode scope) { - super(ctrl, data); - // We lookup memory slices by the naming convention that they start with $ - // We could also use implicit knowledge that all memory projects are at offset >= 2 - String[] names = scope.reverseNames(); - for (String name: names) { - if (!name.equals("$ctrl") && name.startsWith("$")) - addDef(scope.lookup(name)); - } - } - - public Node ctrl() { return in(0); } - public Node expr() { return in(1); } - - @Override - public String label() { return "Return"; } - - @Override - StringBuilder _print1(StringBuilder sb, BitSet visited) { - sb.append("return "); - expr()._print0(sb, visited); - return sb.append(";"); - } - - @Override public boolean isCFG() { return true; } - - @Override - public Type compute() { - return TypeTuple.make(ctrl()._type,expr()._type); - } - - @Override - public Node idealize() { - if( ctrl()._type==Type.XCONTROL ) - return ctrl(); - return null; - } -} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/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..80a1f46c --- /dev/null +++ b/chapter10a/README.md @@ -0,0 +1,331 @@ +# 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, memory, result | Completion with all preceding effects retained | + +For nodes consuming or producing both control and memory, control occupies slot +0 and memory slot 1. Start's outputs are `{ctrl, $mem, arg}`; Return's inputs +are `{ctrl, $mem, result}`. + +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: + +```java +struct Point { int x; int y; } +Point p = new Point; +if( arg ) p.x = 3; else p.x = 4; +return p.x; +``` + + + +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. + +```java +struct Point { int x; int y; } +Point p = new Point; +while( arg ) { + p.x = arg; + arg = arg-1; +}; +return p.x; +``` + + + +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 reverse direction can save an operation: a Phi of matching Loads or Stores +can become one operation after the join. For example, these two writes: + +```java +if (arg) s.x = arg+1; +else s.x = arg+2; +``` + +can become `s.x = Phi(arg+1,arg+2)`. The general rewrite creates a separate, +correctly typed Phi for each differing operand, including memory and pointers; +identical operands need no Phi. An exact-class copy preserves the operation's +attributes. The proposed result must not widen the original Phi's type. + +Every arm must match the operation and field and have no control input. A Store +must have no other users, so moving it removes the original effect. A Load must +have no possible conflicting write or memory merge among its memory's users; +otherwise delaying the read could change its value. The check happens during +optimization, before any scheduler can supply explicit anti-dependency edges. + +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. Boxes +containing an ellipsis abbreviate further parallel alternatives; they are not +extra union types. Field and tuple-element type lattices are elided in the +diagram. Field types can be any scalar type (Integer or Pointer), Tuple types +can be *any* lattice type, including Control and Memory. + +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 pointer fields will never contain integers +(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..001f4bf6 --- /dev/null +++ b/chapter10a/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -0,0 +1,89 @@ +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 boolean canDrop(MemOpNode other, Node dep) { + return super.canDrop(other,dep) && _declaredType==((LoadNode)other)._declaredType && !clobbered(dep); + } + + // A Store directly clobbers this memory. A Phi might lead to a clobber + // through a cycle; conservatively stop rather than searching beyond it. + private boolean clobbered(Node dep) { + Node mem = mem(); + mem.addDepForwards(dep); + for( Node use : mem._outputs ) { + if( use==null ) continue; + use.addDepForwards(dep); + if( use instanceof StoreNode || use instanceof PhiNode ) return true; + } + return false; + } + + @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..138f8fa5 --- /dev/null +++ b/chapter10a/src/main/java/com/seaofnodes/simple/node/MemOpNode.java @@ -0,0 +1,46 @@ +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); } + + // Extra conditions for factoring matching memory operations through a Phi. + // The caller has already checked the opcode, input shape and control. + boolean canDrop(MemOpNode other, Node dep) { + ptr().addDep(dep); + if( !_name.equals(other._name) || err()!=null ) return false; + return !(ptr()._type instanceof TypeMemPtr p && + other.ptr()._type instanceof TypeMemPtr q && p._obj!=q._obj); + } + + @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..0a64a7b9 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter10a/src/main/java/com/seaofnodes/simple/node/Node.java @@ -15,14 +15,14 @@ import java.util.function.Function; * The Node class provides common functionality used by all subtypes. * Subtypes of Node specialize by overriding methods. */ -public abstract class Node { +public abstract class Node implements Cloneable { /** * 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; + public int _nid; /** * Inputs to the node. These are use-def references to Nodes. @@ -31,7 +31,7 @@ public abstract class Node { * 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; + public ArrayList<Node> _inputs; /** * Outputs reference Nodes that are not null and have this Node as an @@ -42,7 +42,7 @@ public abstract class Node { * walked in either direction. These outputs are typically used for * efficient optimizations but otherwise have no semantics meaning. */ - public final ArrayList<Node> _outputs; + public ArrayList<Node> _outputs; /** @@ -210,6 +210,7 @@ public abstract class Node { // Error is 'use' does not exist; ok for 'use' to be null. protected boolean delUse( Node use ) { Utils.del(_outputs, Utils.find(_outputs, use)); + moveDepsToWorklist(); // User-count and anti-dependence queries can now change. return _outputs.isEmpty(); } @@ -265,6 +266,7 @@ public abstract class Node { n.unlock(); int idx = Utils.find(n._inputs, this); n._inputs.set(idx,nnn); + n.moveDepsToWorklist(); // Rewiring can change a dependent query without changing type. nnn.addUse(n); } kill(); @@ -449,7 +451,9 @@ public abstract class Node { * 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 ) { + Node addDep(Node dep) { return addDep(dep,false); } + 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; @@ -457,8 +461,8 @@ public abstract class Node { 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( Utils.find(_inputs,dep) != -1 ) return this; // No need for deps on immediate neighbors - if( Utils.find(_outputs,dep)!= -1 ) return this; + if( !forwards && Utils.find(_inputs,dep) != -1 ) return this; // No need for deps on immediate neighbors + if( !forwards && Utils.find(_outputs,dep)!= -1 ) return this; _deps.add(dep); return this; } @@ -590,6 +594,20 @@ public abstract class Node { // Does not need to be implemented in isCFG() nodes. Node copy(Node lhs, Node rhs) { throw Utils.TODO("Binary ops need to implement copy"); } + // Exact-class copy preserving operation attributes, with fresh identity + // and no edges, dependencies, or GVN membership. + public final Node copyEmpty() { + Node n; + try { n = (Node)clone(); } + catch( CloneNotSupportedException e ) { throw new AssertionError(e); } + n._nid = UNIQUE_ID++; + n._inputs = new ArrayList<>(); + n._outputs = new ArrayList<>(); + n._deps = null; + n._hash = 0; + return n; + } + // Report any post-optimize errors String err() { return null; } 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..c52fad49 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter10a/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -66,23 +66,9 @@ public class PhiNode extends Node { if (live != null) return live; - // Pull "down" a common data op. One less op in the world. One more - // Phi, but Phis do not make code. - // Phi(op(A,B),op(Q,R),op(X,Y)) becomes - // op(Phi(A,Q,X), Phi(B,R,Y)). - Node op = in(1); - if( op.nIns()==3 && op.in(0)==null && !op.isCFG() && same_op() ) { - Node[] lhss = new Node[nIns()]; - Node[] rhss = new Node[nIns()]; - lhss[0] = rhss[0] = in(0); // Set Region - for( int i=1; i<nIns(); i++ ) { - lhss[i] = in(i).in(1); - rhss[i] = in(i).in(2); - } - Node phi_lhs = new PhiNode(_label, _declaredType,lhss).peephole(); - Node phi_rhs = new PhiNode(_label, _declaredType,rhss).peephole(); - return op.copy(phi_lhs,phi_rhs); - } + // Phi(op(a,b,...),op(c,d,...)) -> op(Phi(a,c),Phi(b,d),...). + Node down; + if( same_op() && (down=drop_same_op())!=null ) return down; // If merging Phi(N, cast(N)) - we are losing the cast JOIN effects, so just remove. if( nIns()==3 ) { @@ -112,12 +98,59 @@ public class PhiNode extends Node { } private boolean same_op() { - for( int i=2; i<nIns(); i++ ) - if( in(1).getClass() != in(i).getClass() ) - return false; + Node op = in(1); + if( op.isCFG() || op instanceof ConstantNode || op instanceof PhiNode || + op instanceof ProjNode || op instanceof NewNode || op instanceof ScopeNode ) return false; + Node busy = null; + for( int i=1; i<nIns(); i++ ) { + Node n = in(i); + if( region().in(i).addDep(this)._type==Type.XCONTROL || + op.getClass()!=n.getClass() || n.nIns()!=op.nIns() || n.in(0)!=null || !op.eq(n) ) return false; + if( n instanceof MemOpNode mem && !mem.canDrop((MemOpNode)op,this) ) return false; + // Moving a Store must remove the old effect, not duplicate it. + // Allow one shared arm: the other arms disappear into the single + // factored operation, usually reducing the total operation count. + n.addDepForwards(this); + if( n.nOuts()>1 ) { + for( Node use : n._outputs ) + if( use!=null && use!=this ) use.addDepForwards(this); + if( n instanceof StoreNode || busy!=null ) return false; + busy=n; + } + for( int j=1; j<n.nIns(); j++ ) + if( n.in(j) instanceof ScopeNode || ((n.in(j)==null) != (op.in(j)==null)) ) return false; + } return true; } + private Node drop_same_op() { + Node op = in(1), cp = op.copyEmpty(); + cp._type = null; + cp.addDef(null); + for( int j=1; j<op.nIns(); j++ ) { + Node x = op.in(j); + boolean different = false; + for( int i=2; i<nIns(); i++ ) + if( in(i).in(j)!=x ) different=true; + if( different ) { + Node[] ins = new Node[nIns()]; + ins[0] = region(); + Type t = Type.TOP; + for( int i=1; i<nIns(); i++ ) { + ins[i] = in(i).in(j); + t = t.meet(ins[i]._type); + } + PhiNode phi = new PhiNode(_label,t.glb(),ins); + x = phi.peephole(); + } + cp.addDef(x); + } + // Factoring must not widen the result (e.g. correlated And operands). + if( cp.compute().isa(compute()) ) return cp; + cp.kill(); + return null; + } + /** * If only single unique input, return it */ 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..a6b8d834 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/RegionNode.java +++ b/chapter10a/src/main/java/com/seaofnodes/simple/node/RegionNode.java @@ -67,7 +67,7 @@ public class RegionNode extends Node { 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.addDepForwards(this).in(0)==p2.addDepForwards(this).in(0) && p1.in(0) instanceof IfNode iff ) return iff.ctrl(); diff --git a/chapter10a/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter10a/src/main/java/com/seaofnodes/simple/node/ReturnNode.java new file mode 100644 index 00000000..96d58599 --- /dev/null +++ b/chapter10a/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -0,0 +1,42 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.*; + +import java.util.BitSet; + +/** Keeps all preceding memory effects and the return value alive: {ctrl, $mem, value}. */ +public class ReturnNode extends Node { + + public ReturnNode(Node ctrl, Node data, ScopeNode scope) { + super(ctrl, scope.lookup("$mem"), data); + } + + public Node ctrl() { return in(0); } + public Node mem () { return in(1); } + public Node expr() { return in(2); } + + @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 exposes the complete memory state. + return TypeTuple.make(ctrl()._type,TypeMem.BOT,expr()._type); + } + + @Override + public Node idealize() { + if( ctrl()._type==Type.XCONTROL ) + return ctrl(); + return null; + } +} diff --git a/chapter10/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/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..600e9c73 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/chapter10a/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java 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..edc62f50 100644 --- a/chapter10/src/test/java/com/seaofnodes/simple/Chapter04Test.java +++ b/chapter10a/src/test/java/com/seaofnodes/simple/Chapter04Test.java @@ -85,6 +85,9 @@ public class Chapter04Test { ReturnNode ret = stop.ret(); assertTrue(ret.in(0) instanceof ProjNode); assertTrue(ret.in(1) instanceof ProjNode); + assertEquals(1, ((ProjNode)ret.in(1))._idx); + assertTrue(ret.in(2) instanceof ProjNode); + assertEquals(2, ((ProjNode)ret.in(2))._idx); } @Test diff --git a/chapter10/src/test/java/com/seaofnodes/simple/Chapter05Test.java b/chapter10a/src/test/java/com/seaofnodes/simple/Chapter05Test.java index 3d75ed03..cf7bd6cf 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..f39b1e78 100644 --- a/chapter10/src/test/java/com/seaofnodes/simple/Chapter10Test.java +++ b/chapter10a/src/test/java/com/seaofnodes/simple/Chapter10Test.java @@ -3,6 +3,10 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.node.Node; import com.seaofnodes.simple.node.StopNode; import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.node.LoadNode; +import com.seaofnodes.simple.node.StoreNode; +import com.seaofnodes.simple.evaluator.Evaluator; +import java.util.BitSet; import org.junit.Test; import java.util.Arrays; @@ -165,7 +169,7 @@ while (arg) { return bar.a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,0,(Phi_a+2));", stop.toString()); + assertEquals("return Phi(Loop10,0,(Phi_$mem+2));", stop.toString()); } @Test @@ -286,7 +290,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 +464,118 @@ 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()); + } + } + + @Test public void testDropLoads() { + StopNode stop = new Parser(""" + struct S { int x; } + S a = new S; S b = new S; + a.x = arg; b.x = arg+1; + S p = a; S q = b; + if (arg<0) { p=b; q=a; } + int v; + if (arg>1) v=p.x; else v=q.x; + return v; + """).parse().iterate(); + assertEquals(1,countMemoryNodes(stop,LoadNode.class,new BitSet())); + assertEquals(-1L,Evaluator.evaluate(stop,-1)); + assertEquals(1L,Evaluator.evaluate(stop,0)); + assertEquals(3L,Evaluator.evaluate(stop,3)); + } + + @Test public void testDropStores() { + StopNode stop = new Parser(""" + struct S { int x; } + S s = new S; + if (arg) s.x=arg+1; else s.x=arg+2; + return s; + """).parse().iterate(); + assertEquals(1,countMemoryNodes(stop,StoreNode.class,new BitSet())); + assertEquals(2L,((Evaluator.Obj)Evaluator.evaluate(stop,0)).fields()[0]); + assertEquals(4L,((Evaluator.Obj)Evaluator.evaluate(stop,3)).fields()[0]); + } + + @Test public void testKeepReadsBeforeWrites() { + StopNode stop = new Parser(""" + struct S { int x; int y; } + S a = new S; S b = new S; + a.x=arg; b.x=arg+1; + S p=a; S q=b; + if (arg<0) { p=b; q=a; } + int v; + if (arg>1) { v=p.x; p.x=41; p.y=5; } + else { v=q.x; q.x=42; } + return v; + """).parse().iterate(); + assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet())); + assertEquals(-1L,Evaluator.evaluate(stop,-1)); + assertEquals(1L,Evaluator.evaluate(stop,0)); + assertEquals(3L,Evaluator.evaluate(stop,3)); + } + + private static int countMemoryNodes(Node n, Class<?> kind, BitSet seen) { + if (n==null || seen.get(n._nid)) return 0; + seen.set(n._nid); + int cnt=kind.isInstance(n) ? 1 : 0; + for (Node def : n._inputs) cnt+=countMemoryNodes(def,kind,seen); + return cnt; + } } 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..bddcc596 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; @@ -162,7 +162,7 @@ public class Evaluator { case null: return Status.FALLTHROUGH; case ReturnNode ret: - return val(ret.in(1)); + return val(ret.expr()); case IfNode ifn: block = block.next()[isTrue(val(ifn.in(1))) ? 0 : 1]; if (block == null) return Status.FALLTHROUGH; 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..dfdf74d5 --- /dev/null +++ b/chapter10b/README.md @@ -0,0 +1,198 @@ +# 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. + + + +## 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 `{ctrl, $mem, result}`. As with Start's outputs, control is +in slot 0 and memory in slot 1. 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. Phi +factoring can combine matching Loads or Stores as in Chapter 10a, but cannot +factor MemMerge or BulkMemPhi. Its new memory operands use precise MemPhis for +the operation's alias. Load safety checks only immediate memory users, rejecting +Stores, Phis, and MemMerges. A Phi or aggregate might lead to a later write; +we conservatively skip the rewrite instead of searching farther to save one Load. + +Factoring waits while a BulkMemPhi remains at the same Region. Bulk splitting +identifies parallel slices by Region and alias, whereas factoring a Store can +introduce a memory Phi *before* the Store at that same Region. Waiting avoids +mistaking that earlier memory for the aggregate's completed slice. + +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. Factoring also depends on user counts: +removing a use wakes recorded dependents even when the definition's type stays +unchanged. + +## 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-15 use this representation, including alias-specific load/store +anti-dependencies. Chapter 15 adds allocation with partial memory and typed +alias contents. Chapters 16-24 currently retain their older parser-managed +memory chains; forwarding through those snapshots is tracked in the +[backport queue](../docs/chapter-backports.md). + +## 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..5f096ef7 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; @@ -25,6 +26,24 @@ public class LoadNode extends MemOpNode { _declaredType = glb; } + @Override boolean canDrop(MemOpNode other, Node dep) { + return super.canDrop(other,dep) && _declaredType==((LoadNode)other)._declaredType && !clobbered(dep); + } + + // Check only immediate memory users. Stores clobber memory; Phis and + // aggregates might lead to a clobber, so stop rather than search further. + private boolean clobbered(Node dep) { + Node mem = mem(); + mem.addDepForwards(dep); + for( Node use : mem._outputs ) { + if( use==null ) continue; + use.addDepForwards(dep); + if( use instanceof StoreNode || use instanceof PhiNode || + use instanceof MemMergeNode ) return true; + } + return false; + } + @Override public String label() { return "Load"; } @@ -38,10 +57,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 +77,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 +94,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..44304bcb 100644 --- a/chapter10/src/main/java/com/seaofnodes/simple/node/MemOpNode.java +++ b/chapter10b/src/main/java/com/seaofnodes/simple/node/MemOpNode.java @@ -21,6 +21,13 @@ public abstract class MemOpNode extends Node { public Node mem() { return in(1); } public Node ptr() { return in(2); } + // Extra conditions for factoring matching memory operations through a Phi. + // The caller has already checked the opcode, input shape and control. + boolean canDrop(MemOpNode other, Node dep) { + ptr().addDep(dep); + return _alias==other._alias && err()==null; + } + @Override boolean eq(Node n) { MemOpNode mem = (MemOpNode)n; // Invariant 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..9a48c515 --- /dev/null +++ b/chapter10b/src/main/java/com/seaofnodes/simple/node/Node.java @@ -0,0 +1,657 @@ +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 implements Cloneable { + + /** + * 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 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 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 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)); + moveDepsToWorklist(); // User-count and anti-dependence queries can now change. + 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); + n.moveDepsToWorklist(); // Rewiring can change a dependent query without changing type. + 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"); } + + // Exact-class copy preserving operation attributes, with fresh identity + // and no edges, dependencies, or GVN membership. + public final Node copyEmpty() { + Node n; + try { n = (Node)clone(); } + catch( CloneNotSupportedException e ) { throw new AssertionError(e); } + n._nid = UNIQUE_ID++; + n._inputs = new ArrayList<>(); + n._outputs = new ArrayList<>(); + n._deps = null; + n._hash = 0; + return n; + } + + // 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..cdb4b7cc --- /dev/null +++ b/chapter10b/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -0,0 +1,207 @@ +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; + + // Phi(op(a,b,...),op(c,d,...)) -> op(Phi(a,c),Phi(b,d),...). + Node down; + if( same_op() && (down=drop_same_op())!=null ) return down; + + // 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() { + Node op = in(1); + if( op.isCFG() || op instanceof ConstantNode || op instanceof PhiNode || + op instanceof ProjNode || op instanceof NewNode || op instanceof ScopeNode || op instanceof MemMergeNode ) return false; + // A bulk Phi must split its aliases before factoring precise Stores. + if( this instanceof BulkMemPhiNode ) return false; + // Bulk splitting currently identifies parallel slices by Region/alias. + // Factoring can introduce another slice at a different memory point; + // wait until that Region's bulk partitioning has finished. + if( op instanceof MemOpNode ) + for( Node use : region()._outputs ) + if( use instanceof BulkMemPhiNode ) { + use.addDepForwards(this); + return false; + } + Node busy = null; + for( int i=1; i<nIns(); i++ ) { + Node n = in(i); + if( region().in(i).addDep(this)._type==Type.XCONTROL || + op.getClass()!=n.getClass() || n.nIns()!=op.nIns() || n.in(0)!=null || !op.eq(n) ) return false; + if( n instanceof MemOpNode mem && !mem.canDrop((MemOpNode)op,this) ) return false; + // Moving a Store must remove the old effect, not duplicate it. + // Allow one shared arm: the other arms disappear into the single + // factored operation, usually reducing the total operation count. + n.addDepForwards(this); + if( n.nOuts()>1 ) { + for( Node use : n._outputs ) + if( use!=null && use!=this ) use.addDepForwards(this); + if( n instanceof StoreNode || busy!=null ) return false; + busy=n; + } + for( int j=1; j<n.nIns(); j++ ) + if( n.in(j) instanceof ScopeNode || ((n.in(j)==null) != (op.in(j)==null)) ) return false; + } + return true; + } + + private Node drop_same_op() { + Node op = in(1), cp = op.copyEmpty(); + cp._type = null; + cp.addDef(null); + for( int j=1; j<op.nIns(); j++ ) { + Node x = op.in(j); + boolean different = false; + for( int i=2; i<nIns(); i++ ) + if( in(i).in(j)!=x ) different=true; + if( different ) { + Node[] ins = new Node[nIns()]; + ins[0] = region(); + Type t = Type.TOP; + for( int i=1; i<nIns(); i++ ) { + ins[i] = in(i).in(j); + t = t.meet(ins[i]._type); + } + PhiNode phi = j==1 && op instanceof MemOpNode mem + ? new MemPhiNode(_label,mem._alias,ins) + : PhiNode.make(_label,t.glb(),ins); + x = phi.peephole(); + } + cp.addDef(x); + } + // Factoring must not widen the result (e.g. correlated And operands). + if( cp.compute().isa(compute()) ) return cp; + cp.kill(); + return null; + } + + /** + * 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..a6b8d834 --- /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.addDepForwards(this).in(0)==p2.addDepForwards(this).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..96d58599 --- /dev/null +++ b/chapter10b/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -0,0 +1,42 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.*; + +import java.util.BitSet; + +/** Keeps all preceding memory effects and the return value alive: {ctrl, $mem, value}. */ +public class ReturnNode extends Node { + + public ReturnNode(Node ctrl, Node data, ScopeNode scope) { + super(ctrl, scope.lookup("$mem"), data); + } + + public Node ctrl() { return in(0); } + public Node mem () { return in(1); } + public Node expr() { return in(2); } + + @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 exposes the complete memory state. + return TypeTuple.make(ctrl()._type,TypeMem.BOT,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..600e9c73 --- /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 || 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..edc62f50 --- /dev/null +++ b/chapter10b/src/test/java/com/seaofnodes/simple/Chapter04Test.java @@ -0,0 +1,165 @@ +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); + assertEquals(1, ((ProjNode)ret.in(1))._idx); + assertTrue(ret.in(2) instanceof ProjNode); + assertEquals(2, ((ProjNode)ret.in(2))._idx); + } + + @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..cf7bd6cf --- /dev/null +++ b/chapter10b/src/test/java/com/seaofnodes/simple/Chapter05Test.java @@ -0,0 +1,228 @@ +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 testMergeSameCon() { + Parser parser = new Parser( +""" +int a=arg; +if( arg==1 ) + a=3; +else + a=3; +return a; +"""); + StopNode ret = parser.parse(); + assertEquals("return 3;", 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..8bf1debe --- /dev/null +++ b/chapter10b/src/test/java/com/seaofnodes/simple/Chapter10Test.java @@ -0,0 +1,583 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.node.Node; +import com.seaofnodes.simple.node.StopNode; +import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.node.LoadNode; +import com.seaofnodes.simple.node.StoreNode; +import com.seaofnodes.simple.evaluator.Evaluator; +import java.util.BitSet; +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()); + } + } + + @Test public void testKeepLoadsAtMerge() { + StopNode stop = new Parser(""" + struct S { int x; } + S a = new S; S b = new S; + a.x = arg; b.x = arg+1; + S p = a; S q = b; + if (arg<0) { p=b; q=a; } + int v; + if (arg>1) v=p.x; else v=q.x; + return v; + """).parse().iterate(); + // The one-step safety check stops at the memory aggregate. + assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet())); + assertEquals(-1L,Evaluator.evaluate(stop,-1)); + assertEquals(1L,Evaluator.evaluate(stop,0)); + assertEquals(3L,Evaluator.evaluate(stop,3)); + } + + @Test public void testDropStores() { + StopNode stop = new Parser(""" + struct S { int x; } + S s = new S; + if (arg) s.x=arg+1; else s.x=arg+2; + return s; + """).parse().iterate(); + // The initializing slice also remains live as the aggregate default. + assertEquals(2,countMemoryNodes(stop,StoreNode.class,new BitSet())); + assertEquals(2L,((Evaluator.Obj)Evaluator.evaluate(stop,0)).fields()[0]); + assertEquals(4L,((Evaluator.Obj)Evaluator.evaluate(stop,3)).fields()[0]); + } + + @Test public void testKeepReadsBeforeWrites() { + StopNode stop = new Parser(""" + struct S { int x; int y; } + S a = new S; S b = new S; + a.x=arg; b.x=arg+1; + S p=a; S q=b; + if (arg<0) { p=b; q=a; } + int v; + if (arg>1) { v=p.x; p.x=41; p.y=5; } + else { v=q.x; q.x=42; } + return v; + """).parse().iterate(); + assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet())); + assertEquals(-1L,Evaluator.evaluate(stop,-1)); + assertEquals(1L,Evaluator.evaluate(stop,0)); + assertEquals(3L,Evaluator.evaluate(stop,3)); + } + + private static int countMemoryNodes(Node n, Class<?> kind, BitSet seen) { + if (n==null || seen.get(n._nid)) return 0; + seen.set(n._nid); + int cnt=kind.isInstance(n) ? 1 : 0; + for (Node def : n._inputs) cnt+=countMemoryNodes(def,kind,seen); + return cnt; + } +} 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..ac7c5e09 --- /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.expr()); + 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..b91f4fd7 100644 --- a/chapter11/README.md +++ b/chapter11/README.md @@ -1,5 +1,20 @@ # Chapter 11: Global Code Motion +This chapter schedules the memory representation introduced in +[10b](../chapter10b/README.md): one parser-visible `$mem`, with `MemMerge`, +`MemPhi`, and `BulkMemPhi` recovering independent field chains. Start supplies +`{ctrl, $mem, arg}`. Return consumes `{ctrl, $mem, result}`, with the complete +memory aggregate in slot 1. Nodes consuming or producing both control and +memory consistently use slot 0 for control and slot 1 for memory. + +The new scheduling rule is to distinguish a memory aggregate from a memory +write. `MemMerge` gets a placement from its inputs and uses, but emits no heap +operation and creates no load/store anti-dependency. A Store or MemPhi can +constrain a Load only for the same alias; a BulkMemPhi can constrain it only +while it still covers that alias. Both kinds of memory Phi remain attached to +their Region, just like scalar Phis. + + # Table of Contents 1. [High Level Overview](#high-level-overview) @@ -16,7 +31,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 @@ -50,7 +65,7 @@ At its core, the scheduling algorithm works in two phases: * Schedule Early - in this phase, we do an upward DFS walk on the "inputs" of each Node, starting from the bottom (Stop). We schedule each data node to the first control block where they are dominated by their inputs. -* Schedule Late - in this phase we do a downward DFS walk on the "outputs" of each Node starting from the top (Start), and move data nodes to a block between the first block calculated above, +* Schedule Late - in this phase we work backwards from Stop, scheduling uses before definitions, and move data nodes to a block between the first block calculated above, and the last control block where they dominate all their uses. The placement is subject to the condition that it is in the shallowest loop nest possible, and is as control dependent as possible. Additionally, the placement of Load instructions must ensure correct ordering between Loads and Stores of the same memory location. @@ -72,6 +87,10 @@ return i; First lets look at the graph before scheduling. +These walkthrough diagrams show the precise `S.f` chain and the final +MemMerge consumed by Return. The unchanged default memory comes from Start; +any redundant bulk Phis have folded away. Arrows point from uses to definitions. +  Observe that @@ -363,133 +382,148 @@ A pre-condition of this is to ensure that infinite loops have been "fixed" as de The implementation of early schedule is shown below: ```java - // ------------------------------------------------------------------------ -private static void schedEarly() { - ArrayList<CFGNode> rpo = new ArrayList<>(); - BitSet visit = new BitSet(); - _rpo_cfg(Parser.START, visit, rpo); - // Reverse Post-Order on CFG - for( int j=rpo.size()-1; j>=0; j-- ) { - CFGNode cfg = rpo.get(j); - cfg.loopDepth(); - for( Node n : cfg._inputs ) - _schedEarly(n,visit); - // Strictly for dead infinite loops, we can have entire code blocks - // not reachable from below - so we reach down, from above, one - // step. Since _schedEarly modifies the output arrays, the normal - // region._outputs ArrayList iterator throws CME. The extra edges - // are always *added* after any Phis, so just walk the Phi prefix. - if( cfg instanceof RegionNode region ) { - int len = region.nOuts(); - for( int i=0; i<len; i++ ) - if( region.out(i) instanceof PhiNode phi ) - _schedEarly(phi,visit); + private static void schedEarly() { + ArrayList<CFGNode> rpo = new ArrayList<>(); + BitSet visit = new BitSet(); + _rpo_cfg(Parser.START, visit, rpo); + // Reverse Post-Order on CFG + for( int j=rpo.size()-1; j>=0; j-- ) { + CFGNode cfg = rpo.get(j); + cfg.loopDepth(); + for( Node n : cfg._inputs ) + _schedEarly(n,visit); + // In dead infinite loops, entire code blocks may be unreachable + // from below. Reach down from the CFG to their Phis so their + // inputs are scheduled too. + if( cfg instanceof RegionNode ) { + int len = cfg.nOuts(); + for( int i=0; i<len; i++ ) + if( cfg.out(i) instanceof PhiNode phi ) + _schedEarly(phi,visit); + } + } } - } -} -// Post-Order of CFG -private static void _rpo_cfg(Node n, BitSet visit, ArrayList<CFGNode> rpo) { - if( !(n instanceof CFGNode cfg) || visit.get(cfg._nid) ) - return; // Been there, done that - visit.set(cfg._nid); - for( Node use : cfg._outputs ) - _rpo_cfg(use,visit,rpo); - rpo.add(cfg); -} + // Post-Order of CFG + private static void _rpo_cfg(Node n, BitSet visit, ArrayList<CFGNode> rpo) { + if( !(n instanceof CFGNode cfg) || visit.get(cfg._nid) ) + return; // Been there, done that + visit.set(cfg._nid); + for( Node use : cfg._outputs ) + _rpo_cfg(use,visit,rpo); + rpo.add(cfg); + } -private static void _schedEarly(Node n, BitSet visit) { - if( n==null || visit.get(n._nid) ) return; // Been there, done that - visit.set(n._nid); - // Schedule not-pinned not-CFG inputs before self. Since skipping - // Pinned, this never walks the backedge of Phis (and thus spins around - // a data-only loop, eventually attempting relying on some pre-visited- - // not-post-visited data op with no scheduled control. - for( Node def : n._inputs ) - if( def!=null && !def.isPinned() ) - _schedEarly(def,visit); - // If not-pinned (e.g. constants, projections, phi) and not-CFG - if( !n.isPinned() ) { - // Schedule at deepest input - CFGNode early = Parser.START; // Maximally early, lowest idepth - for( int i=1; i<n.nIns(); i++ ) - if( n.in(i).cfg0().idepth() > early.idepth() ) - early = n.in(i).cfg0(); // Latest/deepest input - n.setDef(0,early); // First place this can go - } -} + private static void _schedEarly(Node n, BitSet visit) { + if( n==null || visit.get(n._nid) ) return; // Been there, done that + visit.set(n._nid); + // Schedule inputs first, except Phis: following their backedges would + // enter a data cycle before its control has been scheduled. + for( Node def : n._inputs ) + if( def!=null && !(def instanceof PhiNode) ) + _schedEarly(def,visit); + // An existing edge 0 already supplies control (or a Phi/Proj binding). + if( n.in(0)==null ) { + // Schedule at deepest input + CFGNode early = Parser.START; // Maximally early, lowest idepth + for( int i=1; i<n.nIns(); i++ ) + if( n.in(i)!=null && n.in(i).cfg0().idepth() > early.idepth() ) + early = n.in(i).cfg0(); // Latest/deepest input + n.setDef(0,early); // First place this can go + } + } ``` -* The early schedule populates the control input for data nodes - recall that this is null up to this point as data nodes are not attached to any control flow nodes until now. -* Most of the code above is about walking the graph in the right order. The actual computation of the early schedule is in these lines: - -```java - // If not-pinned (e.g. constants, projections, phi) and not-CFG - if( !n.isPinned() ) { - // Schedule at deepest input - CFGNode early = Parser.START; // Maximally early, lowest idepth - for( int i=1; i<n.nIns(); i++ ) - if( n.in(i).cfg0().idepth() > early.idepth() ) - early = n.in(i).cfg0(); // Latest/deepest input - n.setDef(0,early); // First place this can go - } -``` +Existing control and Phi/Proj bindings are preserved. Floating nodes receive +the deepest input block as their earliest placement. MemMerge follows this +same rule, ignoring absent entries in its sparse alias table. ## Late Schedule -During this phase we do a downward DFS walk on the "outputs" of each Node starting from the top (Start), and move data nodes to a block between the first block in the early schedule, -and the last control block where they dominate all their uses. The placement is subject to the condition that it is in the shallowest loop nest possible, and is as control dependent as possible. -Additionally, the placement of Load instructions must ensure correct ordering between Loads and Stores of the same memory location. - -The code for computing the late schedule is shown below. +Late scheduling starts at Stop and uses a worklist to place uses before their +definitions. CFG nodes, Phis, projections, and allocations provide fixed +placements. Other nodes wait until their uses have been scheduled; a Load also +waits for memory users that can overwrite or merge its alias. The chosen block +lies between the early placement and the common dominator of all uses, favoring +shallower loops and then deeper control flow. ```java - // ------------------------------------------------------------------------ - private static void schedLate(StartNode start) { + private static void schedLate( StopNode stop) { CFGNode[] late = new CFGNode[Node.UID()]; Node[] ns = new Node[Node.UID()]; - _schedLate(start,ns,late); + // Record Load NIDs at all their CFG block choices, then check against + // Store block choices to force a Load above an anti-dependent Store. + int[] anti = new int[Node.UID()]; + // Breadth-first scheduling + breadth(stop,ns,late,anti); + + // Copy the best placement choice into the control slot for( int i=0; i<late.length; i++ ) - if( ns[i] != null ) + if( ns[i] != null && !(ns[i] instanceof ProjNode) ) ns[i].setDef(0,late[i]); } - // Forwards post-order pass. Schedule all outputs first, then draw a - // idom-tree line from the LCA of uses to the early schedule. Schedule is - // legal anywhere on this line; pick the most control-dependent (largest - // idepth) in the shallowest loop nest. - private static void _schedLate(Node n, Node[] ns, CFGNode[] late) { - if( late[n._nid]!=null ) return; // Been there, done that - // These I know the late schedule of, and need to set early for loops - if( n instanceof CFGNode cfg ) late[n._nid] = cfg.blockHead() ? cfg : cfg.cfg(0); - if( n instanceof PhiNode phi ) late[n._nid] = phi.region(); - - // Walk Stores before Loads, so we can get the anti-deps right - for( Node use : n._outputs ) - if( isForwardsEdge(use,n) && - use._type instanceof TypeMem ) - _schedLate(use,ns,late); - // Walk everybody now - for( Node use : n._outputs ) - if( isForwardsEdge(use,n) ) - _schedLate(use,ns,late); - // Already implicitly scheduled - if( n.isPinned() ) return; - // Need to schedule n + private static void breadth(Node stop, Node[] ns, CFGNode[] late, int[] anti) { + // Things on the worklist have some (but perhaps not all) uses done. + WorkList<Node> work = new WorkList<>(); + work.push(stop); + Node n; + outer: + while( (n = work.pop()) != null ) { + assert late[n._nid]==null; // No double visit + // These I know the late schedule of, and need to set early for loops + if( n instanceof CFGNode cfg ) late[n._nid] = cfg.blockHead() ? cfg : cfg.cfg(0); + else if( n instanceof PhiNode phi ) late[n._nid] = phi.region(); + // These nodes have a fixed late placement at their original control. + else if( n instanceof ProjNode || n instanceof NewNode || n==Parser.ZERO ) late[n._nid] = n.cfg0(); + else { + + // All uses done? + for( Node use : n._outputs ) + if( use!=null && late[use._nid]==null ) + continue outer; // Nope, await all uses done + + // Loads need their memory inputs' uses also done + if( n instanceof LoadNode ld ) + for( Node memuse : ld.mem()._outputs ) + if( antiUse(ld,memuse) && late[memuse._nid]==null ) + continue outer; + + // All uses done, schedule + _doSchedLate(n,ns,late,anti); + } + + // A use just finished; reconsider its inputs and waiting loads, + // even when the shared memory input was already scheduled. + for( Node def : n._inputs ) { + if( def==null ) continue; + if( late[def._nid]==null ) work.push(def); + for( Node out : def._outputs ) + if( out instanceof LoadNode ld && late[ld._nid]==null ) + work.push(ld); + } + if( n instanceof LoopNode loop ) + for( Node phi : loop._outputs ) + if( phi instanceof PhiNode && late[phi._nid]==null ) + work.push(phi); + } + } + private static void _doSchedLate(Node n, Node[] ns, CFGNode[] late, int[] anti) { // Walk uses, gathering the LCA (Least Common Ancestor) of uses - CFGNode early = (CFGNode)n.in(0); + CFGNode early = n.in(0) instanceof CFGNode cfg ? cfg : n.in(0).cfg0(); assert early != null; CFGNode lca = null; for( Node use : n._outputs ) - lca = use_block(n,use, late).idom(lca); + if( use != null ) + lca = use_block(n,use, late).domLCA(lca); // Loads may need anti-dependencies, raising their LCA if( n instanceof LoadNode load ) - lca = find_anti_dep(lca,load,early,late); + lca = find_anti_dep(lca,load,early,late,anti); // Walk up from the LCA to the early, looking for best place. This is - // lowest execution frequency, approximated by least loop depth and + // the lowest execution frequency, approximated by least loop depth and // deepest control flow. CFGNode best = lca; lca = lca.idom(); // Already found best for starting LCA @@ -509,35 +543,41 @@ The code for computing the late schedule is shown below. CFGNode found=null; for( int i=1; i<phi.nIns(); i++ ) if( phi.in(i)==n ) - if( found==null ) found = phi.region().cfg(i); - else Utils.TODO(); // Can be more than once + found = phi.region().cfg(i).domLCA(found); // Can be more than one matching input. assert found!=null; return found; } + // Least loop depth first, then largest idepth private static boolean better( CFGNode lca, CFGNode best ) { - return lca._loopDepth < best._loopDepth || - (lca.idepth() > best.idepth() || best instanceof IfNode); - } - - // Skip iteration if a backedge - private static boolean isForwardsEdge(Node use, Node def) { - return use != null && def != null && - !(use.nIns()>2 && use.in(2)==def && (use instanceof LoopNode || (use instanceof PhiNode phi && phi.region() instanceof LoopNode))); + return lca.loopDepth() < best.loopDepth() || + lca instanceof NeverNode || + lca.idepth() > best.idepth() || + best instanceof IfNode; } ``` ## Inserting Anti Dependencies -To ensure that Loads and Stores to the same memory location are correctly ordered, we insert an edge from the Load to the Store as described below. +To ensure that Loads and Stores to the same memory location are correctly ordered, we insert an edge from the Store to the Load as described below: the Store must wait for the earlier Load. We call these edges anti-dependencies because they do not represent the Def-Use dependency that we normally capture in SoN, and are purely present as scheduling constraints. We compute anti-dependencies DURING running schedule late. This is because we rely on the early-schedule, and the late-schedule of the Load's uses (before scheduling the Load). -Looking backwards from the Load we follow the memory edge to the "memory definer" - a start-mem-projection, or a phi-mem, or a store. -We look forwards from the mem-def to all its mem-def users. There is always at least one; there may be many. These completely (all of it) and exactly (no doubling up) cover the forward memory space, sort of like a network flow problem - but we limit our analysis to same-alias, -because for example, a start-mem-proj produces all aliases. This gives us a candidate set of mem-defs; some of these need the anti-dependency on the Load, but not all. +Looking backwards from the Load, we follow its memory input to Start's memory +projection, a memory Phi, or a Store. Optimization has already selected the +Load's precise slice through any MemMerge. We inspect users of that memory +definition, keeping only Stores and memory Phis that cover the Load's alias. +Start's single memory projection may supply many aliases, so sharing a memory +input alone does not imply an anti-dependency. + +MemMerge only packages slices; it does not overwrite any of them. Treating it +as a write could make a Load wait for an aggregate which itself depends on the +Load's result. The same alias filter is used both when deciding whether a Load +is ready to schedule and when computing its anti-dependencies. A completed +memory user wakes waiting loads even if their shared memory definition has +already been scheduled. Since we're in the middle of "schedule late", we have already computed all the late schedules of a Load's users, and we have the Loads "Least Common Ancestor" of uses, the LCA or late position. We inspect the set of mem-defs that might impact the Load, and either add an anti-dependency from Store to Load, or raise the Loads effective LCA. @@ -547,38 +587,50 @@ For stores, we do the same - until/unless we find stores with the SAME block as The implementation is shown below. ```java - private static CFGNode find_anti_dep(CFGNode lca, LoadNode load, CFGNode early, CFGNode[] late) { + // Only a store or memory Phi covering this alias can constrain a load. + // MemMerge packages slices without overwriting them. + private static boolean antiUse(LoadNode load, Node use) { + return switch( use ) { + case StoreNode st -> st._alias==load._alias; + case MemPhiNode phi -> phi._alias==load._alias; + case BulkMemPhiNode phi -> !phi.isSplit(load._alias); + default -> false; + }; + } + + private static CFGNode find_anti_dep(CFGNode lca, LoadNode load, CFGNode early, CFGNode[] late, int[] anti) { // We could skip final-field loads here. // Walk LCA->early, flagging Load's block location choices for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() ) - cfg._anti = load._nid; + anti[cfg._nid] = load._nid; // Walk load->mem uses, looking for Stores causing an anti-dep for( Node mem : load.mem()._outputs ) { + if( !antiUse(load,mem) ) continue; switch( mem ) { case StoreNode st: - lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st); + lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st,anti); break; case PhiNode phi: // Repeat anti-dep for matching Phi inputs. // No anti-dep edges but may raise the LCA. for( int i=1; i<phi.nIns(); i++ ) if( phi.in(i)==load.mem() ) - lca = anti_dep(load,phi.region().cfg(i),load.mem().cfg0(),lca,null); + lca = anti_dep(load,phi.region().cfg(i),load.mem().cfg0(),lca,null,anti); break; - case LoadNode ld: break; // Loads do not cause anti-deps on other loads - case ReturnNode ret: break; // Load must already be ahead of Return default: throw Utils.TODO(); } } return lca; } - private static CFGNode anti_dep( LoadNode load, CFGNode stblk, CFGNode defblk, CFGNode lca, Node st ) { - // Walk store blocks "reach" from its scheduled location to its earliest + // + private static CFGNode anti_dep( LoadNode load, CFGNode stblk, CFGNode defblk, CFGNode lca, Node st, int[] anti ) { + // Preserve the full store range for the earlier evaluator scheduler. + // It places nodes independently of GCM and may hoist this store. for( ; stblk != defblk.idom(); stblk = stblk.idom() ) { // Store and Load overlap, need anti-dependence - if( stblk._anti==load._nid ) { - lca = stblk.idom(lca); // Raise Loads LCA + if( anti[stblk._nid]==load._nid ) { + lca = stblk.domLCA(lca); // Raise Loads LCA if( lca == stblk && st != null && Utils.find(st._inputs,load) == -1 ) // And if something moved, st.addDef(load); // Add anti-dep as well return lca; // Cap this stores' anti-dep to here diff --git a/chapter11/docs/Makefile b/chapter11/docs/Makefile index 12659b8f..055882b2 100644 --- a/chapter11/docs/Makefile +++ b/chapter11/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/chapter11/docs/graph1.gv b/chapter11/docs/graph1.gv index 74c54fdb..3554a572 100644 --- a/chapter11/docs/graph1.gv +++ b/chapter11/docs/graph1.gv @@ -18,11 +18,12 @@ return i; <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></TR> + <TR><TD PORT="p0" BGCOLOR="yellow">$ctrl</TD><TD PORT="p1">$mem</TD><TD PORT="p2">arg</TD></TR> </TABLE> </TD></TR> </TABLE>> ]; + MemMerge [ shape=box label="MemMerge" ]; Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; Con_4 [ label="#0" ]; new9 [ label="new S" ]; @@ -44,14 +45,14 @@ return i; Con_21 [ label="#1" ]; Store22 [ label=".f =" ]; Region23 [ shape=box style=filled fillcolor=yellow label="Region" ]; - Phi_224 [ style=filled fillcolor=lightyellow label="φ_$2" ]; + Phi_224 [ style=filled fillcolor=lightyellow label="MemPhi_2" ]; Return25 [ shape=box style=filled fillcolor=yellow label="Return" ]; { rank=same; Region23;Phi_224;} } edge [ fontname=Helvetica, fontsize=8 ]; Stop3 -> Return25[taillabel=0 color=red]; new9 -> Start2:p0[taillabel=0 color=green]; - Store12 -> Start2:p2[taillabel=1 color=blue]; + Store12 -> Start2:p1[taillabel=1 color=blue]; Store12 -> new9[taillabel=2]; Store12 -> Con_11[taillabel=3]; new13 -> Start2:p0[taillabel=0 color=green]; @@ -61,7 +62,7 @@ return i; Load16 -> Store14[taillabel=1 color=blue]; Load16 -> new9[taillabel=2]; If17 -> Start2:p0[taillabel=0 color=red]; - If17 -> Start2:p1[taillabel=1]; + If17 -> Start2:p2[taillabel=1]; Store22 -> Store14[taillabel=1 color=blue]; Store22 -> new9[taillabel=2]; Store22 -> Con_21[taillabel=3]; @@ -71,6 +72,8 @@ return i; Phi_224 -> Store22[taillabel=1 color=blue]; Phi_224 -> Store14[taillabel=2 color=blue constraint=false]; Return25 -> Region23[taillabel=0 color=red]; - Return25 -> Load16[taillabel=1]; - Return25 -> Phi_224[taillabel=2 color=blue]; + Return25 -> Load16[taillabel=2]; + Return25 -> MemMerge[taillabel=1 color=blue]; + MemMerge -> Start2:p1[taillabel=1 color=blue]; + MemMerge -> Phi_224[taillabel=2 color=blue]; } diff --git a/chapter11/docs/graph1.svg b/chapter11/docs/graph1.svg index 592db961..422d790e 100644 --- a/chapter11/docs/graph1.svg +++ b/chapter11/docs/graph1.svg @@ -1,285 +1,306 @@ <?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) +<!-- Generated by graphviz version 12.2.0 (20241103.1931) --> <!-- Title: chapter11 Pages: 1 --> -<svg width="336pt" height="636pt" - viewBox="0.00 0.00 336.00 636.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 632)"> +<svg width="346pt" height="714pt" + viewBox="0.00 0.00 346.00 714.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 710)"> <title>chapter11</title> -<polygon fill="white" stroke="transparent" points="-4,4 -4,-632 332,-632 332,4 -4,4"/> +<polygon fill="white" stroke="none" points="-4,4 -4,-710 342,-710 342,4 -4,4"/> <g id="clust1" class="cluster"> <title>cluster_Nodes</title> -<polygon fill="none" stroke="black" points="8,-8 8,-620 320,-620 320,-8 8,-8"/> +<polygon fill="none" stroke="black" points="8,-8 8,-698 330,-698 330,-8 8,-8"/> </g> <!-- Start2 --> <g id="node1" class="node"> <title>Start2</title> -<polygon fill="yellow" stroke="transparent" points="198,-583 198,-608 304,-608 304,-583 198,-583"/> -<polygon fill="none" stroke="black" points="198,-583 198,-608 304,-608 304,-583 198,-583"/> -<text text-anchor="start" x="232.5" y="-591.8" font-family="Times,serif" font-size="14.00">Start</text> -<polygon fill="none" stroke="black" points="198,-552 198,-583 304,-583 304,-552 198,-552"/> -<polygon fill="yellow" stroke="transparent" points="203,-557 203,-578 243,-578 243,-557 203,-557"/> -<polygon fill="none" stroke="black" points="203,-557 203,-578 243,-578 243,-557 203,-557"/> -<text text-anchor="start" x="206" y="-563.8" font-family="Times,serif" font-size="14.00">$ctrl</text> -<polygon fill="none" stroke="black" points="243,-557 243,-578 274,-578 274,-557 243,-557"/> -<text text-anchor="start" x="246" y="-563.8" font-family="Times,serif" font-size="14.00">arg</text> -<polygon fill="none" stroke="black" points="274,-557 274,-578 299,-578 299,-557 274,-557"/> -<text text-anchor="start" x="277" y="-563.8" font-family="Times,serif" font-size="14.00">$2</text> +<polygon fill="yellow" stroke="none" points="207.38,-659.5 207.38,-686 312.62,-686 312.62,-659.5 207.38,-659.5"/> +<polygon fill="none" stroke="black" points="207.38,-659.5 207.38,-686 312.62,-686 312.62,-659.5 207.38,-659.5"/> +<text text-anchor="start" x="247.25" y="-667.7" font-family="Times New Roman,serif" font-size="14.00">Start</text> +<polygon fill="none" stroke="black" points="207.38,-627 207.38,-659.5 312.62,-659.5 312.62,-627 207.38,-627"/> +<polygon fill="yellow" stroke="none" points="212.38,-632 212.38,-654.5 243.12,-654.5 243.12,-632 212.38,-632"/> +<polygon fill="none" stroke="black" points="212.38,-632 212.38,-654.5 243.12,-654.5 243.12,-632 212.38,-632"/> +<text text-anchor="start" x="215.38" y="-638.2" font-family="Times New Roman,serif" font-size="14.00">$ctrl</text> +<polygon fill="none" stroke="black" points="243.12,-632 243.12,-654.5 284.38,-654.5 284.38,-632 243.12,-632"/> +<text text-anchor="start" x="246.12" y="-638.2" font-family="Times New Roman,serif" font-size="14.00">$mem</text> +<polygon fill="none" stroke="black" points="284.38,-632 284.38,-654.5 307.62,-654.5 307.62,-632 284.38,-632"/> +<text text-anchor="start" x="287.38" y="-638.2" font-family="Times New Roman,serif" font-size="14.00">arg</text> +</g> +<!-- MemMerge --> +<g id="node2" class="node"> +<title>MemMerge</title> +<polygon fill="none" stroke="black" points="195,-196 113,-196 113,-160 195,-160 195,-196"/> +<text text-anchor="middle" x="154" y="-172.95" font-family="Times New Roman,serif" font-size="14.00">MemMerge</text> +</g> +<!-- MemMerge->Start2 --> +<g id="edge25" class="edge"> +<title>MemMerge->Start2:p1</title> +<path fill="none" stroke="blue" d="M174.24,-196.05C184.06,-205.56 195.02,-218.24 201,-232 238.55,-318.39 196.42,-352.07 221,-443 234.8,-494.06 259.62,-499.34 271,-551 277.81,-581.92 266.84,-592.84 264.27,-619.8"/> +<polygon fill="blue" stroke="blue" points="260.79,-619.34 263.82,-629.49 267.78,-619.66 260.79,-619.34"/> +<text text-anchor="middle" x="171.99" y="-198.2" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Phi_224 --> +<g id="node15" class="node"> +<title>Phi_224</title> +<ellipse fill="lightyellow" stroke="black" cx="139" cy="-250" rx="52.89" ry="18"/> +<text text-anchor="middle" x="139" y="-244.95" font-family="Times New Roman,serif" font-size="14.00">MemPhi_2</text> +</g> +<!-- MemMerge->Phi_224 --> +<g id="edge26" class="edge"> +<title>MemMerge->Phi_224</title> +<path fill="none" stroke="blue" d="M150.29,-196.3C148.71,-203.68 146.83,-212.48 145.05,-220.75"/> +<polygon fill="blue" stroke="blue" points="141.66,-219.9 142.98,-230.42 148.5,-221.37 141.66,-219.9"/> +<text text-anchor="middle" x="148.04" y="-198.45" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Stop3 --> -<g id="node2" class="node"> +<g id="node3" class="node"> <title>Stop3</title> -<polygon fill="yellow" stroke="black" points="210,-52 156,-52 156,-16 210,-16 210,-52"/> -<text text-anchor="middle" x="183" y="-30.3" font-family="Times,serif" font-size="14.00">Stop</text> +<polygon fill="yellow" stroke="black" points="181,-52 127,-52 127,-16 181,-16 181,-52"/> +<text text-anchor="middle" x="154" y="-28.95" font-family="Times New Roman,serif" font-size="14.00">Stop</text> </g> <!-- Return25 --> -<g id="node15" class="node"> +<g id="node16" class="node"> <title>Return25</title> -<polygon fill="yellow" stroke="black" points="216,-124 150,-124 150,-88 216,-88 216,-124"/> -<text text-anchor="middle" x="183" y="-102.3" font-family="Times,serif" font-size="14.00">Return</text> +<polygon fill="yellow" stroke="black" points="181,-124 127,-124 127,-88 181,-88 181,-124"/> +<text text-anchor="middle" x="154" y="-100.95" font-family="Times New Roman,serif" font-size="14.00">Return</text> </g> <!-- Stop3->Return25 --> <g id="edge1" class="edge"> <title>Stop3->Return25</title> -<path fill="none" stroke="red" d="M183,-52.3C183,-60.02 183,-69.29 183,-77.89"/> -<polygon fill="red" stroke="red" points="179.5,-77.9 183,-87.9 186.5,-77.9 179.5,-77.9"/> -<text text-anchor="middle" x="180" y="-54.9" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="red" d="M154,-52.3C154,-59.59 154,-68.27 154,-76.46"/> +<polygon fill="red" stroke="red" points="150.5,-76.38 154,-86.38 157.5,-76.38 150.5,-76.38"/> +<text text-anchor="middle" x="151.75" y="-54.45" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Con_4 --> -<g id="node3" class="node"> +<g id="node4" class="node"> <title>Con_4</title> -<ellipse fill="none" stroke="black" cx="137" cy="-422" rx="27" ry="18"/> -<text text-anchor="middle" x="137" y="-418.3" font-family="Times,serif" font-size="14.00">#0</text> +<ellipse fill="none" stroke="black" cx="93" cy="-497" rx="27" ry="18"/> +<text text-anchor="middle" x="93" y="-491.95" font-family="Times New Roman,serif" font-size="14.00">#0</text> </g> <!-- new9 --> -<g id="node4" class="node"> +<g id="node5" class="node"> <title>new9</title> -<ellipse fill="none" stroke="black" cx="151" cy="-494" rx="38.99" ry="18"/> -<text text-anchor="middle" x="151" y="-490.3" font-family="Times,serif" font-size="14.00">new S</text> +<ellipse fill="none" stroke="black" cx="50" cy="-569" rx="33.95" ry="18"/> +<text text-anchor="middle" x="50" y="-563.95" font-family="Times New Roman,serif" font-size="14.00">new S</text> </g> <!-- new9->Start2 --> <g id="edge2" class="edge"> <title>new9->Start2:p0</title> -<path fill="none" stroke="green" d="M155.04,-512.06C160.38,-530.53 171.62,-558.28 191.99,-565.33"/> -<polygon fill="green" stroke="green" points="191.56,-568.8 202,-567 192.71,-561.9 191.56,-568.8"/> -<text text-anchor="middle" x="152.04" y="-514.66" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="green" d="M68.15,-584.47C94.9,-604.4 147.46,-638.35 200.14,-642.77"/> +<polygon fill="green" stroke="green" points="199.72,-646.26 209.86,-643.19 200.02,-639.26 199.72,-646.26"/> +<text text-anchor="middle" x="70.4" y="-586.62" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Con_11 --> -<g id="node5" class="node"> +<g id="node6" class="node"> <title>Con_11</title> -<ellipse fill="none" stroke="black" cx="255" cy="-494" rx="27" ry="18"/> -<text text-anchor="middle" x="255" y="-490.3" font-family="Times,serif" font-size="14.00">#2</text> +<ellipse fill="none" stroke="black" cx="149" cy="-569" rx="27" ry="18"/> +<text text-anchor="middle" x="149" y="-563.95" font-family="Times New Roman,serif" font-size="14.00">#2</text> </g> <!-- Store12 --> -<g id="node6" class="node"> +<g id="node7" class="node"> <title>Store12</title> -<ellipse fill="none" stroke="black" cx="230" cy="-422" rx="27.9" ry="18"/> -<text text-anchor="middle" x="230" y="-418.3" font-family="Times,serif" font-size="14.00">.f =</text> +<ellipse fill="none" stroke="black" cx="165" cy="-497" rx="27" ry="18"/> +<text text-anchor="middle" x="165" y="-491.95" font-family="Times New Roman,serif" font-size="14.00">.f =</text> </g> <!-- Store12->Start2 --> <g id="edge3" class="edge"> -<title>Store12->Start2:p2</title> -<path fill="none" stroke="blue" d="M222.43,-439.51C215.04,-458.25 206.57,-489.16 219,-512 234.25,-540.04 275.92,-524.64 285.17,-545.89"/> -<polygon fill="blue" stroke="blue" points="281.77,-546.79 287,-556 288.66,-545.54 281.77,-546.79"/> -<text text-anchor="middle" x="219.43" y="-442.11" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<title>Store12->Start2:p1</title> +<path fill="none" stroke="blue" d="M147.77,-510.96C126.13,-528.81 94,-561.66 113,-587 152.26,-639.35 250.4,-570.87 262.52,-619.62"/> +<polygon fill="blue" stroke="blue" points="259.03,-619.93 263.59,-629.5 265.99,-619.18 259.03,-619.93"/> +<text text-anchor="middle" x="145.52" y="-513.11" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Store12->new9 --> <g id="edge4" class="edge"> <title>Store12->new9</title> -<path fill="none" stroke="black" d="M214.01,-437.17C203.14,-446.8 188.53,-459.75 176.11,-470.75"/> -<polygon fill="black" stroke="black" points="173.5,-468.39 168.33,-477.64 178.14,-473.63 173.5,-468.39"/> -<text text-anchor="middle" x="211.01" y="-439.77" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="black" d="M145.49,-509.87C127.95,-520.55 101.87,-536.43 81.39,-548.89"/> +<polygon fill="black" stroke="black" points="79.58,-545.9 72.86,-554.09 83.22,-551.88 79.58,-545.9"/> +<text text-anchor="middle" x="147.74" y="-502.27" 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="M236.05,-439.95C238.91,-447.94 242.38,-457.67 245.57,-466.6"/> -<polygon fill="black" stroke="black" points="242.34,-467.97 249,-476.21 248.94,-465.62 242.34,-467.97"/> -<text text-anchor="middle" x="233.05" y="-442.55" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> +<path fill="none" stroke="black" d="M161.04,-515.3C159.36,-522.68 157.35,-531.48 155.46,-539.75"/> +<polygon fill="black" stroke="black" points="152.06,-538.89 153.25,-549.42 158.89,-540.45 152.06,-538.89"/> +<text text-anchor="middle" x="158.79" y="-517.45" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> </g> <!-- new13 --> -<g id="node7" class="node"> +<g id="node8" class="node"> <title>new13</title> -<ellipse fill="none" stroke="black" cx="55" cy="-494" rx="38.99" ry="18"/> -<text text-anchor="middle" x="55" y="-490.3" font-family="Times,serif" font-size="14.00">new S</text> +<ellipse fill="none" stroke="black" cx="228" cy="-569" rx="33.95" ry="18"/> +<text text-anchor="middle" x="228" y="-563.95" font-family="Times New Roman,serif" font-size="14.00">new S</text> </g> <!-- new13->Start2 --> <g id="edge6" class="edge"> <title>new13->Start2:p0</title> -<path fill="none" stroke="green" d="M72.65,-510.27C97.21,-530 144.07,-562.34 191.81,-566.54"/> -<polygon fill="green" stroke="green" points="191.85,-570.05 202,-567 192.17,-563.06 191.85,-570.05"/> -<text text-anchor="middle" x="69.65" y="-512.87" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="green" d="M227.89,-587.35C227.84,-596.51 227.79,-608.2 227.76,-619.69"/> +<polygon fill="green" stroke="green" points="224.26,-619.48 227.75,-629.49 231.26,-619.49 224.26,-619.48"/> +<text text-anchor="middle" x="225.64" y="-589.5" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Store14 --> -<g id="node8" class="node"> +<g id="node9" class="node"> <title>Store14</title> -<ellipse fill="none" stroke="black" cx="136" cy="-336" rx="27.9" ry="18"/> -<text text-anchor="middle" x="136" y="-332.3" font-family="Times,serif" font-size="14.00">.f =</text> +<ellipse fill="none" stroke="black" cx="93" cy="-409.5" rx="27" ry="18"/> +<text text-anchor="middle" x="93" y="-404.45" font-family="Times New Roman,serif" font-size="14.00">.f =</text> </g> <!-- Store14->Con_4 --> <g id="edge9" class="edge"> <title>Store14->Con_4</title> -<path fill="none" stroke="black" d="M136.21,-354.4C136.34,-365.74 136.52,-380.77 136.67,-393.68"/> -<polygon fill="black" stroke="black" points="133.18,-393.95 136.8,-403.9 140.18,-393.86 133.18,-393.95"/> -<text text-anchor="middle" x="133.21" y="-357" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> +<path fill="none" stroke="black" d="M93,-427.8C93,-439.16 93,-454.28 93,-467.4"/> +<polygon fill="black" stroke="black" points="89.5,-467.21 93,-477.21 96.5,-467.21 89.5,-467.21"/> +<text text-anchor="middle" x="90.75" y="-429.95" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> </g> <!-- Store14->Store12 --> <g id="edge7" class="edge"> <title>Store14->Store12</title> -<path fill="none" stroke="blue" d="M151.95,-351.25C166.89,-364.6 189.42,-384.73 206.44,-399.94"/> -<polygon fill="blue" stroke="blue" points="204.5,-402.91 214.29,-406.96 209.17,-397.69 204.5,-402.91"/> -<text text-anchor="middle" x="148.95" y="-353.85" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="blue" d="M105.87,-425.79C116.73,-438.67 132.44,-457.34 144.94,-472.18"/> +<polygon fill="blue" stroke="blue" points="142.15,-474.29 151.26,-479.69 147.5,-469.79 142.15,-474.29"/> +<text text-anchor="middle" x="103.62" y="-427.94" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Store14->new13 --> <g id="edge8" class="edge"> <title>Store14->new13</title> -<path fill="none" stroke="black" d="M127.49,-353.24C120.39,-366.8 110.02,-386.65 101,-404 89.97,-425.23 77.52,-449.33 68.35,-467.1"/> -<polygon fill="black" stroke="black" points="65.1,-465.77 63.63,-476.26 71.32,-468.97 65.1,-465.77"/> -<text text-anchor="middle" x="124.49" y="-355.84" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="black" d="M107.31,-425.06C113.63,-431.04 121.35,-437.78 129,-443 158.55,-463.17 177.93,-451.66 201,-479 215.33,-495.99 222.06,-520.44 225.21,-539.41"/> +<polygon fill="black" stroke="black" points="221.73,-539.76 226.58,-549.18 228.66,-538.79 221.73,-539.76"/> +<text text-anchor="middle" x="109.56" y="-427.21" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Load16 --> -<g id="node9" class="node"> +<g id="node10" class="node"> <title>Load16</title> -<ellipse fill="none" stroke="black" cx="183" cy="-250" rx="27" ry="18"/> -<text text-anchor="middle" x="183" y="-246.3" font-family="Times,serif" font-size="14.00">.f</text> +<ellipse fill="none" stroke="black" cx="43" cy="-322" rx="27" ry="18"/> +<text text-anchor="middle" x="43" y="-316.95" font-family="Times New Roman,serif" font-size="14.00">.f</text> </g> <!-- Load16->new9 --> <g id="edge11" class="edge"> <title>Load16->new9</title> -<path fill="none" stroke="black" d="M184.02,-268.21C185.58,-301.74 187.07,-378 173,-440 170.94,-449.08 167.45,-458.59 163.85,-467.01"/> -<polygon fill="black" stroke="black" points="160.59,-465.75 159.66,-476.3 166.96,-468.63 160.59,-465.75"/> -<text text-anchor="middle" x="181.02" y="-270.81" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="black" d="M47,-410.5C36.94,-454.72 41.17,-507.73 45.4,-539.6"/> +<polygon fill="black" stroke="black" points="41.89,-539.81 46.78,-549.21 48.82,-538.82 41.89,-539.81"/> +<path fill="none" stroke="black" d="M46.72,-340.02C49.87,-357.49 53,-385.34 47,-408.5"/> +<text text-anchor="middle" x="44.75" y="-402.9" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Load16->Store14 --> <g id="edge10" class="edge"> <title>Load16->Store14</title> -<path fill="none" stroke="blue" d="M173.94,-267.2C167.18,-279.28 157.81,-296.02 150.08,-309.84"/> -<polygon fill="blue" stroke="blue" points="146.86,-308.42 145.03,-318.85 152.97,-311.84 146.86,-308.42"/> -<text text-anchor="middle" x="170.94" y="-269.8" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="blue" d="M52.4,-339.08C59.55,-351.3 69.53,-368.37 77.81,-382.52"/> +<polygon fill="blue" stroke="blue" points="74.63,-384.03 82.7,-390.89 80.68,-380.49 74.63,-384.03"/> +<text text-anchor="middle" x="50.15" y="-341.23" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- If17 --> -<g id="node10" class="node"> +<g id="node11" class="node"> <title>If17</title> -<polygon fill="yellow" stroke="transparent" points="210,-339 210,-364 304,-364 304,-339 210,-339"/> -<polygon fill="none" stroke="black" points="210,-339 210,-364 304,-364 304,-339 210,-339"/> -<text text-anchor="start" x="251.5" y="-347.8" font-family="Times,serif" font-size="14.00">If</text> -<polygon fill="none" stroke="black" points="210,-308 210,-339 304,-339 304,-308 210,-308"/> -<polygon fill="yellow" stroke="transparent" points="215,-313 215,-334 255,-334 255,-313 215,-313"/> -<polygon fill="none" stroke="black" points="215,-313 215,-334 255,-334 255,-313 215,-313"/> -<text text-anchor="start" x="218" y="-319.8" font-family="Times,serif" font-size="14.00">True</text> -<polygon fill="yellow" stroke="transparent" points="255,-313 255,-334 299,-334 299,-313 255,-313"/> -<polygon fill="none" stroke="black" points="255,-313 255,-334 299,-334 299,-313 255,-313"/> -<text text-anchor="start" x="258" y="-319.8" font-family="Times,serif" font-size="14.00">False</text> +<polygon fill="yellow" stroke="none" points="238,-412.5 238,-439 314,-439 314,-412.5 238,-412.5"/> +<polygon fill="none" stroke="black" points="238,-412.5 238,-439 314,-439 314,-412.5 238,-412.5"/> +<text text-anchor="start" x="271.5" y="-420.7" font-family="Times New Roman,serif" font-size="14.00">If</text> +<polygon fill="none" stroke="black" points="238,-380 238,-412.5 314,-412.5 314,-380 238,-380"/> +<polygon fill="yellow" stroke="none" points="243,-385 243,-407.5 274.5,-407.5 274.5,-385 243,-385"/> +<polygon fill="none" stroke="black" points="243,-385 243,-407.5 274.5,-407.5 274.5,-385 243,-385"/> +<text text-anchor="start" x="246" y="-391.2" font-family="Times New Roman,serif" font-size="14.00">True</text> +<polygon fill="yellow" stroke="none" points="274.5,-385 274.5,-407.5 309,-407.5 309,-385 274.5,-385"/> +<polygon fill="none" stroke="black" points="274.5,-385 274.5,-407.5 309,-407.5 309,-385 274.5,-385"/> +<text text-anchor="start" x="277.5" y="-391.2" font-family="Times New Roman,serif" font-size="14.00">False</text> </g> <!-- If17->Start2 --> <g id="edge12" class="edge"> <title>If17->Start2:p0</title> -<path fill="none" stroke="red" d="M274.94,-368.3C293.31,-404.87 315.92,-466.19 291,-512 275.75,-540.04 234.08,-524.64 224.83,-545.89"/> -<polygon fill="red" stroke="red" points="221.34,-545.54 223,-556 228.23,-546.79 221.34,-545.54"/> -<text text-anchor="middle" x="271.94" y="-370.9" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="red" d="M301,-498C306.79,-539.34 292.85,-551.43 271,-587 261.11,-603.09 244.32,-604.88 234.83,-613.4"/> +<polygon fill="red" stroke="red" points="231.88,-611.53 229.53,-621.86 237.81,-615.25 231.88,-611.53"/> +<path fill="none" stroke="red" d="M287.84,-442.9C293,-458.69 298.44,-478.09 301,-496"/> +<text text-anchor="middle" x="298.75" y="-490.4" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- If17->Start2 --> +<g id="edge13" class="edge"> +<title>If17->Start2:p2</title> +<path fill="none" stroke="black" d="M301,-498C306.61,-537.22 310.42,-547.63 306,-587 304.69,-598.67 301.49,-604.88 299.06,-612.05"/> +<polygon fill="black" stroke="black" points="295.7,-611.02 296.82,-621.55 302.52,-612.62 295.7,-611.02"/> +<text text-anchor="middle" x="298.75" y="-500.15" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Con_21 --> -<g id="node11" class="node"> +<g id="node12" class="node"> <title>Con_21</title> -<ellipse fill="none" stroke="black" cx="43" cy="-336" rx="27" ry="18"/> -<text text-anchor="middle" x="43" y="-332.3" font-family="Times,serif" font-size="14.00">#1</text> +<ellipse fill="none" stroke="black" cx="165" cy="-409.5" rx="27" ry="18"/> +<text text-anchor="middle" x="165" y="-404.45" font-family="Times New Roman,serif" font-size="14.00">#1</text> </g> <!-- Store22 --> -<g id="node12" class="node"> +<g id="node13" class="node"> <title>Store22</title> -<ellipse fill="none" stroke="black" cx="99" cy="-250" rx="27.9" ry="18"/> -<text text-anchor="middle" x="99" y="-246.3" font-family="Times,serif" font-size="14.00">.f =</text> +<ellipse fill="none" stroke="black" cx="135" cy="-322" rx="27" ry="18"/> +<text text-anchor="middle" x="135" y="-316.95" font-family="Times New Roman,serif" font-size="14.00">.f =</text> </g> <!-- Store22->new9 --> <g id="edge15" class="edge"> <title>Store22->new9</title> -<path fill="none" stroke="black" d="M95.12,-268.01C88.19,-302.16 76.55,-380.87 101,-440 105.95,-451.96 114.93,-462.83 123.93,-471.57"/> -<polygon fill="black" stroke="black" points="121.66,-474.24 131.41,-478.38 126.37,-469.06 121.66,-474.24"/> -<text text-anchor="middle" x="92.12" y="-270.61" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="black" d="M113.11,-332.75C95.54,-341.64 71.49,-356.38 57,-376 48.02,-388.16 50.79,-393.87 47,-408.5"/> +<text text-anchor="middle" x="115.36" y="-334.9" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Store22->Store14 --> <g id="edge14" class="edge"> <title>Store22->Store14</title> -<path fill="none" stroke="blue" d="M106.31,-267.6C111.53,-279.45 118.65,-295.62 124.61,-309.15"/> -<polygon fill="blue" stroke="blue" points="121.47,-310.7 128.71,-318.44 127.88,-307.88 121.47,-310.7"/> -<text text-anchor="middle" x="103.31" y="-270.2" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="blue" d="M126.9,-339.49C120.98,-351.55 112.81,-368.17 105.98,-382.08"/> +<polygon fill="blue" stroke="blue" points="103.01,-380.19 101.74,-390.71 109.29,-383.27 103.01,-380.19"/> +<text text-anchor="middle" x="124.65" y="-341.64" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Store22->Con_21 --> <g id="edge16" class="edge"> <title>Store22->Con_21</title> -<path fill="none" stroke="black" d="M88.47,-266.8C80.25,-279.12 68.7,-296.45 59.3,-310.55"/> -<polygon fill="black" stroke="black" points="56.12,-309.01 53.49,-319.27 61.95,-312.89 56.12,-309.01"/> -<text text-anchor="middle" x="85.47" y="-269.4" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> +<path fill="none" stroke="black" d="M140.93,-339.89C145.04,-351.6 150.6,-367.46 155.34,-380.98"/> +<polygon fill="black" stroke="black" points="151.97,-381.94 158.58,-390.21 158.58,-379.62 151.97,-381.94"/> +<text text-anchor="middle" x="138.68" y="-342.04" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> </g> <!-- Region23 --> -<g id="node13" class="node"> +<g id="node14" class="node"> <title>Region23</title> -<polygon fill="yellow" stroke="black" points="268,-196 202,-196 202,-160 268,-160 268,-196"/> -<text text-anchor="middle" x="235" y="-174.3" font-family="Times,serif" font-size="14.00">Region</text> +<polygon fill="yellow" stroke="black" points="285.5,-268 230.5,-268 230.5,-232 285.5,-232 285.5,-268"/> +<text text-anchor="middle" x="258" y="-244.95" font-family="Times New Roman,serif" font-size="14.00">Region</text> </g> <!-- Region23->If17 --> <g id="edge17" class="edge"> <title>Region23->If17:p0</title> -<path fill="none" stroke="red" d="M235,-196.22C235,-219.49 235,-262.58 235,-301.64"/> -<polygon fill="red" stroke="red" points="231.5,-302 235,-312 238.5,-302 231.5,-302"/> -<text text-anchor="middle" x="232" y="-198.82" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> -</g> -<!-- Region23->If17 --> -<g id="edge18" class="edge"> -<title>Region23->If17:p1</title> -<path fill="none" stroke="red" d="M244.11,-196.1C255.5,-218.96 273.84,-261.33 276.64,-301.77"/> -<polygon fill="red" stroke="red" points="273.15,-302.13 277,-312 280.14,-301.88 273.15,-302.13"/> -<text text-anchor="middle" x="241.11" y="-198.7" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> -</g> -<!-- Phi_224 --> -<g id="node14" class="node"> -<title>Phi_224</title> -<ellipse fill="lightyellow" stroke="black" cx="131" cy="-178" rx="33.29" ry="18"/> -<text text-anchor="middle" x="131" y="-174.3" font-family="Times,serif" font-size="14.00">φ_$2</text> +<path fill="none" stroke="red" d="M258.16,-268.22C258.36,-291.29 258.68,-333.85 258.74,-372.64"/> +<polygon fill="red" stroke="red" points="255.24,-372.49 258.75,-382.49 262.24,-372.48 255.24,-372.49"/> +<text text-anchor="middle" x="255.91" y="-270.37" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Phi_224->Store14 --> <g id="edge21" class="edge"> <title>Phi_224->Store14</title> -<path fill="none" stroke="blue" d="M159.11,-187.55C179.74,-195.47 206.28,-209.49 219,-232 237.92,-265.48 196.57,-299.24 165.7,-318.58"/> -<polygon fill="blue" stroke="blue" points="163.69,-315.71 156.94,-323.87 167.31,-321.7 163.69,-315.71"/> -<text text-anchor="middle" x="162.11" y="-190.15" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="blue" d="M122.4,-267.57C113.78,-277.31 104.01,-290.37 99,-304 90,-328.5 89.21,-358.4 90.25,-379.93"/> +<polygon fill="blue" stroke="blue" points="86.75,-379.98 90.9,-389.73 93.73,-379.52 86.75,-379.98"/> +<text text-anchor="middle" x="120.15" y="-269.72" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Phi_224->Store22 --> <g id="edge20" class="edge"> <title>Phi_224->Store22</title> -<path fill="none" stroke="blue" d="M123.42,-195.59C119.63,-203.87 114.97,-214.08 110.73,-223.34"/> -<polygon fill="blue" stroke="blue" points="107.5,-221.98 106.53,-232.53 113.87,-224.89 107.5,-221.98"/> -<text text-anchor="middle" x="120.42" y="-198.19" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="blue" d="M138.01,-268.3C137.59,-275.59 137.1,-284.27 136.63,-292.46"/> +<polygon fill="blue" stroke="blue" points="133.14,-292.2 136.06,-302.38 140.13,-292.6 133.14,-292.2"/> +<text text-anchor="middle" x="135.76" y="-270.45" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Phi_224->Region23 --> <g id="edge19" class="edge"> <title>Phi_224->Region23</title> -<path fill="none" stroke="black" stroke-dasharray="1,5" d="M164.31,-178C173.41,-178 182.5,-178 191.6,-178"/> -<polygon fill="black" stroke="black" points="191.87,-181.5 201.87,-178 191.87,-174.5 191.87,-181.5"/> -<text text-anchor="middle" x="167.31" y="-171.6" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="black" stroke-dasharray="1,5" d="M191.99,-250C200.94,-250 209.88,-250 218.82,-250"/> +<polygon fill="black" stroke="black" points="218.64,-253.5 228.64,-250 218.64,-246.5 218.64,-253.5"/> +<text text-anchor="middle" x="194.24" y="-242.4" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Return25->MemMerge --> +<g id="edge24" class="edge"> +<title>Return25->MemMerge</title> +<path fill="none" stroke="blue" d="M154,-124.3C154,-131.59 154,-140.27 154,-148.46"/> +<polygon fill="blue" stroke="blue" points="150.5,-148.38 154,-158.38 157.5,-148.38 150.5,-148.38"/> +<text text-anchor="middle" x="151.75" y="-126.45" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Return25->Load16 --> <g id="edge23" class="edge"> <title>Return25->Load16</title> -<path fill="none" stroke="black" d="M183,-124.13C183,-148.33 183,-192.79 183,-221.61"/> -<polygon fill="black" stroke="black" points="179.5,-221.81 183,-231.81 186.5,-221.81 179.5,-221.81"/> -<text text-anchor="middle" x="180" y="-126.73" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="black" d="M134.47,-124.14C124.31,-133.85 112.32,-146.7 104,-160 77.05,-203.06 59,-259.91 49.91,-293.26"/> +<polygon fill="black" stroke="black" points="46.62,-291.99 47.45,-302.55 53.39,-293.78 46.62,-291.99"/> +<text text-anchor="middle" x="132.22" y="-126.29" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Return25->Region23 --> <g id="edge22" class="edge"> <title>Return25->Region23</title> -<path fill="none" stroke="red" d="M195.85,-124.3C202.03,-132.61 209.54,-142.72 216.32,-151.86"/> -<polygon fill="red" stroke="red" points="213.52,-153.96 222.29,-159.9 219.14,-149.78 213.52,-153.96"/> -<text text-anchor="middle" x="192.85" y="-126.9" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> -</g> -<!-- Return25->Phi_224 --> -<g id="edge24" class="edge"> -<title>Return25->Phi_224</title> -<path fill="none" stroke="blue" d="M170.15,-124.3C163.79,-132.86 156.01,-143.33 149.07,-152.67"/> -<polygon fill="blue" stroke="blue" points="146.15,-150.74 142.99,-160.86 151.77,-154.92 146.15,-150.74"/> -<text text-anchor="middle" x="167.15" y="-126.9" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="red" d="M172.12,-124.31C182.08,-134.28 194.34,-147.32 204,-160 218.92,-179.59 233.25,-203.54 243.4,-221.66"/> +<polygon fill="red" stroke="red" points="240.24,-223.18 248.13,-230.25 246.37,-219.8 240.24,-223.18"/> +<text text-anchor="middle" x="169.87" y="-126.46" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> </g> </svg> diff --git a/chapter11/docs/graph2.gv b/chapter11/docs/graph2.gv index 04d02a3f..730cf845 100644 --- a/chapter11/docs/graph2.gv +++ b/chapter11/docs/graph2.gv @@ -18,11 +18,12 @@ return i; <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></TR> + <TR><TD PORT="p0" BGCOLOR="yellow">$ctrl</TD><TD PORT="p1">$mem</TD><TD PORT="p2">arg</TD></TR> </TABLE> </TD></TR> </TABLE>> ]; + MemMerge [ shape=box label="MemMerge" ]; Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; Con_4 [ label="#0" ]; new9 [ label="new S" ]; @@ -44,7 +45,7 @@ return i; Con_21 [ label="#1" ]; Store22 [ label=".f =" ]; Region23 [ shape=box style=filled fillcolor=yellow label="Region" ]; - Phi_224 [ style=filled fillcolor=lightyellow label="φ_$2" ]; + Phi_224 [ style=filled fillcolor=lightyellow label="MemPhi_2" ]; Return25 [ shape=box style=filled fillcolor=yellow label="Return" ]; { rank=same; Region23;Phi_224;} } @@ -52,7 +53,7 @@ return i; Stop3 -> Return25[taillabel=0 color=red]; new9 -> Start2:p0[taillabel=0 color=green]; Store12 -> Start2:p0[taillabel=0 color=red]; - Store12 -> Start2:p2[taillabel=1 color=blue]; + Store12 -> Start2:p1[taillabel=1 color=blue]; Store12 -> new9[taillabel=2]; Store12 -> Con_11[taillabel=3]; new13 -> Start2:p0[taillabel=0 color=green]; @@ -64,7 +65,7 @@ return i; Load16 -> Store14[taillabel=1 color=blue]; Load16 -> new9[taillabel=2]; If17 -> Start2:p0[taillabel=0 color=red]; - If17 -> Start2:p1[taillabel=1]; + If17 -> Start2:p2[taillabel=1]; Store22 -> Start2:p0[taillabel=0 color=red]; Store22 -> Store14[taillabel=1 color=blue]; Store22 -> new9[taillabel=2]; @@ -75,6 +76,9 @@ return i; Phi_224 -> Store22[taillabel=1 color=blue]; Phi_224 -> Store14[taillabel=2 color=blue constraint=false]; Return25 -> Region23[taillabel=0 color=red]; - Return25 -> Load16[taillabel=1]; - Return25 -> Phi_224[taillabel=2 color=blue]; + Return25 -> Load16[taillabel=2]; + Return25 -> MemMerge[taillabel=1 color=blue]; + MemMerge -> Start2:p1[taillabel=1 color=blue]; + MemMerge -> Phi_224[taillabel=2 color=blue]; + MemMerge -> Region23[taillabel=0 color=green]; } diff --git a/chapter11/docs/graph2.svg b/chapter11/docs/graph2.svg index 00f7b594..b8d43cc9 100644 --- a/chapter11/docs/graph2.svg +++ b/chapter11/docs/graph2.svg @@ -1,309 +1,335 @@ <?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) +<!-- Generated by graphviz version 12.2.0 (20241103.1931) --> <!-- Title: chapter11 Pages: 1 --> -<svg width="346pt" height="636pt" - viewBox="0.00 0.00 346.00 636.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 632)"> +<svg width="358pt" height="714pt" + viewBox="0.00 0.00 358.12 714.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 710)"> <title>chapter11</title> -<polygon fill="white" stroke="transparent" points="-4,4 -4,-632 342,-632 342,4 -4,4"/> +<polygon fill="white" stroke="none" points="-4,4 -4,-710 354.12,-710 354.12,4 -4,4"/> <g id="clust1" class="cluster"> <title>cluster_Nodes</title> -<polygon fill="none" stroke="black" points="8,-8 8,-620 330,-620 330,-8 8,-8"/> +<polygon fill="none" stroke="black" points="10.12,-8 10.12,-698 342.12,-698 342.12,-8 10.12,-8"/> </g> <!-- Start2 --> <g id="node1" class="node"> <title>Start2</title> -<polygon fill="yellow" stroke="transparent" points="137,-583 137,-608 243,-608 243,-583 137,-583"/> -<polygon fill="none" stroke="black" points="137,-583 137,-608 243,-608 243,-583 137,-583"/> -<text text-anchor="start" x="171.5" y="-591.8" font-family="Times,serif" font-size="14.00">Start</text> -<polygon fill="none" stroke="black" points="137,-552 137,-583 243,-583 243,-552 137,-552"/> -<polygon fill="yellow" stroke="transparent" points="142,-557 142,-578 182,-578 182,-557 142,-557"/> -<polygon fill="none" stroke="black" points="142,-557 142,-578 182,-578 182,-557 142,-557"/> -<text text-anchor="start" x="145" y="-563.8" font-family="Times,serif" font-size="14.00">$ctrl</text> -<polygon fill="none" stroke="black" points="182,-557 182,-578 213,-578 213,-557 182,-557"/> -<text text-anchor="start" x="185" y="-563.8" font-family="Times,serif" font-size="14.00">arg</text> -<polygon fill="none" stroke="black" points="213,-557 213,-578 238,-578 238,-557 213,-557"/> -<text text-anchor="start" x="216" y="-563.8" font-family="Times,serif" font-size="14.00">$2</text> +<polygon fill="yellow" stroke="none" points="178.5,-659.5 178.5,-686 283.75,-686 283.75,-659.5 178.5,-659.5"/> +<polygon fill="none" stroke="black" points="178.5,-659.5 178.5,-686 283.75,-686 283.75,-659.5 178.5,-659.5"/> +<text text-anchor="start" x="218.37" y="-667.7" font-family="Times New Roman,serif" font-size="14.00">Start</text> +<polygon fill="none" stroke="black" points="178.5,-627 178.5,-659.5 283.75,-659.5 283.75,-627 178.5,-627"/> +<polygon fill="yellow" stroke="none" points="183.5,-632 183.5,-654.5 214.25,-654.5 214.25,-632 183.5,-632"/> +<polygon fill="none" stroke="black" points="183.5,-632 183.5,-654.5 214.25,-654.5 214.25,-632 183.5,-632"/> +<text text-anchor="start" x="186.5" y="-638.2" font-family="Times New Roman,serif" font-size="14.00">$ctrl</text> +<polygon fill="none" stroke="black" points="214.25,-632 214.25,-654.5 255.5,-654.5 255.5,-632 214.25,-632"/> +<text text-anchor="start" x="217.25" y="-638.2" font-family="Times New Roman,serif" font-size="14.00">$mem</text> +<polygon fill="none" stroke="black" points="255.5,-632 255.5,-654.5 278.75,-654.5 278.75,-632 255.5,-632"/> +<text text-anchor="start" x="258.5" y="-638.2" font-family="Times New Roman,serif" font-size="14.00">arg</text> +</g> +<!-- MemMerge --> +<g id="node2" class="node"> +<title>MemMerge</title> +<polygon fill="none" stroke="black" points="235.12,-196 153.12,-196 153.12,-160 235.12,-160 235.12,-196"/> +<text text-anchor="middle" x="194.12" y="-172.95" font-family="Times New Roman,serif" font-size="14.00">MemMerge</text> +</g> +<!-- MemMerge->Start2 --> +<g id="edge29" class="edge"> +<title>MemMerge->Start2:p1</title> +<path fill="none" stroke="blue" d="M230.64,-196.45C271.82,-218.98 333.12,-262.35 333.12,-321 333.12,-321 333.12,-321 333.12,-498 333.12,-542.32 315.79,-552.37 288.12,-587 271.8,-607.42 244.66,-602.71 236.96,-619.82"/> +<polygon fill="blue" stroke="blue" points="233.55,-619.04 235.15,-629.51 240.43,-620.33 233.55,-619.04"/> +<text text-anchor="middle" x="228.39" y="-198.6" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Region23 --> +<g id="node14" class="node"> +<title>Region23</title> +<polygon fill="yellow" stroke="black" points="270.62,-268 215.62,-268 215.62,-232 270.62,-232 270.62,-268"/> +<text text-anchor="middle" x="243.12" y="-244.95" font-family="Times New Roman,serif" font-size="14.00">Region</text> +</g> +<!-- MemMerge->Region23 --> +<g id="edge31" class="edge"> +<title>MemMerge->Region23</title> +<path fill="none" stroke="green" d="M206.23,-196.3C211.81,-204.27 218.55,-213.9 224.74,-222.74"/> +<polygon fill="green" stroke="green" points="221.68,-224.47 230.28,-230.66 227.41,-220.46 221.68,-224.47"/> +<text text-anchor="middle" x="203.98" y="-198.45" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Phi_224 --> +<g id="node15" class="node"> +<title>Phi_224</title> +<ellipse fill="lightyellow" stroke="black" cx="145.12" cy="-250" rx="52.89" ry="18"/> +<text text-anchor="middle" x="145.12" y="-244.95" font-family="Times New Roman,serif" font-size="14.00">MemPhi_2</text> +</g> +<!-- MemMerge->Phi_224 --> +<g id="edge30" class="edge"> +<title>MemMerge->Phi_224</title> +<path fill="none" stroke="blue" d="M182.01,-196.3C176.35,-204.39 169.49,-214.19 163.23,-223.13"/> +<polygon fill="blue" stroke="blue" points="160.49,-220.94 157.62,-231.14 166.23,-224.95 160.49,-220.94"/> +<text text-anchor="middle" x="179.76" y="-198.45" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Stop3 --> -<g id="node2" class="node"> +<g id="node3" class="node"> <title>Stop3</title> -<polygon fill="yellow" stroke="black" points="176,-52 122,-52 122,-16 176,-16 176,-52"/> -<text text-anchor="middle" x="149" y="-30.3" font-family="Times,serif" font-size="14.00">Stop</text> +<polygon fill="yellow" stroke="black" points="136.12,-52 82.12,-52 82.12,-16 136.12,-16 136.12,-52"/> +<text text-anchor="middle" x="109.12" y="-28.95" font-family="Times New Roman,serif" font-size="14.00">Stop</text> </g> <!-- Return25 --> -<g id="node15" class="node"> +<g id="node16" class="node"> <title>Return25</title> -<polygon fill="yellow" stroke="black" points="182,-124 116,-124 116,-88 182,-88 182,-124"/> -<text text-anchor="middle" x="149" y="-102.3" font-family="Times,serif" font-size="14.00">Return</text> +<polygon fill="yellow" stroke="black" points="136.12,-124 82.12,-124 82.12,-88 136.12,-88 136.12,-124"/> +<text text-anchor="middle" x="109.12" y="-100.95" font-family="Times New Roman,serif" font-size="14.00">Return</text> </g> <!-- Stop3->Return25 --> <g id="edge1" class="edge"> <title>Stop3->Return25</title> -<path fill="none" stroke="red" d="M149,-52.3C149,-60.02 149,-69.29 149,-77.89"/> -<polygon fill="red" stroke="red" points="145.5,-77.9 149,-87.9 152.5,-77.9 145.5,-77.9"/> -<text text-anchor="middle" x="146" y="-54.9" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="red" d="M109.12,-52.3C109.12,-59.59 109.12,-68.27 109.12,-76.46"/> +<polygon fill="red" stroke="red" points="105.62,-76.38 109.12,-86.38 112.62,-76.38 105.62,-76.38"/> +<text text-anchor="middle" x="106.87" y="-54.45" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Con_4 --> -<g id="node3" class="node"> +<g id="node4" class="node"> <title>Con_4</title> -<ellipse fill="none" stroke="black" cx="121" cy="-422" rx="27" ry="18"/> -<text text-anchor="middle" x="121" y="-418.3" font-family="Times,serif" font-size="14.00">#0</text> +<ellipse fill="none" stroke="black" cx="85.12" cy="-497" rx="27" ry="18"/> +<text text-anchor="middle" x="85.12" y="-491.95" font-family="Times New Roman,serif" font-size="14.00">#0</text> </g> <!-- new9 --> -<g id="node4" class="node"> +<g id="node5" class="node"> <title>new9</title> -<ellipse fill="none" stroke="black" cx="191" cy="-494" rx="38.99" ry="18"/> -<text text-anchor="middle" x="191" y="-490.3" font-family="Times,serif" font-size="14.00">new S</text> +<ellipse fill="none" stroke="black" cx="158.12" cy="-569" rx="33.95" ry="18"/> +<text text-anchor="middle" x="158.12" y="-563.95" font-family="Times New Roman,serif" font-size="14.00">new S</text> </g> <!-- new9->Start2 --> <g id="edge2" class="edge"> <title>new9->Start2:p0</title> -<path fill="none" stroke="green" d="M178.53,-511.11C172.27,-520.49 165.49,-533 162.99,-546"/> -<polygon fill="green" stroke="green" points="159.51,-545.7 162,-556 166.47,-546.4 159.51,-545.7"/> -<text text-anchor="middle" x="175.53" y="-513.71" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="green" d="M175.17,-584.76C183.98,-593.73 193.64,-606.07 197.33,-619.77"/> +<polygon fill="green" stroke="green" points="193.84,-620.07 198.66,-629.5 200.77,-619.12 193.84,-620.07"/> +<text text-anchor="middle" x="177.42" y="-586.91" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Con_11 --> -<g id="node5" class="node"> +<g id="node6" class="node"> <title>Con_11</title> -<ellipse fill="none" stroke="black" cx="275" cy="-494" rx="27" ry="18"/> -<text text-anchor="middle" x="275" y="-490.3" font-family="Times,serif" font-size="14.00">#2</text> +<ellipse fill="none" stroke="black" cx="237.12" cy="-569" rx="27" ry="18"/> +<text text-anchor="middle" x="237.12" y="-563.95" font-family="Times New Roman,serif" font-size="14.00">#2</text> </g> <!-- Store12 --> -<g id="node6" class="node"> +<g id="node7" class="node"> <title>Store12</title> -<ellipse fill="none" stroke="black" cx="217" cy="-422" rx="27.9" ry="18"/> -<text text-anchor="middle" x="217" y="-418.3" font-family="Times,serif" font-size="14.00">.f =</text> +<ellipse fill="none" stroke="black" cx="200.12" cy="-497" rx="27" ry="18"/> +<text text-anchor="middle" x="200.12" y="-491.95" font-family="Times New Roman,serif" font-size="14.00">.f =</text> </g> <!-- Store12->Start2 --> <g id="edge3" class="edge"> <title>Store12->Start2:p0</title> -<path fill="none" stroke="red" d="M239.72,-432.8C270.79,-446.95 322.54,-473.53 321,-493"/> -<path fill="none" stroke="red" d="M321,-495C315.6,-563.11 190.37,-496.28 166.07,-538.32"/> -<polygon fill="red" stroke="red" points="162.72,-537.27 162.68,-547.87 169.32,-539.61 162.72,-537.27"/> -<text text-anchor="middle" x="242.72" y="-435.4" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="red" d="M283.12,-570C284.3,-608.41 225.3,-593.17 205.36,-613.49"/> +<polygon fill="red" stroke="red" points="202.48,-611.47 200.58,-621.89 208.57,-614.92 202.48,-611.47"/> +<path fill="none" stroke="red" d="M223.33,-506.27C247.55,-516.58 282.16,-536.72 283.12,-568"/> +<text text-anchor="middle" x="280.87" y="-562.4" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Store12->new9 --> <g id="edge5" class="edge"> <title>Store12->new9</title> -<path fill="none" stroke="black" d="M210.84,-439.59C207.84,-447.66 204.16,-457.57 200.79,-466.65"/> -<polygon fill="black" stroke="black" points="197.5,-465.45 197.3,-476.04 204.06,-467.89 197.5,-465.45"/> -<text text-anchor="middle" x="207.84" y="-442.19" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="black" d="M190.38,-514.24C185.52,-522.33 179.52,-532.34 174.02,-541.51"/> +<polygon fill="black" stroke="black" points="171.02,-539.69 168.88,-550.07 177.03,-543.29 171.02,-539.69"/> +<text text-anchor="middle" x="188.13" y="-516.39" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Store12->Con_11 --> <g id="edge6" class="edge"> <title>Store12->Con_11</title> -<path fill="none" stroke="black" d="M229.59,-438.19C237.26,-447.45 247.23,-459.48 255.86,-469.91"/> -<polygon fill="black" stroke="black" points="253.36,-472.37 262.44,-477.84 258.75,-467.91 253.36,-472.37"/> -<text text-anchor="middle" x="226.59" y="-440.79" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> +<path fill="none" stroke="black" d="M208.7,-514.24C213.05,-522.45 218.42,-532.63 223.33,-541.91"/> +<polygon fill="black" stroke="black" points="220.11,-543.31 227.88,-550.51 226.3,-540.04 220.11,-543.31"/> +<text text-anchor="middle" x="206.45" y="-516.39" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> </g> <!-- new13 --> -<g id="node7" class="node"> +<g id="node8" class="node"> <title>new13</title> -<ellipse fill="none" stroke="black" cx="75" cy="-494" rx="38.99" ry="18"/> -<text text-anchor="middle" x="75" y="-490.3" font-family="Times,serif" font-size="14.00">new S</text> +<ellipse fill="none" stroke="black" cx="72.12" cy="-569" rx="33.95" ry="18"/> +<text text-anchor="middle" x="72.12" y="-563.95" font-family="Times New Roman,serif" font-size="14.00">new S</text> </g> <!-- new13->Start2 --> <g id="edge7" class="edge"> <title>new13->Start2:p0</title> -<path fill="none" stroke="green" d="M81.73,-512.06C90.25,-530.86 106.79,-559.28 130.97,-565.69"/> -<polygon fill="green" stroke="green" points="130.63,-569.17 141,-567 131.54,-562.23 130.63,-569.17"/> -<text text-anchor="middle" x="78.73" y="-514.66" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="green" d="M84.9,-585.89C102,-605.42 134.45,-636.66 171.03,-642.35"/> +<polygon fill="green" stroke="green" points="170.74,-645.84 180.99,-643.13 171.29,-638.86 170.74,-645.84"/> +<text text-anchor="middle" x="82.65" y="-588.04" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Store14 --> -<g id="node8" class="node"> +<g id="node9" class="node"> <title>Store14</title> -<ellipse fill="none" stroke="black" cx="74" cy="-336" rx="27.9" ry="18"/> -<text text-anchor="middle" x="74" y="-332.3" font-family="Times,serif" font-size="14.00">.f =</text> +<ellipse fill="none" stroke="black" cx="65.12" cy="-409.5" rx="27" ry="18"/> +<text text-anchor="middle" x="65.12" y="-404.45" font-family="Times New Roman,serif" font-size="14.00">.f =</text> </g> <!-- Store14->Start2 --> <g id="edge8" class="edge"> <title>Store14->Start2:p0</title> -<path fill="none" stroke="red" d="M75,-423C81.23,-454.16 104.2,-450.38 123,-476 125.09,-478.85 144.12,-515.97 154.8,-538.72"/> -<polygon fill="red" stroke="red" points="151.69,-540.34 159.03,-547.99 158.06,-537.44 151.69,-540.34"/> -<text text-anchor="middle" x="72" y="-416.6" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="red" d="M39.12,-498C35.91,-530.2 3.02,-539.93 19.12,-568"/> +<path fill="none" stroke="red" d="M57.93,-427.13C51.12,-444.08 41.58,-471.3 39.12,-496"/> +<text text-anchor="middle" x="36.87" y="-490.4" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Store14->Con_4 --> <g id="edge11" class="edge"> <title>Store14->Con_4</title> -<path fill="none" stroke="black" d="M83.06,-353.2C89.82,-365.28 99.19,-382.02 106.92,-395.84"/> -<polygon fill="black" stroke="black" points="104.03,-397.84 111.97,-404.85 110.14,-394.42 104.03,-397.84"/> -<text text-anchor="middle" x="80.06" y="-355.8" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> +<path fill="none" stroke="black" d="M69.07,-427.39C71.77,-438.91 75.4,-454.44 78.53,-467.81"/> +<polygon fill="black" stroke="black" points="75.08,-468.44 80.76,-477.38 81.89,-466.84 75.08,-468.44"/> +<text text-anchor="middle" x="66.82" y="-429.54" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> </g> <!-- Store14->Store12 --> <g id="edge9" class="edge"> <title>Store14->Store12</title> -<path fill="none" stroke="blue" d="M91.16,-350.58C98.61,-356.24 107.55,-362.71 116,-368 138.6,-382.16 165.37,-396.08 185.67,-406.11"/> -<polygon fill="blue" stroke="blue" points="184.27,-409.32 194.79,-410.56 187.34,-403.03 184.27,-409.32"/> -<text text-anchor="middle" x="88.16" y="-353.18" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="blue" d="M84.18,-422.57C106.99,-437.02 145.41,-461.35 171.73,-478.02"/> +<polygon fill="blue" stroke="blue" points="169.56,-480.79 179.88,-483.18 173.31,-474.88 169.56,-480.79"/> +<text text-anchor="middle" x="86.43" y="-424.72" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Store14->new13 --> <g id="edge10" class="edge"> <title>Store14->new13</title> -<path fill="none" stroke="black" d="M72.36,-354.25C71.17,-371.36 70.46,-398.26 75,-421"/> -<path fill="none" stroke="black" d="M75,-423C77.77,-436.87 78.02,-452.6 77.47,-465.63"/> -<polygon fill="black" stroke="black" points="73.97,-465.65 76.86,-475.84 80.95,-466.07 73.97,-465.65"/> -<text text-anchor="middle" x="69.36" y="-356.85" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="black" d="M39.12,-498C37.54,-513.82 44.48,-529.98 52.39,-542.78"/> +<polygon fill="black" stroke="black" points="49.45,-544.68 57.96,-551 55.25,-540.75 49.45,-544.68"/> +<text text-anchor="middle" x="36.87" y="-500.15" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Load16 --> -<g id="node9" class="node"> +<g id="node10" class="node"> <title>Load16</title> -<ellipse fill="none" stroke="black" cx="74" cy="-250" rx="27" ry="18"/> -<text text-anchor="middle" x="74" y="-246.3" font-family="Times,serif" font-size="14.00">.f</text> +<ellipse fill="none" stroke="black" cx="45.12" cy="-322" rx="27" ry="18"/> +<text text-anchor="middle" x="45.12" y="-316.95" font-family="Times New Roman,serif" font-size="14.00">.f</text> </g> <!-- Load16->Start2 --> <g id="edge12" class="edge"> <title>Load16->Start2:p0</title> -<path fill="none" stroke="red" d="M60.29,-266C52.02,-276 42.12,-289.86 37,-304 19.23,-353.07 6.22,-373.12 27,-421"/> -<path fill="none" stroke="red" d="M27,-423C42.75,-459.29 5.97,-478.5 27,-512 52.69,-552.92 76.85,-564.12 118.8,-566.47"/> -<polygon fill="red" stroke="red" points="118.8,-569.97 128.93,-566.86 119.08,-562.98 118.8,-569.97"/> -<text text-anchor="middle" x="57.29" y="-268.6" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="red" d="M37.1,-339.54C18.2,-381.13 -24.39,-492.12 19.12,-568"/> +<path fill="none" stroke="red" d="M19.12,-570C23.95,-577.31 22.39,-581.38 29.12,-587 78.43,-628.14 103.69,-640.51 159.1,-642.82"/> +<polygon fill="red" stroke="red" points="158.99,-646.32 169.09,-643.11 159.19,-639.32 158.99,-646.32"/> +<text text-anchor="middle" x="34.85" y="-341.69" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Load16->new9 --> <g id="edge14" class="edge"> <title>Load16->new9</title> -<path fill="none" stroke="black" d="M27,-423C37.2,-446.5 102.74,-468.87 147.82,-481.75"/> -<polygon fill="black" stroke="black" points="147.08,-485.18 157.66,-484.5 148.97,-478.44 147.08,-485.18"/> -<text text-anchor="middle" x="24" y="-416.6" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="black" d="M63.11,-335.98C75.41,-345.72 91.23,-360.08 101.12,-376 109.09,-388.84 106.98,-393.97 111.12,-408.5"/> +<path fill="none" stroke="black" d="M111.12,-410.5C124.08,-456.01 139.97,-508.64 149.56,-540.08"/> +<polygon fill="black" stroke="black" points="146.19,-541.04 152.46,-549.58 152.89,-538.99 146.19,-541.04"/> +<text text-anchor="middle" x="60.86" y="-338.13" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Load16->Store14 --> <g id="edge13" class="edge"> <title>Load16->Store14</title> -<path fill="none" stroke="blue" d="M74,-268.4C74,-279.74 74,-294.77 74,-307.68"/> -<polygon fill="blue" stroke="blue" points="70.5,-307.9 74,-317.9 77.5,-307.9 70.5,-307.9"/> -<text text-anchor="middle" x="71" y="-271" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="blue" d="M49.07,-339.89C51.77,-351.41 55.4,-366.94 58.53,-380.31"/> +<polygon fill="blue" stroke="blue" points="55.08,-380.94 60.76,-389.88 61.89,-379.34 55.08,-380.94"/> +<text text-anchor="middle" x="46.82" y="-342.04" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- If17 --> -<g id="node10" class="node"> +<g id="node11" class="node"> <title>If17</title> -<polygon fill="yellow" stroke="transparent" points="220,-339 220,-364 314,-364 314,-339 220,-339"/> -<polygon fill="none" stroke="black" points="220,-339 220,-364 314,-364 314,-339 220,-339"/> -<text text-anchor="start" x="261.5" y="-347.8" font-family="Times,serif" font-size="14.00">If</text> -<polygon fill="none" stroke="black" points="220,-308 220,-339 314,-339 314,-308 220,-308"/> -<polygon fill="yellow" stroke="transparent" points="225,-313 225,-334 265,-334 265,-313 225,-313"/> -<polygon fill="none" stroke="black" points="225,-313 225,-334 265,-334 265,-313 225,-313"/> -<text text-anchor="start" x="228" y="-319.8" font-family="Times,serif" font-size="14.00">True</text> -<polygon fill="yellow" stroke="transparent" points="265,-313 265,-334 309,-334 309,-313 265,-313"/> -<polygon fill="none" stroke="black" points="265,-313 265,-334 309,-334 309,-313 265,-313"/> -<text text-anchor="start" x="268" y="-319.8" font-family="Times,serif" font-size="14.00">False</text> +<polygon fill="yellow" stroke="none" points="230.12,-412.5 230.12,-439 306.12,-439 306.12,-412.5 230.12,-412.5"/> +<polygon fill="none" stroke="black" points="230.12,-412.5 230.12,-439 306.12,-439 306.12,-412.5 230.12,-412.5"/> +<text text-anchor="start" x="263.62" y="-420.7" font-family="Times New Roman,serif" font-size="14.00">If</text> +<polygon fill="none" stroke="black" points="230.12,-380 230.12,-412.5 306.12,-412.5 306.12,-380 230.12,-380"/> +<polygon fill="yellow" stroke="none" points="235.12,-385 235.12,-407.5 266.62,-407.5 266.62,-385 235.12,-385"/> +<polygon fill="none" stroke="black" points="235.12,-385 235.12,-407.5 266.62,-407.5 266.62,-385 235.12,-385"/> +<text text-anchor="start" x="238.12" y="-391.2" font-family="Times New Roman,serif" font-size="14.00">True</text> +<polygon fill="yellow" stroke="none" points="266.62,-385 266.62,-407.5 301.12,-407.5 301.12,-385 266.62,-385"/> +<polygon fill="none" stroke="black" points="266.62,-385 266.62,-407.5 301.12,-407.5 301.12,-385 266.62,-385"/> +<text text-anchor="start" x="269.62" y="-391.2" font-family="Times New Roman,serif" font-size="14.00">False</text> </g> <!-- If17->Start2 --> <g id="edge15" class="edge"> <title>If17->Start2:p0</title> -<path fill="none" stroke="red" d="M258.27,-368.06C255.35,-385.41 255.35,-406.41 267,-421"/> -<text text-anchor="middle" x="255.27" y="-370.66" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="red" d="M263.95,-442.76C262.88,-459.03 263.19,-478.88 268.12,-496"/> +<text text-anchor="middle" x="261.7" y="-444.91" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Con_21 --> -<g id="node11" class="node"> +<g id="node12" class="node"> <title>Con_21</title> -<ellipse fill="none" stroke="black" cx="167" cy="-336" rx="27" ry="18"/> -<text text-anchor="middle" x="167" y="-332.3" font-family="Times,serif" font-size="14.00">#1</text> +<ellipse fill="none" stroke="black" cx="157.12" cy="-409.5" rx="27" ry="18"/> +<text text-anchor="middle" x="157.12" y="-404.45" font-family="Times New Roman,serif" font-size="14.00">#1</text> </g> <!-- Store22 --> -<g id="node12" class="node"> +<g id="node13" class="node"> <title>Store22</title> -<ellipse fill="none" stroke="black" cx="149" cy="-250" rx="27.9" ry="18"/> -<text text-anchor="middle" x="149" y="-246.3" font-family="Times,serif" font-size="14.00">.f =</text> +<ellipse fill="none" stroke="black" cx="117.12" cy="-322" rx="27" ry="18"/> +<text text-anchor="middle" x="117.12" y="-316.95" font-family="Times New Roman,serif" font-size="14.00">.f =</text> </g> <!-- Store22->Start2 --> <g id="edge17" class="edge"> <title>Store22->Start2:p0</title> -<path fill="none" stroke="red" d="M121,-337C124.72,-350.99 120.62,-357.9 131,-368 171.82,-407.72 207.03,-371.77 254,-404 261.84,-409.38 261.07,-413.57 267,-421"/> -<path fill="none" stroke="red" d="M267,-423C291.51,-453.71 324.1,-453.83 321,-493"/> -<text text-anchor="middle" x="118" y="-330.6" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="red" d="M138.84,-333.04C155.95,-342.01 179.17,-356.71 193.12,-376 211.33,-401.18 196.96,-416.46 213.12,-443 230.78,-471.99 258.72,-463.38 268.12,-496"/> +<path fill="none" stroke="red" d="M268.12,-498C276.93,-528.57 282.14,-536.2 283.12,-568"/> +<text text-anchor="middle" x="136.59" y="-335.19" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Store22->new9 --> <g id="edge19" class="edge"> <title>Store22->new9</title> -<path fill="none" stroke="black" d="M138.02,-266.62C127.62,-283.37 114.49,-310.8 121,-335"/> -<path fill="none" stroke="black" d="M121,-337C124.76,-350.98 124.46,-355.08 131,-368 139.92,-385.61 147.62,-386.63 157,-404 167.83,-424.05 176.84,-448.28 182.87,-466.4"/> -<polygon fill="black" stroke="black" points="179.57,-467.59 185.98,-476.03 186.23,-465.43 179.57,-467.59"/> -<text text-anchor="middle" x="135.02" y="-269.22" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="black" d="M112.54,-340.07C108.62,-357.58 104.56,-385.46 111.12,-408.5"/> +<text text-anchor="middle" x="110.29" y="-342.22" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Store22->Store14 --> <g id="edge18" class="edge"> <title>Store22->Store14</title> -<path fill="none" stroke="blue" d="M135.59,-266.02C124.07,-278.92 107.3,-297.71 94.16,-312.42"/> -<polygon fill="blue" stroke="blue" points="91.34,-310.32 87.29,-320.11 96.56,-314.98 91.34,-310.32"/> -<text text-anchor="middle" x="132.59" y="-268.62" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="blue" d="M107.34,-339.08C99.86,-351.38 89.4,-368.59 80.76,-382.79"/> +<polygon fill="blue" stroke="blue" points="77.85,-380.83 75.64,-391.2 83.83,-384.47 77.85,-380.83"/> +<text text-anchor="middle" x="105.09" y="-341.23" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Store22->Con_21 --> <g id="edge20" class="edge"> <title>Store22->Con_21</title> -<path fill="none" stroke="black" d="M152.64,-268C155.11,-279.5 158.41,-294.93 161.23,-308.08"/> -<polygon fill="black" stroke="black" points="157.85,-308.98 163.36,-318.03 164.69,-307.52 157.85,-308.98"/> -<text text-anchor="middle" x="149.64" y="-270.6" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> -</g> -<!-- Region23 --> -<g id="node13" class="node"> -<title>Region23</title> -<polygon fill="yellow" stroke="black" points="269,-196 203,-196 203,-160 269,-160 269,-196"/> -<text text-anchor="middle" x="236" y="-174.3" font-family="Times,serif" font-size="14.00">Region</text> +<path fill="none" stroke="black" d="M124.83,-339.49C130.39,-351.36 138,-367.64 144.44,-381.4"/> +<polygon fill="black" stroke="black" points="141.25,-382.83 148.65,-390.4 147.59,-379.86 141.25,-382.83"/> +<text text-anchor="middle" x="122.58" y="-341.64" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> </g> <!-- Region23->If17 --> <g id="edge21" class="edge"> <title>Region23->If17:p0</title> -<path fill="none" stroke="red" d="M237.91,-196.19C240.33,-219.44 244.28,-262.49 244.91,-301.62"/> -<polygon fill="red" stroke="red" points="241.42,-302.03 245,-312 248.42,-301.97 241.42,-302.03"/> -<text text-anchor="middle" x="234.91" y="-198.79" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="red" d="M244.76,-268.2C246.83,-291.25 250.19,-333.78 250.78,-372.63"/> +<polygon fill="red" stroke="red" points="247.28,-372.51 250.86,-382.49 254.28,-372.46 247.28,-372.51"/> +<text text-anchor="middle" x="242.51" y="-270.35" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Region23->If17 --> <g id="edge22" class="edge"> <title>Region23->If17:p1</title> -<path fill="none" stroke="red" d="M249.35,-196.26C256.45,-206.22 264.76,-219.26 270,-232 282.41,-262.21 286.24,-273.37 286.89,-301.69"/> -<polygon fill="red" stroke="red" points="283.4,-302.04 287,-312 290.4,-301.96 283.4,-302.04"/> -<text text-anchor="middle" x="246.35" y="-198.86" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> -</g> -<!-- Phi_224 --> -<g id="node14" class="node"> -<title>Phi_224</title> -<ellipse fill="lightyellow" stroke="black" cx="149" cy="-178" rx="33.29" ry="18"/> -<text text-anchor="middle" x="149" y="-174.3" font-family="Times,serif" font-size="14.00">φ_$2</text> +<path fill="none" stroke="red" d="M252.57,-268.22C257.8,-278.38 264.05,-291.63 268.12,-304 278.15,-334.53 282.78,-345.32 283.7,-372.64"/> +<polygon fill="red" stroke="red" points="280.19,-372.54 283.85,-382.49 287.19,-372.43 280.19,-372.54"/> +<text text-anchor="middle" x="250.32" y="-270.37" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Phi_224->Store14 --> <g id="edge25" class="edge"> <title>Phi_224->Store14</title> -<path fill="none" stroke="blue" d="M163.74,-194.16C179.64,-212.44 200.82,-243.72 186,-268 167.78,-297.86 145.73,-285.55 116,-304 110.44,-307.45 104.68,-311.42 99.27,-315.34"/> -<polygon fill="blue" stroke="blue" points="97.08,-312.61 91.14,-321.38 101.25,-318.23 97.08,-312.61"/> -<text text-anchor="middle" x="160.74" y="-196.76" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="blue" d="M151.73,-268.04C157.85,-286.71 164.45,-317.05 153.12,-340 141.47,-363.59 126.88,-359.83 106.12,-376 101.11,-379.91 95.77,-384.11 90.66,-388.15"/> +<polygon fill="blue" stroke="blue" points="88.67,-385.26 83.01,-394.21 93.02,-390.74 88.67,-385.26"/> +<text text-anchor="middle" x="149.48" y="-270.19" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Phi_224->Store22 --> <g id="edge24" class="edge"> <title>Phi_224->Store22</title> -<path fill="none" stroke="blue" d="M149,-196.3C149,-204.02 149,-213.29 149,-221.89"/> -<polygon fill="blue" stroke="blue" points="145.5,-221.9 149,-231.9 152.5,-221.9 145.5,-221.9"/> -<text text-anchor="middle" x="146" y="-198.9" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="blue" d="M138.2,-268.3C135.14,-275.96 131.46,-285.15 128.04,-293.7"/> +<polygon fill="blue" stroke="blue" points="124.8,-292.39 124.33,-302.97 131.3,-294.99 124.8,-292.39"/> +<text text-anchor="middle" x="135.95" y="-270.45" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Phi_224->Region23 --> <g id="edge23" class="edge"> <title>Phi_224->Region23</title> -<path fill="none" stroke="black" stroke-dasharray="1,5" d="M182.3,-178C185.73,-178 189.16,-178 192.58,-178"/> -<polygon fill="black" stroke="black" points="192.86,-181.5 202.86,-178 192.86,-174.5 192.86,-181.5"/> -<text text-anchor="middle" x="185.3" y="-171.6" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="black" stroke-dasharray="1,5" d="M198.33,-250C200.17,-250 202.01,-250 203.84,-250"/> +<polygon fill="black" stroke="black" points="203.61,-253.5 213.61,-250 203.61,-246.5 203.61,-253.5"/> +<text text-anchor="middle" x="200.58" y="-242.4" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Return25->MemMerge --> +<g id="edge28" class="edge"> +<title>Return25->MemMerge</title> +<path fill="none" stroke="blue" d="M130.13,-124.3C140.45,-132.8 153.06,-143.19 164.35,-152.49"/> +<polygon fill="blue" stroke="blue" points="161.94,-155.04 171.89,-158.69 166.39,-149.63 161.94,-155.04"/> +<text text-anchor="middle" x="127.88" y="-126.45" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Return25->Load16 --> <g id="edge27" class="edge"> <title>Return25->Load16</title> -<path fill="none" stroke="black" d="M133.12,-124.15C124.54,-134.07 114.25,-147.11 107,-160 95.8,-179.9 87.06,-204.34 81.39,-222.56"/> -<polygon fill="black" stroke="black" points="78.01,-221.66 78.49,-232.24 84.71,-223.67 78.01,-221.66"/> -<text text-anchor="middle" x="130.12" y="-126.75" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="black" d="M73.12,-179C81.32,-218.99 67.84,-265.4 56.89,-293.98"/> +<polygon fill="black" stroke="black" points="53.76,-292.38 53.29,-302.96 60.26,-294.99 53.76,-292.38"/> +<path fill="none" stroke="black" d="M92.26,-124.39C81.14,-137.97 69.27,-157.7 73.12,-177"/> +<text text-anchor="middle" x="70.87" y="-171.4" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Return25->Region23 --> <g id="edge26" class="edge"> <title>Return25->Region23</title> -<path fill="none" stroke="red" d="M170.51,-124.3C181.46,-133.12 194.95,-143.97 206.82,-153.53"/> -<polygon fill="red" stroke="red" points="204.76,-156.35 214.74,-159.9 209.14,-150.9 204.76,-156.35"/> -<text text-anchor="middle" x="167.51" y="-126.9" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> -</g> -<!-- Return25->Phi_224 --> -<g id="edge28" class="edge"> -<title>Return25->Phi_224</title> -<path fill="none" stroke="blue" d="M149,-124.3C149,-132.02 149,-141.29 149,-149.89"/> -<polygon fill="blue" stroke="blue" points="145.5,-149.9 149,-159.9 152.5,-149.9 145.5,-149.9"/> -<text text-anchor="middle" x="146" y="-126.9" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="red" d="M73.12,-179C85.3,-238.43 141.92,-209.98 204.75,-231.55"/> +<polygon fill="red" stroke="red" points="203.29,-234.74 213.88,-235.09 205.83,-228.21 203.29,-234.74"/> +<text text-anchor="middle" x="70.87" y="-181.15" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> </g> </svg> diff --git a/chapter11/docs/graph3.gv b/chapter11/docs/graph3.gv index c47ac565..d89d0ed4 100644 --- a/chapter11/docs/graph3.gv +++ b/chapter11/docs/graph3.gv @@ -18,11 +18,12 @@ return i; <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></TR> + <TR><TD PORT="p0" BGCOLOR="yellow">$ctrl</TD><TD PORT="p1">$mem</TD><TD PORT="p2">arg</TD></TR> </TABLE> </TD></TR> </TABLE>> ]; + MemMerge [ shape=box label="MemMerge" ]; Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; Con_4 [ label="#0" ]; new9 [ label="new S" ]; @@ -44,7 +45,7 @@ return i; Con_21 [ label="#1" ]; Store22 [ label=".f =" ]; Region23 [ shape=box style=filled fillcolor=yellow label="Region" ]; - Phi_224 [ style=filled fillcolor=lightyellow label="φ_$2" ]; + Phi_224 [ style=filled fillcolor=lightyellow label="MemPhi_2" ]; Return25 [ shape=box style=filled fillcolor=yellow label="Return" ]; { rank=same; Region23;Phi_224;} } @@ -52,7 +53,7 @@ return i; Stop3 -> Return25[taillabel=0 color=red]; new9 -> Start2:p0[taillabel=0 color=green]; Store12 -> Start2:p0[taillabel=0 color=red]; - Store12 -> Start2:p2[taillabel=1 color=blue]; + Store12 -> Start2:p1[taillabel=1 color=blue]; Store12 -> new9[taillabel=2]; Store12 -> Con_11[taillabel=3]; new13 -> Start2:p0[taillabel=0 color=green]; @@ -64,7 +65,7 @@ return i; Load16 -> Store14[taillabel=1 color=blue]; Load16 -> new9[taillabel=2]; If17 -> Start2:p0[taillabel=0 color=red]; - If17 -> Start2:p1[taillabel=1]; + If17 -> Start2:p2[taillabel=1]; Store22 -> If17:p0[taillabel=0 color=red]; Store22 -> Store14[taillabel=1 color=blue]; Store22 -> new9[taillabel=2]; @@ -76,6 +77,9 @@ return i; Phi_224 -> Store22[taillabel=1 color=blue]; Phi_224 -> Store14[taillabel=2 color=blue constraint=false]; Return25 -> Region23[taillabel=0 color=red]; - Return25 -> Load16[taillabel=1]; - Return25 -> Phi_224[taillabel=2 color=blue]; + Return25 -> Load16[taillabel=2]; + Return25 -> MemMerge[taillabel=1 color=blue]; + MemMerge -> Start2:p1[taillabel=1 color=blue]; + MemMerge -> Phi_224[taillabel=2 color=blue]; + MemMerge -> Region23[taillabel=0 color=green]; } diff --git a/chapter11/docs/graph3.svg b/chapter11/docs/graph3.svg index e017c4af..a16a1733 100644 --- a/chapter11/docs/graph3.svg +++ b/chapter11/docs/graph3.svg @@ -1,316 +1,348 @@ <?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) +<!-- Generated by graphviz version 12.2.0 (20241103.1931) --> <!-- Title: chapter11 Pages: 1 --> -<svg width="346pt" height="708pt" - viewBox="0.00 0.00 346.00 708.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 704)"> +<svg width="356pt" height="786pt" + viewBox="0.00 0.00 356.00 786.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 782)"> <title>chapter11</title> -<polygon fill="white" stroke="transparent" points="-4,4 -4,-704 342,-704 342,4 -4,4"/> +<polygon fill="white" stroke="none" points="-4,4 -4,-782 352,-782 352,4 -4,4"/> <g id="clust1" class="cluster"> <title>cluster_Nodes</title> -<polygon fill="none" stroke="black" points="8,-8 8,-692 330,-692 330,-8 8,-8"/> +<polygon fill="none" stroke="black" points="8,-8 8,-770 340,-770 340,-8 8,-8"/> </g> <!-- Start2 --> <g id="node1" class="node"> <title>Start2</title> -<polygon fill="yellow" stroke="transparent" points="137,-655 137,-680 243,-680 243,-655 137,-655"/> -<polygon fill="none" stroke="black" points="137,-655 137,-680 243,-680 243,-655 137,-655"/> -<text text-anchor="start" x="171.5" y="-663.8" font-family="Times,serif" font-size="14.00">Start</text> -<polygon fill="none" stroke="black" points="137,-624 137,-655 243,-655 243,-624 137,-624"/> -<polygon fill="yellow" stroke="transparent" points="142,-629 142,-650 182,-650 182,-629 142,-629"/> -<polygon fill="none" stroke="black" points="142,-629 142,-650 182,-650 182,-629 142,-629"/> -<text text-anchor="start" x="145" y="-635.8" font-family="Times,serif" font-size="14.00">$ctrl</text> -<polygon fill="none" stroke="black" points="182,-629 182,-650 213,-650 213,-629 182,-629"/> -<text text-anchor="start" x="185" y="-635.8" font-family="Times,serif" font-size="14.00">arg</text> -<polygon fill="none" stroke="black" points="213,-629 213,-650 238,-650 238,-629 213,-629"/> -<text text-anchor="start" x="216" y="-635.8" font-family="Times,serif" font-size="14.00">$2</text> +<polygon fill="yellow" stroke="none" points="218.38,-731.5 218.38,-758 323.62,-758 323.62,-731.5 218.38,-731.5"/> +<polygon fill="none" stroke="black" points="218.38,-731.5 218.38,-758 323.62,-758 323.62,-731.5 218.38,-731.5"/> +<text text-anchor="start" x="258.25" y="-739.7" font-family="Times New Roman,serif" font-size="14.00">Start</text> +<polygon fill="none" stroke="black" points="218.38,-699 218.38,-731.5 323.62,-731.5 323.62,-699 218.38,-699"/> +<polygon fill="yellow" stroke="none" points="223.38,-704 223.38,-726.5 254.12,-726.5 254.12,-704 223.38,-704"/> +<polygon fill="none" stroke="black" points="223.38,-704 223.38,-726.5 254.12,-726.5 254.12,-704 223.38,-704"/> +<text text-anchor="start" x="226.38" y="-710.2" font-family="Times New Roman,serif" font-size="14.00">$ctrl</text> +<polygon fill="none" stroke="black" points="254.12,-704 254.12,-726.5 295.38,-726.5 295.38,-704 254.12,-704"/> +<text text-anchor="start" x="257.12" y="-710.2" font-family="Times New Roman,serif" font-size="14.00">$mem</text> +<polygon fill="none" stroke="black" points="295.38,-704 295.38,-726.5 318.62,-726.5 318.62,-704 295.38,-704"/> +<text text-anchor="start" x="298.38" y="-710.2" font-family="Times New Roman,serif" font-size="14.00">arg</text> +</g> +<!-- MemMerge --> +<g id="node2" class="node"> +<title>MemMerge</title> +<polygon fill="none" stroke="black" points="234,-196 152,-196 152,-160 234,-160 234,-196"/> +<text text-anchor="middle" x="193" y="-172.95" font-family="Times New Roman,serif" font-size="14.00">MemMerge</text> +</g> +<!-- MemMerge->Start2 --> +<g id="edge30" class="edge"> +<title>MemMerge->Start2:p1</title> +<path fill="none" stroke="blue" d="M234.18,-194.39C253.1,-203.11 274.68,-215.59 290,-232 319.72,-263.83 331,-277.45 331,-321 331,-321 331,-321 331,-498 331,-538.11 326.09,-548.18 316,-587 309.95,-610.27 282.67,-661.11 276.15,-691.66"/> +<polygon fill="blue" stroke="blue" points="272.69,-691.14 274.94,-701.5 279.64,-692 272.69,-691.14"/> +<text text-anchor="middle" x="236.43" y="-186.79" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Region23 --> +<g id="node14" class="node"> +<title>Region23</title> +<polygon fill="yellow" stroke="black" points="280.5,-268 225.5,-268 225.5,-232 280.5,-232 280.5,-268"/> +<text text-anchor="middle" x="253" y="-244.95" font-family="Times New Roman,serif" font-size="14.00">Region</text> +</g> +<!-- MemMerge->Region23 --> +<g id="edge32" class="edge"> +<title>MemMerge->Region23</title> +<path fill="none" stroke="green" d="M207.83,-196.3C214.81,-204.44 223.27,-214.31 230.97,-223.3"/> +<polygon fill="green" stroke="green" points="228.19,-225.43 237.35,-230.75 233.5,-220.88 228.19,-225.43"/> +<text text-anchor="middle" x="205.58" y="-198.45" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Phi_224 --> +<g id="node15" class="node"> +<title>Phi_224</title> +<ellipse fill="lightyellow" stroke="black" cx="155" cy="-250" rx="52.89" ry="18"/> +<text text-anchor="middle" x="155" y="-244.95" font-family="Times New Roman,serif" font-size="14.00">MemPhi_2</text> +</g> +<!-- MemMerge->Phi_224 --> +<g id="edge31" class="edge"> +<title>MemMerge->Phi_224</title> +<path fill="none" stroke="blue" d="M183.61,-196.3C179.42,-204.02 174.39,-213.29 169.72,-221.89"/> +<polygon fill="blue" stroke="blue" points="166.7,-220.11 165.01,-230.57 172.85,-223.45 166.7,-220.11"/> +<text text-anchor="middle" x="181.36" y="-198.45" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Stop3 --> -<g id="node2" class="node"> +<g id="node3" class="node"> <title>Stop3</title> -<polygon fill="yellow" stroke="black" points="186,-52 132,-52 132,-16 186,-16 186,-52"/> -<text text-anchor="middle" x="159" y="-30.3" font-family="Times,serif" font-size="14.00">Stop</text> +<polygon fill="yellow" stroke="black" points="220,-52 166,-52 166,-16 220,-16 220,-52"/> +<text text-anchor="middle" x="193" y="-28.95" font-family="Times New Roman,serif" font-size="14.00">Stop</text> </g> <!-- Return25 --> -<g id="node15" class="node"> +<g id="node16" class="node"> <title>Return25</title> -<polygon fill="yellow" stroke="black" points="192,-124 126,-124 126,-88 192,-88 192,-124"/> -<text text-anchor="middle" x="159" y="-102.3" font-family="Times,serif" font-size="14.00">Return</text> +<polygon fill="yellow" stroke="black" points="220,-124 166,-124 166,-88 220,-88 220,-124"/> +<text text-anchor="middle" x="193" y="-100.95" font-family="Times New Roman,serif" font-size="14.00">Return</text> </g> <!-- Stop3->Return25 --> <g id="edge1" class="edge"> <title>Stop3->Return25</title> -<path fill="none" stroke="red" d="M159,-52.3C159,-60.02 159,-69.29 159,-77.89"/> -<polygon fill="red" stroke="red" points="155.5,-77.9 159,-87.9 162.5,-77.9 155.5,-77.9"/> -<text text-anchor="middle" x="156" y="-54.9" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="red" d="M193,-52.3C193,-59.59 193,-68.27 193,-76.46"/> +<polygon fill="red" stroke="red" points="189.5,-76.38 193,-86.38 196.5,-76.38 189.5,-76.38"/> +<text text-anchor="middle" x="190.75" y="-54.45" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Con_4 --> -<g id="node3" class="node"> +<g id="node4" class="node"> <title>Con_4</title> -<ellipse fill="none" stroke="black" cx="87" cy="-494" rx="27" ry="18"/> -<text text-anchor="middle" x="87" y="-490.3" font-family="Times,serif" font-size="14.00">#0</text> +<ellipse fill="none" stroke="black" cx="235" cy="-569" rx="27" ry="18"/> +<text text-anchor="middle" x="235" y="-563.95" font-family="Times New Roman,serif" font-size="14.00">#0</text> </g> <!-- new9 --> -<g id="node4" class="node"> +<g id="node5" class="node"> <title>new9</title> -<ellipse fill="none" stroke="black" cx="191" cy="-566" rx="38.99" ry="18"/> -<text text-anchor="middle" x="191" y="-562.3" font-family="Times,serif" font-size="14.00">new S</text> +<ellipse fill="none" stroke="black" cx="70" cy="-641" rx="33.95" ry="18"/> +<text text-anchor="middle" x="70" y="-635.95" font-family="Times New Roman,serif" font-size="14.00">new S</text> </g> <!-- new9->Start2 --> <g id="edge2" class="edge"> <title>new9->Start2:p0</title> -<path fill="none" stroke="green" d="M178.53,-583.11C172.27,-592.49 165.49,-605 162.99,-618"/> -<polygon fill="green" stroke="green" points="159.51,-617.7 162,-628 166.47,-618.4 159.51,-617.7"/> -<text text-anchor="middle" x="175.53" y="-585.71" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="green" d="M87.44,-656.82C112.58,-676.72 161.43,-710.12 211.1,-714.72"/> +<polygon fill="green" stroke="green" points="210.71,-718.2 220.86,-715.18 211.04,-711.21 210.71,-718.2"/> +<text text-anchor="middle" x="89.69" y="-658.97" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Con_11 --> -<g id="node5" class="node"> +<g id="node6" class="node"> <title>Con_11</title> -<ellipse fill="none" stroke="black" cx="275" cy="-566" rx="27" ry="18"/> -<text text-anchor="middle" x="275" y="-562.3" font-family="Times,serif" font-size="14.00">#2</text> +<ellipse fill="none" stroke="black" cx="235" cy="-641" rx="27" ry="18"/> +<text text-anchor="middle" x="235" y="-635.95" font-family="Times New Roman,serif" font-size="14.00">#2</text> </g> <!-- Store12 --> -<g id="node6" class="node"> +<g id="node7" class="node"> <title>Store12</title> -<ellipse fill="none" stroke="black" cx="256" cy="-494" rx="27.9" ry="18"/> -<text text-anchor="middle" x="256" y="-490.3" font-family="Times,serif" font-size="14.00">.f =</text> +<ellipse fill="none" stroke="black" cx="163" cy="-569" rx="27" ry="18"/> +<text text-anchor="middle" x="163" y="-563.95" font-family="Times New Roman,serif" font-size="14.00">.f =</text> </g> <!-- Store12->Start2 --> <g id="edge3" class="edge"> <title>Store12->Start2:p0</title> -<path fill="none" stroke="red" d="M278.09,-505.5C299.78,-517.5 328.59,-538.88 321,-565"/> -<path fill="none" stroke="red" d="M321,-567C301.92,-632.61 187.71,-567.79 165.68,-610.25"/> -<polygon fill="red" stroke="red" points="162.32,-609.27 162.62,-619.86 168.99,-611.39 162.32,-609.27"/> -<text text-anchor="middle" x="281.09" y="-508.1" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="red" d="M184.74,-580.27C215.27,-594.76 267.54,-619.8 271,-623 277.43,-628.96 279.81,-631.32 281,-640"/> +<path fill="none" stroke="red" d="M281,-642C284.46,-667.2 257.45,-670.2 244.91,-685.38"/> +<polygon fill="red" stroke="red" points="241.97,-683.46 240.43,-693.94 248.17,-686.71 241.97,-683.46"/> +<text text-anchor="middle" x="182.49" y="-582.42" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Store12->new9 --> <g id="edge5" class="edge"> <title>Store12->new9</title> -<path fill="none" stroke="black" d="M242.22,-509.85C233.66,-519.06 222.49,-531.09 212.76,-541.57"/> -<polygon fill="black" stroke="black" points="209.91,-539.49 205.67,-549.2 215.04,-544.26 209.91,-539.49"/> -<text text-anchor="middle" x="239.22" y="-512.45" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="black" d="M145.51,-583.17C132.17,-593.2 113.54,-607.23 98.12,-618.83"/> +<polygon fill="black" stroke="black" points="96.11,-615.97 90.22,-624.78 100.32,-621.56 96.11,-615.97"/> +<text text-anchor="middle" x="143.26" y="-585.32" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Store12->Con_11 --> <g id="edge6" class="edge"> <title>Store12->Con_11</title> -<path fill="none" stroke="black" d="M260.6,-511.95C262.75,-519.86 265.35,-529.46 267.76,-538.31"/> -<polygon fill="black" stroke="black" points="264.45,-539.48 270.44,-548.21 271.2,-537.64 264.45,-539.48"/> -<text text-anchor="middle" x="257.6" y="-514.55" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> +<path fill="none" stroke="black" d="M177.92,-584.5C187.77,-594.08 200.86,-606.81 212.03,-617.66"/> +<polygon fill="black" stroke="black" points="209.41,-620 219.02,-624.46 214.29,-614.98 209.41,-620"/> +<text text-anchor="middle" x="175.67" y="-586.65" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> </g> <!-- new13 --> -<g id="node7" class="node"> +<g id="node8" class="node"> <title>new13</title> -<ellipse fill="none" stroke="black" cx="75" cy="-566" rx="38.99" ry="18"/> -<text text-anchor="middle" x="75" y="-562.3" font-family="Times,serif" font-size="14.00">new S</text> +<ellipse fill="none" stroke="black" cx="156" cy="-641" rx="33.95" ry="18"/> +<text text-anchor="middle" x="156" y="-635.95" font-family="Times New Roman,serif" font-size="14.00">new S</text> </g> <!-- new13->Start2 --> <g id="edge7" class="edge"> <title>new13->Start2:p0</title> -<path fill="none" stroke="green" d="M81.73,-584.06C90.25,-602.86 106.79,-631.28 130.97,-637.69"/> -<polygon fill="green" stroke="green" points="130.63,-641.17 141,-639 131.54,-634.23 130.63,-641.17"/> -<text text-anchor="middle" x="78.73" y="-586.66" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="green" d="M162.55,-658.99C170.89,-677.84 187.18,-706.54 211.2,-713.63"/> +<polygon fill="green" stroke="green" points="210.48,-717.06 220.88,-715.03 211.48,-710.13 210.48,-717.06"/> +<text text-anchor="middle" x="160.3" y="-661.14" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Store14 --> -<g id="node8" class="node"> +<g id="node9" class="node"> <title>Store14</title> -<ellipse fill="none" stroke="black" cx="133" cy="-422" rx="27.9" ry="18"/> -<text text-anchor="middle" x="133" y="-418.3" font-family="Times,serif" font-size="14.00">.f =</text> +<ellipse fill="none" stroke="black" cx="182" cy="-497" rx="27" ry="18"/> +<text text-anchor="middle" x="182" y="-491.95" font-family="Times New Roman,serif" font-size="14.00">.f =</text> </g> <!-- Store14->Start2 --> <g id="edge8" class="edge"> <title>Store14->Start2:p0</title> -<path fill="none" stroke="red" d="M133,-495C125.43,-534.08 132.78,-545.53 143,-584 146.42,-596.87 152.99,-602.52 157.44,-610.16"/> -<polygon fill="red" stroke="red" points="154.26,-611.64 161.18,-619.66 160.77,-609.07 154.26,-611.64"/> -<text text-anchor="middle" x="130" y="-488.6" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="red" d="M205.98,-505.45C226.83,-513.27 255.99,-527.73 271,-551 292.57,-584.45 275.58,-600.57 281,-640"/> +<text text-anchor="middle" x="203.73" y="-507.6" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Store14->Con_4 --> <g id="edge11" class="edge"> <title>Store14->Con_4</title> -<path fill="none" stroke="black" d="M122.56,-438.88C116.8,-447.66 109.52,-458.74 103.04,-468.58"/> -<polygon fill="black" stroke="black" points="99.96,-466.9 97.4,-477.18 105.81,-470.74 99.96,-466.9"/> -<text text-anchor="middle" x="119.56" y="-441.48" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> +<path fill="none" stroke="black" d="M193.76,-513.54C200.42,-522.33 208.89,-533.52 216.43,-543.47"/> +<polygon fill="black" stroke="black" points="213.53,-545.44 222.36,-551.3 219.11,-541.22 213.53,-545.44"/> +<text text-anchor="middle" x="191.51" y="-515.69" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> </g> <!-- Store14->Store12 --> <g id="edge9" class="edge"> <title>Store14->Store12</title> -<path fill="none" stroke="blue" d="M153.31,-434.56C173.34,-445.96 204.16,-463.5 226.81,-476.39"/> -<polygon fill="blue" stroke="blue" points="225.24,-479.52 235.66,-481.42 228.7,-473.44 225.24,-479.52"/> -<text text-anchor="middle" x="150.31" y="-437.16" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="blue" d="M177.4,-514.95C175.35,-522.51 172.87,-531.63 170.56,-540.15"/> +<polygon fill="blue" stroke="blue" points="167.2,-539.18 167.95,-549.75 173.95,-541.02 167.2,-539.18"/> +<text text-anchor="middle" x="175.15" y="-517.1" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Store14->new13 --> <g id="edge10" class="edge"> <title>Store14->new13</title> -<path fill="none" stroke="black" d="M134.81,-440.15C135.88,-454.43 136.45,-475.22 133,-493"/> -<path fill="none" stroke="black" d="M133,-495C129.28,-514.17 115.61,-531.16 102.48,-543.6"/> -<polygon fill="black" stroke="black" points="100.01,-541.11 94.89,-550.38 104.68,-546.33 100.01,-541.11"/> -<text text-anchor="middle" x="131.81" y="-442.75" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="black" d="M162.83,-510.26C150.03,-519.67 134.26,-533.91 127,-551 118.14,-571.85 127.53,-596.39 137.92,-614.41"/> +<polygon fill="black" stroke="black" points="134.8,-616.02 143.05,-622.65 140.74,-612.32 134.8,-616.02"/> +<text text-anchor="middle" x="160.58" y="-512.41" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Load16 --> -<g id="node9" class="node"> +<g id="node10" class="node"> <title>Load16</title> -<ellipse fill="none" stroke="black" cx="53" cy="-336" rx="27" ry="18"/> -<text text-anchor="middle" x="53" y="-332.3" font-family="Times,serif" font-size="14.00">.f</text> +<ellipse fill="none" stroke="black" cx="43" cy="-409.5" rx="27" ry="18"/> +<text text-anchor="middle" x="43" y="-404.45" font-family="Times New Roman,serif" font-size="14.00">.f</text> </g> <!-- Load16->Start2 --> <g id="edge12" class="edge"> <title>Load16->Start2:p0</title> -<path fill="none" stroke="red" d="M47.25,-353.59C38.09,-382.82 22.94,-444.94 41,-493"/> -<path fill="none" stroke="red" d="M41,-495C50.66,-517.36 30.14,-523.84 27,-548 24.94,-563.87 18.49,-570.45 27,-584 52.69,-624.92 76.85,-636.12 118.8,-638.47"/> -<polygon fill="red" stroke="red" points="118.8,-641.97 128.93,-638.86 119.08,-634.98 118.8,-641.97"/> -<text text-anchor="middle" x="44.25" y="-356.19" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="red" d="M49.4,-427.07C59.67,-455.78 77.82,-516.65 69,-568"/> +<path fill="none" stroke="red" d="M69,-570C64.24,-599.68 36.88,-594.62 27,-623 21.74,-638.11 17.38,-646.22 27,-659 50.34,-690 144.38,-709.24 199.08,-714.06"/> +<polygon fill="red" stroke="red" points="198.71,-717.54 208.93,-714.77 199.21,-710.56 198.71,-717.54"/> +<text text-anchor="middle" x="47.15" y="-429.22" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Load16->new9 --> <g id="edge14" class="edge"> <title>Load16->new9</title> -<path fill="none" stroke="black" d="M41,-495C44.08,-503.21 44.3,-506.35 51,-512 66.3,-524.9 113.87,-541.46 149.33,-552.61"/> -<polygon fill="black" stroke="black" points="148.39,-555.98 158.98,-555.6 150.47,-549.3 148.39,-555.98"/> -<text text-anchor="middle" x="38" y="-488.6" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="black" d="M69,-570C66.69,-583.43 66.6,-598.53 67.22,-611.26"/> +<polygon fill="black" stroke="black" points="63.72,-611.35 67.89,-621.09 70.7,-610.88 63.72,-611.35"/> +<text text-anchor="middle" x="66.75" y="-562.4" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Load16->Store14 --> <g id="edge13" class="edge"> <title>Load16->Store14</title> -<path fill="none" stroke="blue" d="M66.93,-351.63C79.36,-364.68 97.74,-383.97 111.95,-398.9"/> -<polygon fill="blue" stroke="blue" points="109.53,-401.43 118.96,-406.25 114.59,-396.6 109.53,-401.43"/> -<text text-anchor="middle" x="63.93" y="-354.23" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="blue" d="M62.33,-422.39C85.88,-436.87 125.88,-461.48 153.09,-478.22"/> +<polygon fill="blue" stroke="blue" points="151.2,-481.16 161.55,-483.42 154.86,-475.2 151.2,-481.16"/> +<text text-anchor="middle" x="64.58" y="-424.54" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- If17 --> -<g id="node10" class="node"> +<g id="node11" class="node"> <title>If17</title> -<polygon fill="yellow" stroke="transparent" points="218,-339 218,-364 312,-364 312,-339 218,-339"/> -<polygon fill="none" stroke="black" points="218,-339 218,-364 312,-364 312,-339 218,-339"/> -<text text-anchor="start" x="259.5" y="-347.8" font-family="Times,serif" font-size="14.00">If</text> -<polygon fill="none" stroke="black" points="218,-308 218,-339 312,-339 312,-308 218,-308"/> -<polygon fill="yellow" stroke="transparent" points="223,-313 223,-334 263,-334 263,-313 223,-313"/> -<polygon fill="none" stroke="black" points="223,-313 223,-334 263,-334 263,-313 223,-313"/> -<text text-anchor="start" x="226" y="-319.8" font-family="Times,serif" font-size="14.00">True</text> -<polygon fill="yellow" stroke="transparent" points="263,-313 263,-334 307,-334 307,-313 263,-313"/> -<polygon fill="none" stroke="black" points="263,-313 263,-334 307,-334 307,-313 263,-313"/> -<text text-anchor="start" x="266" y="-319.8" font-family="Times,serif" font-size="14.00">False</text> +<polygon fill="yellow" stroke="none" points="228,-412.5 228,-439 304,-439 304,-412.5 228,-412.5"/> +<polygon fill="none" stroke="black" points="228,-412.5 228,-439 304,-439 304,-412.5 228,-412.5"/> +<text text-anchor="start" x="261.5" y="-420.7" font-family="Times New Roman,serif" font-size="14.00">If</text> +<polygon fill="none" stroke="black" points="228,-380 228,-412.5 304,-412.5 304,-380 228,-380"/> +<polygon fill="yellow" stroke="none" points="233,-385 233,-407.5 264.5,-407.5 264.5,-385 233,-385"/> +<polygon fill="none" stroke="black" points="233,-385 233,-407.5 264.5,-407.5 264.5,-385 233,-385"/> +<text text-anchor="start" x="236" y="-391.2" font-family="Times New Roman,serif" font-size="14.00">True</text> +<polygon fill="yellow" stroke="none" points="264.5,-385 264.5,-407.5 299,-407.5 299,-385 264.5,-385"/> +<polygon fill="none" stroke="black" points="264.5,-385 264.5,-407.5 299,-407.5 299,-385 264.5,-385"/> +<text text-anchor="start" x="267.5" y="-391.2" font-family="Times New Roman,serif" font-size="14.00">False</text> </g> <!-- If17->Start2 --> <g id="edge15" class="edge"> <title>If17->Start2:p0</title> -<path fill="none" stroke="red" d="M283.07,-368.2C306.27,-412.32 341.25,-495.33 321,-565"/> -<text text-anchor="middle" x="280.07" y="-370.8" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="red" d="M311,-570C308.67,-603.77 276.11,-606.51 281,-640"/> +<path fill="none" stroke="red" d="M282.37,-442.96C296.46,-474.31 314.08,-523.46 311,-568"/> +<text text-anchor="middle" x="308.75" y="-562.4" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- If17->Start2 --> +<g id="edge16" class="edge"> +<title>If17->Start2:p2</title> +<path fill="none" stroke="black" d="M311,-570C310.66,-622.18 307.81,-639.78 307.14,-683.82"/> +<polygon fill="black" stroke="black" points="303.64,-683.74 307.04,-693.78 310.64,-683.81 303.64,-683.74"/> +<text text-anchor="middle" x="308.75" y="-572.15" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Con_21 --> -<g id="node11" class="node"> +<g id="node12" class="node"> <title>Con_21</title> -<ellipse fill="none" stroke="black" cx="125" cy="-336" rx="27" ry="18"/> -<text text-anchor="middle" x="125" y="-332.3" font-family="Times,serif" font-size="14.00">#1</text> +<ellipse fill="none" stroke="black" cx="155" cy="-409.5" rx="27" ry="18"/> +<text text-anchor="middle" x="155" y="-404.45" font-family="Times New Roman,serif" font-size="14.00">#1</text> </g> <!-- Store22 --> -<g id="node12" class="node"> +<g id="node13" class="node"> <title>Store22</title> -<ellipse fill="none" stroke="black" cx="159" cy="-250" rx="27.9" ry="18"/> -<text text-anchor="middle" x="159" y="-246.3" font-family="Times,serif" font-size="14.00">.f =</text> +<ellipse fill="none" stroke="black" cx="155" cy="-322" rx="27" ry="18"/> +<text text-anchor="middle" x="155" y="-316.95" font-family="Times New Roman,serif" font-size="14.00">.f =</text> </g> <!-- Store22->new9 --> <g id="edge19" class="edge"> <title>Store22->new9</title> -<path fill="none" stroke="black" d="M191,-337C192.12,-409.45 191.64,-494.88 191.27,-537.67"/> -<polygon fill="black" stroke="black" points="187.77,-537.81 191.18,-547.84 194.77,-537.87 187.77,-537.81"/> -<text text-anchor="middle" x="188" y="-330.6" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="black" d="M137.84,-335.86C126.26,-345.54 111.73,-359.88 104,-376 93.96,-396.94 78.72,-547.81 72.64,-611.62"/> +<polygon fill="black" stroke="black" points="69.18,-611.02 71.72,-621.31 76.15,-611.68 69.18,-611.02"/> +<text text-anchor="middle" x="135.59" y="-338.01" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Store22->Store14 --> <g id="edge18" class="edge"> <title>Store22->Store14</title> -<path fill="none" stroke="blue" d="M168.96,-267.05C178.21,-283.46 190.61,-310.03 191,-335"/> -<path fill="none" stroke="blue" d="M191,-337C191.39,-361.97 173.77,-384.91 157.85,-400.48"/> -<polygon fill="blue" stroke="blue" points="155.22,-398.15 150.26,-407.51 159.98,-403.28 155.22,-398.15"/> -<text text-anchor="middle" x="165.96" y="-269.65" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="blue" d="M168.75,-337.93C176.96,-347.9 186.62,-361.75 191,-376 200.34,-406.37 195.4,-443.02 189.86,-467.76"/> +<polygon fill="blue" stroke="blue" points="186.48,-466.85 187.51,-477.4 193.28,-468.51 186.48,-466.85"/> +<text text-anchor="middle" x="166.5" y="-340.08" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Store22->Load16 --> <g id="edge21" class="edge"> <title>Store22->Load16</title> -<path fill="none" stroke="black" d="M141.22,-263.98C126.92,-274.53 106.43,-289.92 89,-304 84.86,-307.35 80.51,-310.98 76.33,-314.54"/> -<polygon fill="black" stroke="black" points="73.89,-312.03 68.59,-321.2 78.45,-317.33 73.89,-312.03"/> -<text text-anchor="middle" x="138.22" y="-266.58" font-family="Helvetica,sans-Serif" font-size="8.00">4</text> +<path fill="none" stroke="black" d="M137.39,-335.93C122.95,-346.57 102.04,-362.12 84,-376 78.96,-379.88 73.61,-384.06 68.5,-388.1"/> +<polygon fill="black" stroke="black" points="66.51,-385.21 60.86,-394.17 70.86,-390.69 66.51,-385.21"/> +<text text-anchor="middle" x="139.64" y="-338.08" font-family="Helvetica,sans-Serif" font-size="8.00">4</text> </g> <!-- Store22->If17 --> <g id="edge17" class="edge"> <title>Store22->If17:p0</title> -<path fill="none" stroke="red" d="M170.65,-266.62C182.83,-282.67 201.77,-306.77 213.47,-317.51"/> -<polygon fill="red" stroke="red" points="211.7,-320.53 222,-323 215.49,-314.64 211.7,-320.53"/> -<text text-anchor="middle" x="173.65" y="-269.22" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="red" d="M169.18,-337.47C184.82,-353.32 209.52,-378.02 222.95,-389.7"/> +<polygon fill="red" stroke="red" points="220.62,-392.34 230.77,-395.36 224.72,-386.67 220.62,-392.34"/> +<text text-anchor="middle" x="171.43" y="-339.62" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Store22->Con_21 --> <g id="edge20" class="edge"> <title>Store22->Con_21</title> -<path fill="none" stroke="black" d="M152.28,-267.6C147.49,-279.45 140.94,-295.62 135.46,-309.15"/> -<polygon fill="black" stroke="black" points="132.21,-307.86 131.7,-318.44 138.7,-310.48 132.21,-307.86"/> -<text text-anchor="middle" x="149.28" y="-270.2" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> -</g> -<!-- Region23 --> -<g id="node13" class="node"> -<title>Region23</title> -<polygon fill="yellow" stroke="black" points="276,-196 210,-196 210,-160 276,-160 276,-196"/> -<text text-anchor="middle" x="243" y="-174.3" font-family="Times,serif" font-size="14.00">Region</text> +<path fill="none" stroke="black" d="M155,-340.3C155,-351.66 155,-366.78 155,-379.9"/> +<polygon fill="black" stroke="black" points="151.5,-379.71 155,-389.71 158.5,-379.71 151.5,-379.71"/> +<text text-anchor="middle" x="152.75" y="-342.45" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> </g> <!-- Region23->If17 --> <g id="edge22" class="edge"> <title>Region23->If17:p0</title> -<path fill="none" stroke="red" d="M243,-196.22C243,-219.49 243,-262.58 243,-301.64"/> -<polygon fill="red" stroke="red" points="239.5,-302 243,-312 246.5,-302 239.5,-302"/> -<text text-anchor="middle" x="240" y="-198.82" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="red" d="M252.1,-268.21C250.96,-291.28 249.12,-333.83 248.8,-372.64"/> +<polygon fill="red" stroke="red" points="245.3,-372.47 248.76,-382.49 252.3,-372.5 245.3,-372.47"/> +<text text-anchor="middle" x="249.85" y="-270.36" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Region23->If17 --> <g id="edge23" class="edge"> <title>Region23->If17:p1</title> -<path fill="none" stroke="red" d="M252.84,-196.18C258.29,-206.33 264.79,-219.58 269,-232 279.46,-262.85 284.04,-273.57 284.86,-301.73"/> -<polygon fill="red" stroke="red" points="281.37,-302.05 285,-312 288.37,-301.95 281.37,-302.05"/> -<text text-anchor="middle" x="249.84" y="-198.78" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> -</g> -<!-- Phi_224 --> -<g id="node14" class="node"> -<title>Phi_224</title> -<ellipse fill="lightyellow" stroke="black" cx="159" cy="-178" rx="33.29" ry="18"/> -<text text-anchor="middle" x="159" y="-174.3" font-family="Times,serif" font-size="14.00">φ_$2</text> +<path fill="none" stroke="red" d="M259.4,-268.46C262.95,-278.7 267.2,-291.95 270,-304 277.21,-335.04 280.87,-345.68 281.61,-372.75"/> +<polygon fill="red" stroke="red" points="278.11,-372.53 281.73,-382.49 285.11,-372.44 278.11,-372.53"/> +<text text-anchor="middle" x="257.15" y="-270.61" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Phi_224->Store14 --> <g id="edge26" class="edge"> <title>Phi_224->Store14</title> -<path fill="none" stroke="blue" d="M173.5,-194.31C181.86,-204.22 191.56,-217.87 196,-232 200.8,-247.26 198.68,-252.23 196,-268 192.06,-291.21 170.41,-346.42 161,-368 156.97,-377.25 151.97,-387.09 147.32,-395.75"/> -<polygon fill="blue" stroke="blue" points="144.21,-394.15 142.48,-404.6 150.35,-397.51 144.21,-394.15"/> -<text text-anchor="middle" x="170.5" y="-196.91" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="blue" d="M170.61,-267.54C186.09,-286.03 205.55,-316.46 191,-340 172.19,-370.43 137.81,-345.57 119,-376 103.34,-401.33 106.43,-416 119,-443 125.92,-457.87 139.3,-470.06 151.95,-478.98"/> +<polygon fill="blue" stroke="blue" points="149.82,-481.77 160.1,-484.33 153.66,-475.91 149.82,-481.77"/> +<text text-anchor="middle" x="168.36" y="-269.69" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Phi_224->Store22 --> <g id="edge25" class="edge"> <title>Phi_224->Store22</title> -<path fill="none" stroke="blue" d="M159,-196.3C159,-204.02 159,-213.29 159,-221.89"/> -<polygon fill="blue" stroke="blue" points="155.5,-221.9 159,-231.9 162.5,-221.9 155.5,-221.9"/> -<text text-anchor="middle" x="156" y="-198.9" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="blue" d="M155,-268.3C155,-275.59 155,-284.27 155,-292.46"/> +<polygon fill="blue" stroke="blue" points="151.5,-292.38 155,-302.38 158.5,-292.38 151.5,-292.38"/> +<text text-anchor="middle" x="152.75" y="-270.45" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Phi_224->Region23 --> <g id="edge24" class="edge"> <title>Phi_224->Region23</title> -<path fill="none" stroke="black" stroke-dasharray="1,5" d="M192.47,-178C194.91,-178 197.35,-178 199.8,-178"/> -<polygon fill="black" stroke="black" points="199.84,-181.5 209.84,-178 199.84,-174.5 199.84,-181.5"/> -<text text-anchor="middle" x="195.47" y="-171.6" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="black" stroke-dasharray="1,5" d="M208.21,-250C210.05,-250 211.89,-250 213.72,-250"/> +<polygon fill="black" stroke="black" points="213.49,-253.5 223.49,-250 213.49,-246.5 213.49,-253.5"/> +<text text-anchor="middle" x="210.46" y="-242.4" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Return25->MemMerge --> +<g id="edge29" class="edge"> +<title>Return25->MemMerge</title> +<path fill="none" stroke="blue" d="M193,-124.3C193,-131.59 193,-140.27 193,-148.46"/> +<polygon fill="blue" stroke="blue" points="189.5,-148.38 193,-158.38 196.5,-148.38 189.5,-148.38"/> +<text text-anchor="middle" x="190.75" y="-126.45" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Return25->Load16 --> <g id="edge28" class="edge"> <title>Return25->Load16</title> -<path fill="none" stroke="black" d="M143.1,-124.14C134.51,-134.05 124.23,-147.09 117,-160 89.5,-209.12 69.82,-272.31 60.03,-307.76"/> -<polygon fill="black" stroke="black" points="56.54,-307.25 57.31,-317.82 63.3,-309.08 56.54,-307.25"/> -<text text-anchor="middle" x="140.1" y="-126.74" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="black" d="M175.65,-124.07C165.7,-134.18 153.2,-147.43 143,-160 118.45,-190.25 109.65,-196.78 93,-232 69.52,-281.67 54.98,-344.54 48,-380.27"/> +<polygon fill="black" stroke="black" points="44.61,-379.32 46.19,-389.8 51.49,-380.62 44.61,-379.32"/> +<text text-anchor="middle" x="173.4" y="-126.22" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Return25->Region23 --> <g id="edge27" class="edge"> <title>Return25->Region23</title> -<path fill="none" stroke="red" d="M179.76,-124.3C190.24,-133.03 203.11,-143.76 214.5,-153.25"/> -<polygon fill="red" stroke="red" points="212.55,-156.18 222.47,-159.9 217.03,-150.81 212.55,-156.18"/> -<text text-anchor="middle" x="176.76" y="-126.9" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> -</g> -<!-- Return25->Phi_224 --> -<g id="edge29" class="edge"> -<title>Return25->Phi_224</title> -<path fill="none" stroke="blue" d="M159,-124.3C159,-132.02 159,-141.29 159,-149.89"/> -<polygon fill="blue" stroke="blue" points="155.5,-149.9 159,-159.9 162.5,-149.9 155.5,-149.9"/> -<text text-anchor="middle" x="156" y="-126.9" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="red" d="M214.94,-124.32C225.23,-133.72 236.62,-146.23 243,-160 251.76,-178.89 254.09,-202.36 254.32,-220.46"/> +<polygon fill="red" stroke="red" points="250.82,-220.19 254.22,-230.23 257.82,-220.26 250.82,-220.19"/> +<text text-anchor="middle" x="212.69" y="-126.47" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> </g> </svg> diff --git a/chapter11/docs/graph4.gv b/chapter11/docs/graph4.gv index f6958ccb..cf622aa1 100644 --- a/chapter11/docs/graph4.gv +++ b/chapter11/docs/graph4.gv @@ -21,11 +21,12 @@ return v; <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></TR> + <TR><TD PORT="p0" BGCOLOR="yellow">$ctrl</TD><TD PORT="p1">$mem</TD><TD PORT="p2">arg</TD></TR> </TABLE> </TD></TR> </TABLE>> ]; + MemMerge [ shape=box label="MemMerge" ]; Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; Con_4 [ label="#0" ]; new9 [ label="new S" ]; @@ -57,10 +58,10 @@ return v; </TD></TR> </TABLE>> ]; - Phi_230 [ style=filled fillcolor=lightyellow label="φ_$2" ]; + Phi_230 [ style=filled fillcolor=lightyellow label="MemPhi_2" ]; Store31 [ label=".f =" ]; Region32 [ shape=box style=filled fillcolor=yellow label="Region" ]; - Phi_233 [ style=filled fillcolor=lightyellow label="φ_$2" ]; + Phi_233 [ style=filled fillcolor=lightyellow label="MemPhi_2" ]; Sub35 [ label="-" ]; Con_36 [ label="#1" ]; Return37 [ shape=box style=filled fillcolor=yellow label="Return" ]; @@ -70,19 +71,19 @@ return v; edge [ fontname=Helvetica, fontsize=8 ]; Stop3 -> Return37[taillabel=0 color=red]; new9 -> Start2:p0[taillabel=0 color=green]; - Store10 -> Start2:p2[taillabel=1 color=blue]; + Store10 -> Start2:p1[taillabel=1 color=blue]; Store10 -> new9[taillabel=2]; Store10 -> Con_4[taillabel=3]; Loop11 -> Start2:p0[taillabel=1 color=red]; Loop11 -> Region32[taillabel=2 color=red constraint=false]; Phi_arg13 -> Loop11 [style=dotted taillabel=0]; - Phi_arg13 -> Start2:p1[taillabel=1]; + Phi_arg13 -> Start2:p2[taillabel=1]; Phi_arg13 -> Sub35[taillabel=2 constraint=false]; LT14 -> Con_4[taillabel=1]; LT14 -> Phi_arg13[taillabel=2]; If16 -> Loop11[taillabel=0 color=red]; If16 -> LT14[taillabel=1]; - Div21 -> Start2:p1[taillabel=1]; + Div21 -> Start2:p2[taillabel=1]; Div21 -> Con_22[taillabel=2]; EQ23 -> Phi_arg13[taillabel=1]; EQ23 -> Con_24[taillabel=2]; @@ -102,6 +103,8 @@ return v; Sub35 -> Phi_arg13[taillabel=1]; Sub35 -> Con_36[taillabel=2]; Return37 -> If16:p1[taillabel=0 color=red]; - Return37 -> new9[taillabel=1]; - Return37 -> Phi_230[taillabel=2 color=blue]; + Return37 -> new9[taillabel=2]; + Return37 -> MemMerge[taillabel=1 color=blue]; + MemMerge -> Start2:p1[taillabel=1 color=blue]; + MemMerge -> Phi_230[taillabel=2 color=blue]; } diff --git a/chapter11/docs/graph4.svg b/chapter11/docs/graph4.svg index 7f392664..2730813a 100644 --- a/chapter11/docs/graph4.svg +++ b/chapter11/docs/graph4.svg @@ -1,408 +1,429 @@ <?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) +<!-- Generated by graphviz version 12.2.0 (20241103.1931) --> <!-- Title: chapter11 Pages: 1 --> -<svg width="454pt" height="680pt" - viewBox="0.00 0.00 454.00 680.14" 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 676.14)"> +<svg width="510pt" height="689pt" + viewBox="0.00 0.00 510.00 688.57" 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 684.57)"> <title>chapter11</title> -<polygon fill="white" stroke="transparent" points="-4,4 -4,-676.14 450,-676.14 450,4 -4,4"/> +<polygon fill="white" stroke="none" points="-4,4 -4,-684.57 506,-684.57 506,4 -4,4"/> <g id="clust1" class="cluster"> <title>cluster_Nodes</title> -<polygon fill="none" stroke="black" points="8,-24.14 8,-664.14 438,-664.14 438,-24.14 8,-24.14"/> +<polygon fill="none" stroke="black" points="8,-23.57 8,-672.57 494,-672.57 494,-23.57 8,-23.57"/> </g> <!-- Start2 --> <g id="node1" class="node"> <title>Start2</title> -<polygon fill="yellow" stroke="transparent" points="129,-627.14 129,-652.14 235,-652.14 235,-627.14 129,-627.14"/> -<polygon fill="none" stroke="black" points="129,-627.14 129,-652.14 235,-652.14 235,-627.14 129,-627.14"/> -<text text-anchor="start" x="163.5" y="-635.94" font-family="Times,serif" font-size="14.00">Start</text> -<polygon fill="none" stroke="black" points="129,-596.14 129,-627.14 235,-627.14 235,-596.14 129,-596.14"/> -<polygon fill="yellow" stroke="transparent" points="134,-601.14 134,-622.14 174,-622.14 174,-601.14 134,-601.14"/> -<polygon fill="none" stroke="black" points="134,-601.14 134,-622.14 174,-622.14 174,-601.14 134,-601.14"/> -<text text-anchor="start" x="137" y="-607.94" font-family="Times,serif" font-size="14.00">$ctrl</text> -<polygon fill="none" stroke="black" points="174,-601.14 174,-622.14 205,-622.14 205,-601.14 174,-601.14"/> -<text text-anchor="start" x="177" y="-607.94" font-family="Times,serif" font-size="14.00">arg</text> -<polygon fill="none" stroke="black" points="205,-601.14 205,-622.14 230,-622.14 230,-601.14 205,-601.14"/> -<text text-anchor="start" x="208" y="-607.94" font-family="Times,serif" font-size="14.00">$2</text> +<polygon fill="yellow" stroke="none" points="177.38,-634.07 177.38,-660.57 282.62,-660.57 282.62,-634.07 177.38,-634.07"/> +<polygon fill="none" stroke="black" points="177.38,-634.07 177.38,-660.57 282.62,-660.57 282.62,-634.07 177.38,-634.07"/> +<text text-anchor="start" x="217.25" y="-642.27" font-family="Times New Roman,serif" font-size="14.00">Start</text> +<polygon fill="none" stroke="black" points="177.38,-601.57 177.38,-634.07 282.62,-634.07 282.62,-601.57 177.38,-601.57"/> +<polygon fill="yellow" stroke="none" points="182.38,-606.57 182.38,-629.07 213.12,-629.07 213.12,-606.57 182.38,-606.57"/> +<polygon fill="none" stroke="black" points="182.38,-606.57 182.38,-629.07 213.12,-629.07 213.12,-606.57 182.38,-606.57"/> +<text text-anchor="start" x="185.38" y="-612.77" font-family="Times New Roman,serif" font-size="14.00">$ctrl</text> +<polygon fill="none" stroke="black" points="213.12,-606.57 213.12,-629.07 254.38,-629.07 254.38,-606.57 213.12,-606.57"/> +<text text-anchor="start" x="216.12" y="-612.77" font-family="Times New Roman,serif" font-size="14.00">$mem</text> +<polygon fill="none" stroke="black" points="254.38,-606.57 254.38,-629.07 277.62,-629.07 277.62,-606.57 254.38,-606.57"/> +<text text-anchor="start" x="257.38" y="-612.77" font-family="Times New Roman,serif" font-size="14.00">arg</text> +</g> +<!-- MemMerge --> +<g id="node2" class="node"> +<title>MemMerge</title> +<polygon fill="none" stroke="black" points="190,-345.57 108,-345.57 108,-309.57 190,-309.57 190,-345.57"/> +<text text-anchor="middle" x="149" y="-322.52" font-family="Times New Roman,serif" font-size="14.00">MemMerge</text> +</g> +<!-- MemMerge->Start2 --> +<g id="edge37" class="edge"> +<title>MemMerge->Start2:p1</title> +<path fill="none" stroke="blue" d="M182.26,-345.95C196.56,-354.9 212.4,-367 223,-381.57 271.6,-448.32 296.36,-483.66 269,-561.57 262.19,-580.96 242.83,-581.28 236.1,-594.6"/> +<polygon fill="blue" stroke="blue" points="232.74,-593.57 234.07,-604.09 239.58,-595.04 232.74,-593.57"/> +<text text-anchor="middle" x="180.01" y="-348.1" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Phi_230 --> +<g id="node16" class="node"> +<title>Phi_230</title> +<ellipse fill="lightyellow" stroke="black" cx="161" cy="-399.57" rx="52.89" ry="18"/> +<text text-anchor="middle" x="161" y="-394.52" font-family="Times New Roman,serif" font-size="14.00">MemPhi_2</text> +</g> +<!-- MemMerge->Phi_230 --> +<g id="edge38" class="edge"> +<title>MemMerge->Phi_230</title> +<path fill="none" stroke="blue" d="M151.97,-345.87C153.23,-353.24 154.74,-362.04 156.16,-370.32"/> +<polygon fill="blue" stroke="blue" points="152.67,-370.7 157.81,-379.97 159.57,-369.52 152.67,-370.7"/> +<text text-anchor="middle" x="149.72" y="-348.02" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Stop3 --> -<g id="node2" class="node"> +<g id="node3" class="node"> <title>Stop3</title> -<polygon fill="yellow" stroke="black" points="189,-68.14 135,-68.14 135,-32.14 189,-32.14 189,-68.14"/> -<text text-anchor="middle" x="162" y="-46.44" font-family="Times,serif" font-size="14.00">Stop</text> +<polygon fill="yellow" stroke="black" points="194,-67.57 140,-67.57 140,-31.57 194,-31.57 194,-67.57"/> +<text text-anchor="middle" x="167" y="-44.52" font-family="Times New Roman,serif" font-size="14.00">Stop</text> </g> <!-- Return37 --> -<g id="node21" class="node"> +<g id="node22" class="node"> <title>Return37</title> -<polygon fill="yellow" stroke="black" points="193,-154.14 127,-154.14 127,-118.14 193,-118.14 193,-154.14"/> -<text text-anchor="middle" x="160" y="-132.44" font-family="Times,serif" font-size="14.00">Return</text> +<polygon fill="yellow" stroke="black" points="183,-155.07 129,-155.07 129,-119.07 183,-119.07 183,-155.07"/> +<text text-anchor="middle" x="156" y="-132.02" font-family="Times New Roman,serif" font-size="14.00">Return</text> </g> <!-- Stop3->Return37 --> <g id="edge1" class="edge"> <title>Stop3->Return37</title> -<path fill="none" stroke="red" d="M161.59,-68.55C161.32,-79.89 160.96,-94.92 160.65,-107.83"/> -<polygon fill="red" stroke="red" points="157.15,-107.97 160.41,-118.05 164.14,-108.13 157.15,-107.97"/> -<text text-anchor="middle" x="158.59" y="-71.15" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="red" d="M164.77,-67.87C163.31,-79.22 161.37,-94.35 159.68,-107.47"/> +<polygon fill="red" stroke="red" points="156.22,-106.92 158.42,-117.29 163.16,-107.82 156.22,-106.92"/> +<text text-anchor="middle" x="162.52" y="-70.02" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Con_4 --> -<g id="node3" class="node"> +<g id="node4" class="node"> <title>Con_4</title> -<ellipse fill="none" stroke="black" cx="264" cy="-538.14" rx="27" ry="18"/> -<text text-anchor="middle" x="264" y="-534.44" font-family="Times,serif" font-size="14.00">#0</text> +<ellipse fill="none" stroke="black" cx="233" cy="-543.57" rx="27" ry="18"/> +<text text-anchor="middle" x="233" y="-538.52" font-family="Times New Roman,serif" font-size="14.00">#0</text> </g> <!-- new9 --> -<g id="node4" class="node"> +<g id="node5" class="node"> <title>new9</title> -<ellipse fill="none" stroke="black" cx="55" cy="-538.14" rx="38.99" ry="18"/> -<text text-anchor="middle" x="55" y="-534.44" font-family="Times,serif" font-size="14.00">new S</text> +<ellipse fill="none" stroke="black" cx="134" cy="-543.57" rx="33.95" ry="18"/> +<text text-anchor="middle" x="134" y="-538.52" font-family="Times New Roman,serif" font-size="14.00">new S</text> </g> <!-- new9->Start2 --> <g id="edge2" class="edge"> <title>new9->Start2:p0</title> -<path fill="none" stroke="green" d="M63.59,-555.84C74.57,-574.87 95.49,-604.11 123,-610.06"/> -<polygon fill="green" stroke="green" points="122.68,-613.55 133,-611.14 123.43,-606.59 122.68,-613.55"/> -<text text-anchor="middle" x="60.59" y="-558.44" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="green" d="M137.27,-561.92C141.67,-580.29 151.26,-607.68 170.22,-615.6"/> +<polygon fill="green" stroke="green" points="169.4,-619 179.89,-617.52 170.76,-612.14 169.4,-619"/> +<text text-anchor="middle" x="135.02" y="-564.07" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Store10 --> -<g id="node5" class="node"> +<g id="node6" class="node"> <title>Store10</title> -<ellipse fill="none" stroke="black" cx="218" cy="-466.14" rx="27.9" ry="18"/> -<text text-anchor="middle" x="218" y="-462.44" font-family="Times,serif" font-size="14.00">.f =</text> +<ellipse fill="none" stroke="black" cx="174" cy="-471.57" rx="27" ry="18"/> +<text text-anchor="middle" x="174" y="-466.52" font-family="Times New Roman,serif" font-size="14.00">.f =</text> </g> <!-- Store10->Start2 --> <g id="edge3" class="edge"> -<title>Store10->Start2:p2</title> -<path fill="none" stroke="blue" d="M218,-484.36C218,-507.63 218,-550.72 218,-589.78"/> -<polygon fill="blue" stroke="blue" points="214.5,-590.14 218,-600.14 221.5,-590.14 214.5,-590.14"/> -<text text-anchor="middle" x="215" y="-486.96" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<title>Store10->Start2:p1</title> +<path fill="none" stroke="blue" d="M176.14,-489.77C178.97,-508.31 185.04,-538.28 197,-561.57 206.55,-580.16 225.16,-580.71 231.54,-594.37"/> +<polygon fill="blue" stroke="blue" points="228.09,-594.95 233.46,-604.08 234.95,-593.59 228.09,-594.95"/> +<text text-anchor="middle" x="173.89" y="-491.92" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Store10->Con_4 --> <g id="edge5" class="edge"> <title>Store10->Con_4</title> -<path fill="none" stroke="black" d="M228.44,-483.03C234.2,-491.8 241.48,-502.88 247.96,-512.73"/> -<polygon fill="black" stroke="black" points="245.19,-514.89 253.6,-521.32 251.04,-511.04 245.19,-514.89"/> -<text text-anchor="middle" x="225.44" y="-485.63" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> +<path fill="none" stroke="black" d="M186.8,-487.75C194.46,-496.84 204.35,-508.57 213.01,-518.85"/> +<polygon fill="black" stroke="black" points="210.13,-520.86 219.25,-526.25 215.48,-516.35 210.13,-520.86"/> +<text text-anchor="middle" x="184.55" y="-489.9" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> </g> <!-- Store10->new9 --> <g id="edge4" class="edge"> <title>Store10->new9</title> -<path fill="none" stroke="black" d="M195.75,-477.51C190.9,-479.73 185.79,-482.04 181,-484.14 151.47,-497.13 117.77,-511.27 92.65,-521.68"/> -<polygon fill="black" stroke="black" points="91.04,-518.56 83.14,-525.61 93.72,-525.03 91.04,-518.56"/> -<text text-anchor="middle" x="192.75" y="-480.11" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="black" d="M164.72,-488.8C160.09,-496.9 154.38,-506.9 149.14,-516.07"/> +<polygon fill="black" stroke="black" points="146.18,-514.2 144.26,-524.62 152.26,-517.67 146.18,-514.2"/> +<text text-anchor="middle" x="162.47" y="-490.95" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Loop11 --> -<g id="node6" class="node"> +<g id="node7" class="node"> <title>Loop11</title> -<polygon fill="yellow" stroke="black" points="430,-412.14 376,-412.14 376,-376.14 430,-376.14 430,-412.14"/> -<text text-anchor="middle" x="403" y="-390.44" font-family="Times,serif" font-size="14.00">Loop</text> +<polygon fill="yellow" stroke="black" points="326,-417.57 272,-417.57 272,-381.57 326,-381.57 326,-417.57"/> +<text text-anchor="middle" x="299" y="-394.52" font-family="Times New Roman,serif" font-size="14.00">Loop</text> </g> <!-- Loop11->Start2 --> <g id="edge6" class="edge"> <title>Loop11->Start2:p0</title> -<path fill="none" stroke="red" d="M398.43,-412.15C388.69,-445.12 362.68,-517.81 314,-556.14 259,-599.46 164.25,-533.74 154.77,-590.03"/> -<polygon fill="red" stroke="red" points="151.27,-589.91 154,-600.14 158.25,-590.44 151.27,-589.91"/> -<text text-anchor="middle" x="395.43" y="-414.75" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="red" d="M302.41,-417.85C307.63,-449.41 314.1,-517.19 284,-561.57 262.39,-593.42 210.43,-569.06 199.7,-594.37"/> +<polygon fill="red" stroke="red" points="196.28,-593.62 198.01,-604.07 203.18,-594.83 196.28,-593.62"/> +<text text-anchor="middle" x="300.16" y="-420" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Region32 --> -<g id="node17" class="node"> +<g id="node18" class="node"> <title>Region32</title> -<polygon fill="yellow" stroke="black" points="278,-68.14 212,-68.14 212,-32.14 278,-32.14 278,-68.14"/> -<text text-anchor="middle" x="245" y="-46.44" font-family="Times,serif" font-size="14.00">Region</text> +<polygon fill="yellow" stroke="black" points="277.5,-67.57 222.5,-67.57 222.5,-31.57 277.5,-31.57 277.5,-67.57"/> +<text text-anchor="middle" x="250" y="-44.52" font-family="Times New Roman,serif" font-size="14.00">Region</text> </g> <!-- Loop11->Region32 --> <g id="edge7" class="edge"> <title>Loop11->Region32</title> -<path fill="none" stroke="red" d="M375.77,-379.43C372.85,-378.21 369.89,-377.09 367,-376.14 287.4,-350.06 245.88,-394.32 182,-340.14 148.1,-311.39 72.96,-171.74 118,-104.14 120.57,-100.29 166.66,-81.64 202.59,-67.55"/> -<polygon fill="red" stroke="red" points="203.9,-70.79 211.94,-63.89 201.35,-64.27 203.9,-70.79"/> -<text text-anchor="middle" x="372.77" y="-373.03" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="red" d="M294.37,-381.1C287.79,-356.73 275.97,-314.9 271,-309.57 241.37,-277.72 212.98,-301.92 180,-273.57 139.82,-239.03 133.81,-221.71 120,-170.57 112.24,-141.82 103.39,-128.28 120,-103.57 122.76,-99.46 174.37,-79.22 211.72,-64.98"/> +<polygon fill="red" stroke="red" points="212.79,-68.32 220.89,-61.49 210.3,-61.77 212.79,-68.32"/> +<text text-anchor="middle" x="292.12" y="-373.5" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Phi_arg13 --> -<g id="node7" class="node"> +<g id="node8" class="node"> <title>Phi_arg13</title> -<ellipse fill="lightyellow" stroke="black" cx="321" cy="-394.14" rx="37.09" ry="18"/> -<text text-anchor="middle" x="321" y="-390.44" font-family="Times,serif" font-size="14.00">φ_arg</text> +<ellipse fill="lightyellow" stroke="black" cx="377" cy="-399.57" rx="32.93" ry="18"/> +<text text-anchor="middle" x="377" y="-394.52" font-family="Times New Roman,serif" font-size="14.00">φ_arg</text> </g> <!-- Phi_arg13->Start2 --> <g id="edge9" class="edge"> -<title>Phi_arg13->Start2:p1</title> -<path fill="none" stroke="black" d="M323.94,-412.28C328.4,-444.2 333.18,-513.39 300,-556.14 270.11,-594.65 201.29,-554.44 191.23,-590.04"/> -<polygon fill="black" stroke="black" points="187.74,-589.79 190,-600.14 194.69,-590.64 187.74,-589.79"/> -<text text-anchor="middle" x="320.94" y="-414.88" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<title>Phi_arg13->Start2:p2</title> +<path fill="none" stroke="black" d="M377.05,-417.98C376.25,-467.21 367.08,-604.69 289.96,-616.94"/> +<polygon fill="black" stroke="black" points="289.83,-613.44 280.13,-617.7 290.37,-620.42 289.83,-613.44"/> +<text text-anchor="middle" x="374.8" y="-420.13" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Phi_arg13->Loop11 --> <g id="edge8" class="edge"> <title>Phi_arg13->Loop11</title> -<path fill="none" stroke="black" stroke-dasharray="1,5" d="M358.16,-394.14C360.62,-394.14 363.08,-394.14 365.55,-394.14"/> -<polygon fill="black" stroke="black" points="365.67,-397.64 375.67,-394.14 365.67,-390.64 365.67,-397.64"/> -<text text-anchor="middle" x="361.16" y="-387.74" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="black" stroke-dasharray="1,5" d="M343.81,-399.57C341.82,-399.57 339.82,-399.57 337.83,-399.57"/> +<polygon fill="black" stroke="black" points="337.94,-396.07 327.94,-399.57 337.94,-403.07 337.94,-396.07"/> +<text text-anchor="middle" x="341.56" y="-391.97" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Sub35 --> -<g id="node19" class="node"> +<g id="node20" class="node"> <title>Sub35</title> -<ellipse fill="none" stroke="black" cx="218" cy="-322.14" rx="27" ry="18"/> -<text text-anchor="middle" x="218" y="-318.44" font-family="Times,serif" font-size="14.00">-</text> +<ellipse fill="none" stroke="black" cx="459" cy="-327.57" rx="27" ry="18"/> +<text text-anchor="middle" x="459" y="-322.52" font-family="Times New Roman,serif" font-size="14.00">-</text> </g> <!-- LT14 --> -<g id="node8" class="node"> +<g id="node9" class="node"> <title>LT14</title> -<ellipse fill="none" stroke="black" cx="290" cy="-322.14" rx="27" ry="18"/> -<text text-anchor="middle" x="290" y="-318.44" font-family="Times,serif" font-size="14.00"><</text> +<ellipse fill="none" stroke="black" cx="235" cy="-327.57" rx="27" ry="18"/> +<text text-anchor="middle" x="235" y="-322.52" font-family="Times New Roman,serif" font-size="14.00"><</text> </g> <!-- LT14->Con_4 --> <g id="edge11" class="edge"> <title>LT14->Con_4</title> -<path fill="none" stroke="black" d="M284.22,-340.12C280.96,-350.42 277.13,-363.89 275,-376.14 266.94,-422.56 264.72,-477.47 264.15,-509.69"/> -<polygon fill="black" stroke="black" points="260.64,-510.04 264.01,-520.08 267.64,-510.13 260.64,-510.04"/> -<text text-anchor="middle" x="281.22" y="-342.72" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="black" d="M234.84,-345.72C234.5,-382.42 233.69,-468.91 233.27,-514.03"/> +<polygon fill="black" stroke="black" points="229.77,-513.79 233.18,-523.82 236.77,-513.85 229.77,-513.79"/> +<text text-anchor="middle" x="232.59" y="-347.87" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- LT14->Phi_arg13 --> <g id="edge12" class="edge"> <title>LT14->Phi_arg13</title> -<path fill="none" stroke="black" d="M297.35,-339.73C300.92,-347.81 305.31,-357.71 309.33,-366.79"/> -<polygon fill="black" stroke="black" points="306.24,-368.46 313.49,-376.19 312.64,-365.63 306.24,-368.46"/> -<text text-anchor="middle" x="294.35" y="-342.33" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="black" d="M256.26,-339.05C279.21,-350.36 316.09,-368.54 343.06,-381.84"/> +<polygon fill="black" stroke="black" points="341.35,-384.89 351.86,-386.17 344.44,-378.61 341.35,-384.89"/> +<text text-anchor="middle" x="258.51" y="-341.2" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- If16 --> -<g id="node9" class="node"> +<g id="node10" class="node"> <title>If16</title> -<polygon fill="yellow" stroke="transparent" points="242,-239.14 242,-264.14 336,-264.14 336,-239.14 242,-239.14"/> -<polygon fill="none" stroke="black" points="242,-239.14 242,-264.14 336,-264.14 336,-239.14 242,-239.14"/> -<text text-anchor="start" x="283.5" y="-247.94" font-family="Times,serif" font-size="14.00">If</text> -<polygon fill="none" stroke="black" points="242,-208.14 242,-239.14 336,-239.14 336,-208.14 242,-208.14"/> -<polygon fill="yellow" stroke="transparent" points="247,-213.14 247,-234.14 287,-234.14 287,-213.14 247,-213.14"/> -<polygon fill="none" stroke="black" points="247,-213.14 247,-234.14 287,-234.14 287,-213.14 247,-213.14"/> -<text text-anchor="start" x="250" y="-219.94" font-family="Times,serif" font-size="14.00">True</text> -<polygon fill="yellow" stroke="transparent" points="287,-213.14 287,-234.14 331,-234.14 331,-213.14 287,-213.14"/> -<polygon fill="none" stroke="black" points="287,-213.14 287,-234.14 331,-234.14 331,-213.14 287,-213.14"/> -<text text-anchor="start" x="290" y="-219.94" font-family="Times,serif" font-size="14.00">False</text> +<polygon fill="yellow" stroke="none" points="197,-243.07 197,-269.57 273,-269.57 273,-243.07 197,-243.07"/> +<polygon fill="none" stroke="black" points="197,-243.07 197,-269.57 273,-269.57 273,-243.07 197,-243.07"/> +<text text-anchor="start" x="230.5" y="-251.27" font-family="Times New Roman,serif" font-size="14.00">If</text> +<polygon fill="none" stroke="black" points="197,-210.57 197,-243.07 273,-243.07 273,-210.57 197,-210.57"/> +<polygon fill="yellow" stroke="none" points="202,-215.57 202,-238.07 233.5,-238.07 233.5,-215.57 202,-215.57"/> +<polygon fill="none" stroke="black" points="202,-215.57 202,-238.07 233.5,-238.07 233.5,-215.57 202,-215.57"/> +<text text-anchor="start" x="205" y="-221.77" font-family="Times New Roman,serif" font-size="14.00">True</text> +<polygon fill="yellow" stroke="none" points="233.5,-215.57 233.5,-238.07 268,-238.07 268,-215.57 233.5,-215.57"/> +<polygon fill="none" stroke="black" points="233.5,-215.57 233.5,-238.07 268,-238.07 268,-215.57 233.5,-215.57"/> +<text text-anchor="start" x="236.5" y="-221.77" font-family="Times New Roman,serif" font-size="14.00">False</text> </g> <!-- If16->Loop11 --> <g id="edge13" class="edge"> <title>If16->Loop11</title> -<path fill="none" stroke="red" d="M311.8,-268.34C333.14,-297.54 364.63,-340.64 384.47,-367.78"/> -<polygon fill="red" stroke="red" points="381.75,-369.99 390.47,-376 387.4,-365.86 381.75,-369.99"/> -<text text-anchor="middle" x="308.8" y="-270.94" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="red" d="M264.28,-273.52C272.55,-284.3 280.73,-296.83 286,-309.57 293.91,-328.67 297.12,-351.83 298.37,-369.73"/> +<polygon fill="red" stroke="red" points="294.87,-369.85 298.88,-379.66 301.86,-369.5 294.87,-369.85"/> +<text text-anchor="middle" x="262.03" y="-275.67" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- If16->LT14 --> <g id="edge14" class="edge"> <title>If16->LT14</title> -<path fill="none" stroke="black" d="M289.37,-268.42C289.47,-276.72 289.58,-285.63 289.67,-293.71"/> -<polygon fill="black" stroke="black" points="286.17,-293.78 289.79,-303.74 293.17,-293.7 286.17,-293.78"/> -<text text-anchor="middle" x="286.37" y="-271.02" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="black" d="M235,-273.39C235,-281.55 235,-290.26 235,-298.21"/> +<polygon fill="black" stroke="black" points="231.5,-298.02 235,-308.02 238.5,-298.02 231.5,-298.02"/> +<text text-anchor="middle" x="232.75" y="-275.54" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Div21 --> -<g id="node10" class="node"> +<g id="node11" class="node"> <title>Div21</title> -<ellipse fill="none" stroke="black" cx="63" cy="-394.14" rx="27" ry="18"/> -<text text-anchor="middle" x="63" y="-390.44" font-family="Times,serif" font-size="14.00">//</text> +<ellipse fill="none" stroke="black" cx="43" cy="-399.57" rx="27" ry="18"/> +<text text-anchor="middle" x="43" y="-394.52" font-family="Times New Roman,serif" font-size="14.00">//</text> </g> <!-- Div21->Start2 --> <g id="edge15" class="edge"> -<title>Div21->Start2:p1</title> -<path fill="none" stroke="black" d="M70.68,-411.57C79.34,-429.71 94.16,-459.58 109,-484.14 139.67,-534.92 184.56,-537.17 189.54,-590.05"/> -<polygon fill="black" stroke="black" points="186.05,-590.31 190,-600.14 193.05,-590 186.05,-590.31"/> -<text text-anchor="middle" x="67.68" y="-414.17" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<title>Div21->Start2:p2</title> +<path fill="none" stroke="black" d="M56.22,-415.68C64.22,-425.72 73.84,-439.58 79,-453.57 95.73,-498.87 59.24,-525.18 91,-561.57 140.95,-618.8 253.3,-535.12 265.01,-594.3"/> +<polygon fill="black" stroke="black" points="261.5,-594.4 265.87,-604.06 268.48,-593.79 261.5,-594.4"/> +<text text-anchor="middle" x="58.47" y="-417.83" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Con_22 --> -<g id="node11" class="node"> +<g id="node12" class="node"> <title>Con_22</title> -<ellipse fill="none" stroke="black" cx="145" cy="-466.14" rx="27" ry="18"/> -<text text-anchor="middle" x="145" y="-462.44" font-family="Times,serif" font-size="14.00">#3</text> +<ellipse fill="none" stroke="black" cx="43" cy="-471.57" rx="27" ry="18"/> +<text text-anchor="middle" x="43" y="-466.52" font-family="Times New Roman,serif" font-size="14.00">#3</text> </g> <!-- Div21->Con_22 --> <g id="edge16" class="edge"> <title>Div21->Con_22</title> -<path fill="none" stroke="black" d="M79.2,-408.97C91.18,-419.2 107.69,-433.29 121.18,-444.8"/> -<polygon fill="black" stroke="black" points="119.23,-447.75 129.11,-451.58 123.78,-442.43 119.23,-447.75"/> -<text text-anchor="middle" x="76.2" y="-411.57" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="black" d="M43,-417.87C43,-425.16 43,-433.84 43,-442.03"/> +<polygon fill="black" stroke="black" points="39.5,-441.95 43,-451.95 46.5,-441.95 39.5,-441.95"/> +<text text-anchor="middle" x="40.75" y="-420.02" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- EQ23 --> -<g id="node12" class="node"> +<g id="node13" class="node"> <title>EQ23</title> -<ellipse fill="none" stroke="black" cx="389" cy="-236.14" rx="27" ry="18"/> -<text text-anchor="middle" x="389" y="-232.44" font-family="Times,serif" font-size="14.00">==</text> +<ellipse fill="none" stroke="black" cx="346" cy="-240.07" rx="27" ry="18"/> +<text text-anchor="middle" x="346" y="-235.02" font-family="Times New Roman,serif" font-size="14.00">==</text> </g> <!-- EQ23->Phi_arg13 --> <g id="edge17" class="edge"> <title>EQ23->Phi_arg13</title> -<path fill="none" stroke="black" d="M381.85,-253.54C370.11,-280.48 346.63,-334.34 332.52,-366.71"/> -<polygon fill="black" stroke="black" points="329.25,-365.45 328.46,-376.02 335.67,-368.25 329.25,-365.45"/> -<text text-anchor="middle" x="378.85" y="-256.14" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="black" d="M344.71,-258.4C343.62,-279.39 343.3,-315.66 351,-345.57 353.31,-354.54 357.23,-363.78 361.33,-371.99"/> +<polygon fill="black" stroke="black" points="358.21,-373.56 366.01,-380.73 364.38,-370.26 358.21,-373.56"/> +<text text-anchor="middle" x="342.46" y="-260.55" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Con_24 --> -<g id="node13" class="node"> +<g id="node14" class="node"> <title>Con_24</title> -<ellipse fill="none" stroke="black" cx="402" cy="-322.14" rx="27" ry="18"/> -<text text-anchor="middle" x="402" y="-318.44" font-family="Times,serif" font-size="14.00">#5</text> +<ellipse fill="none" stroke="black" cx="387" cy="-327.57" rx="27" ry="18"/> +<text text-anchor="middle" x="387" y="-322.52" font-family="Times New Roman,serif" font-size="14.00">#5</text> </g> <!-- EQ23->Con_24 --> <g id="edge18" class="edge"> <title>EQ23->Con_24</title> -<path fill="none" stroke="black" d="M391.63,-254.14C393.41,-265.64 395.8,-281.07 397.83,-294.23"/> -<polygon fill="black" stroke="black" points="394.38,-294.82 399.37,-304.17 401.3,-293.75 394.38,-294.82"/> -<text text-anchor="middle" x="388.63" y="-256.74" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="black" d="M353.91,-257.55C359.69,-269.62 367.66,-286.24 374.33,-300.14"/> +<polygon fill="black" stroke="black" points="370.98,-301.26 378.46,-308.77 377.3,-298.24 370.98,-301.26"/> +<text text-anchor="middle" x="351.66" y="-259.7" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- If25 --> -<g id="node14" class="node"> +<g id="node15" class="node"> <title>If25</title> -<polygon fill="yellow" stroke="transparent" points="220,-139.14 220,-164.14 314,-164.14 314,-139.14 220,-139.14"/> -<polygon fill="none" stroke="black" points="220,-139.14 220,-164.14 314,-164.14 314,-139.14 220,-139.14"/> -<text text-anchor="start" x="261.5" y="-147.94" font-family="Times,serif" font-size="14.00">If</text> -<polygon fill="none" stroke="black" points="220,-108.14 220,-139.14 314,-139.14 314,-108.14 220,-108.14"/> -<polygon fill="yellow" stroke="transparent" points="225,-113.14 225,-134.14 265,-134.14 265,-113.14 225,-113.14"/> -<polygon fill="none" stroke="black" points="225,-113.14 225,-134.14 265,-134.14 265,-113.14 225,-113.14"/> -<text text-anchor="start" x="228" y="-119.94" font-family="Times,serif" font-size="14.00">True</text> -<polygon fill="yellow" stroke="transparent" points="265,-113.14 265,-134.14 309,-134.14 309,-113.14 265,-113.14"/> -<polygon fill="none" stroke="black" points="265,-113.14 265,-134.14 309,-134.14 309,-113.14 265,-113.14"/> -<text text-anchor="start" x="268" y="-119.94" font-family="Times,serif" font-size="14.00">False</text> +<polygon fill="yellow" stroke="none" points="229,-140.07 229,-166.57 305,-166.57 305,-140.07 229,-140.07"/> +<polygon fill="none" stroke="black" points="229,-140.07 229,-166.57 305,-166.57 305,-140.07 229,-140.07"/> +<text text-anchor="start" x="262.5" y="-148.27" font-family="Times New Roman,serif" font-size="14.00">If</text> +<polygon fill="none" stroke="black" points="229,-107.57 229,-140.07 305,-140.07 305,-107.57 229,-107.57"/> +<polygon fill="yellow" stroke="none" points="234,-112.57 234,-135.07 265.5,-135.07 265.5,-112.57 234,-112.57"/> +<polygon fill="none" stroke="black" points="234,-112.57 234,-135.07 265.5,-135.07 265.5,-112.57 234,-112.57"/> +<text text-anchor="start" x="237" y="-118.77" font-family="Times New Roman,serif" font-size="14.00">True</text> +<polygon fill="yellow" stroke="none" points="265.5,-112.57 265.5,-135.07 300,-135.07 300,-112.57 265.5,-112.57"/> +<polygon fill="none" stroke="black" points="265.5,-112.57 265.5,-135.07 300,-135.07 300,-112.57 265.5,-112.57"/> +<text text-anchor="start" x="268.5" y="-118.77" font-family="Times New Roman,serif" font-size="14.00">False</text> </g> <!-- If25->If16 --> <g id="edge19" class="edge"> <title>If25->If16:p0</title> -<path fill="none" stroke="red" d="M267,-168.16C267,-178.61 267,-190.56 267,-202.13"/> -<polygon fill="red" stroke="red" points="263.5,-202.14 267,-212.14 270.5,-202.14 263.5,-202.14"/> -<text text-anchor="middle" x="264" y="-170.76" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="red" d="M234.98,-170.32C227.77,-180.06 221.5,-191.38 218.96,-203.38"/> +<polygon fill="red" stroke="red" points="215.51,-202.74 217.91,-213.06 222.47,-203.49 215.51,-202.74"/> +<text text-anchor="middle" x="232.73" y="-172.47" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- If25->EQ23 --> <g id="edge20" class="edge"> <title>If25->EQ23</title> -<path fill="none" stroke="black" d="M308.01,-168.43C322.36,-179.56 338.51,-192.27 353,-204.14 357.12,-207.52 361.45,-211.17 365.63,-214.73"/> -<polygon fill="black" stroke="black" points="363.51,-217.52 373.37,-221.39 368.08,-212.22 363.51,-217.52"/> -<text text-anchor="middle" x="305.01" y="-171.03" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> -</g> -<!-- Phi_230 --> -<g id="node15" class="node"> -<title>Phi_230</title> -<ellipse fill="lightyellow" stroke="black" cx="141" cy="-394.14" rx="33.29" ry="18"/> -<text text-anchor="middle" x="141" y="-390.44" font-family="Times,serif" font-size="14.00">φ_$2</text> +<path fill="none" stroke="black" d="M292.21,-170.3C303.4,-184.61 316.37,-201.18 326.76,-214.47"/> +<polygon fill="black" stroke="black" points="323.96,-216.57 332.88,-222.29 329.48,-212.26 323.96,-216.57"/> +<text text-anchor="middle" x="289.96" y="-172.45" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Phi_230->Store10 --> <g id="edge22" class="edge"> <title>Phi_230->Store10</title> -<path fill="none" stroke="blue" d="M157.33,-409.99C168.27,-419.93 182.82,-433.16 194.94,-444.18"/> -<polygon fill="blue" stroke="blue" points="192.73,-446.9 202.48,-451.04 197.44,-441.72 192.73,-446.9"/> -<text text-anchor="middle" x="154.33" y="-412.59" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="blue" d="M164.21,-417.87C165.58,-425.24 167.22,-434.04 168.75,-442.32"/> +<polygon fill="blue" stroke="blue" points="165.28,-442.78 170.55,-451.97 172.16,-441.5 165.28,-442.78"/> +<text text-anchor="middle" x="161.96" y="-420.02" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Phi_230->Loop11 --> <g id="edge21" class="edge"> <title>Phi_230->Loop11</title> -<path fill="none" stroke="black" stroke-dasharray="1,5" d="M157.93,-378.17C167.22,-370.51 179.4,-362.14 192,-358.14 262.32,-335.82 288.38,-333.72 358,-358.14 365.14,-360.65 371.99,-364.81 378.09,-369.44"/> -<polygon fill="black" stroke="black" points="375.99,-372.24 385.92,-375.93 380.45,-366.85 375.99,-372.24"/> -<text text-anchor="middle" x="154.93" y="-371.77" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="black" stroke-dasharray="1,5" d="M214.37,-399.57C229.71,-399.57 245.05,-399.57 260.39,-399.57"/> +<polygon fill="black" stroke="black" points="260.05,-403.07 270.05,-399.57 260.05,-396.07 260.05,-403.07"/> +<text text-anchor="middle" x="216.62" y="-391.97" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Phi_233 --> -<g id="node18" class="node"> +<g id="node19" class="node"> <title>Phi_233</title> -<ellipse fill="lightyellow" stroke="black" cx="84" cy="-50.14" rx="33.29" ry="18"/> -<text text-anchor="middle" x="84" y="-46.44" font-family="Times,serif" font-size="14.00">φ_$2</text> +<ellipse fill="lightyellow" stroke="black" cx="69" cy="-49.57" rx="52.89" ry="18"/> +<text text-anchor="middle" x="69" y="-44.52" font-family="Times New Roman,serif" font-size="14.00">MemPhi_2</text> </g> <!-- Store31 --> -<g id="node16" class="node"> +<g id="node17" class="node"> <title>Store31</title> -<ellipse fill="none" stroke="black" cx="84" cy="-322.14" rx="27.9" ry="18"/> -<text text-anchor="middle" x="84" y="-318.44" font-family="Times,serif" font-size="14.00">.f =</text> +<ellipse fill="none" stroke="black" cx="43" cy="-327.57" rx="27" ry="18"/> +<text text-anchor="middle" x="43" y="-322.52" font-family="Times New Roman,serif" font-size="14.00">.f =</text> </g> <!-- Store31->new9 --> <g id="edge25" class="edge"> <title>Store31->new9</title> -<path fill="none" stroke="black" d="M64.25,-334.88C46.61,-346.87 22.71,-367.36 17,-393.14"/> -<text text-anchor="middle" x="61.25" y="-337.48" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="black" d="M56.48,-343.35C67.65,-356.68 82.68,-377.39 89,-398.57"/> +<path fill="none" stroke="black" d="M89,-400.57C100.9,-440.42 115.61,-486.25 124.9,-514.81"/> +<polygon fill="black" stroke="black" points="121.51,-515.71 127.94,-524.13 128.17,-513.54 121.51,-515.71"/> +<text text-anchor="middle" x="58.73" y="-345.5" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Store31->Div21 --> <g id="edge26" class="edge"> <title>Store31->Div21</title> -<path fill="none" stroke="black" d="M78.92,-340.09C76.52,-348.08 73.6,-357.81 70.92,-366.74"/> -<polygon fill="black" stroke="black" points="67.56,-365.77 68.04,-376.35 74.26,-367.78 67.56,-365.77"/> -<text text-anchor="middle" x="75.92" y="-342.69" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> +<path fill="none" stroke="black" d="M43,-345.87C43,-353.16 43,-361.84 43,-370.03"/> +<polygon fill="black" stroke="black" points="39.5,-369.95 43,-379.95 46.5,-369.95 39.5,-369.95"/> +<text text-anchor="middle" x="40.75" y="-348.02" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> </g> <!-- Store31->Phi_230 --> <g id="edge24" class="edge"> <title>Store31->Phi_230</title> -<path fill="none" stroke="blue" d="M96.37,-338.33C103.81,-347.47 113.44,-359.29 121.84,-369.62"/> -<polygon fill="blue" stroke="blue" points="119.23,-371.95 128.26,-377.5 124.66,-367.53 119.23,-371.95"/> -<text text-anchor="middle" x="93.37" y="-340.93" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="blue" d="M62.75,-340.28C79.89,-350.45 105.12,-365.42 125.72,-377.64"/> +<polygon fill="blue" stroke="blue" points="123.72,-380.52 134.1,-382.61 127.29,-374.5 123.72,-380.52"/> +<text text-anchor="middle" x="65" y="-342.43" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Region32->If25 --> <g id="edge27" class="edge"> <title>Region32->If25:p0</title> -<path fill="none" stroke="red" d="M245,-68.23C245,-77.69 245,-89.92 245,-101.83"/> -<polygon fill="red" stroke="red" points="241.5,-102.14 245,-112.14 248.5,-102.14 241.5,-102.14"/> -<text text-anchor="middle" x="242" y="-70.83" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="red" d="M249.89,-67.92C249.84,-77.07 249.79,-88.77 249.76,-100.25"/> +<polygon fill="red" stroke="red" points="246.26,-100.05 249.75,-110.05 253.26,-100.06 246.26,-100.05"/> +<text text-anchor="middle" x="247.64" y="-70.07" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Region32->If25 --> <g id="edge28" class="edge"> <title>Region32->If25:p1</title> -<path fill="none" stroke="red" d="M265.02,-68.27C273.64,-77.2 282.48,-88.98 285.71,-101.99"/> -<polygon fill="red" stroke="red" points="282.27,-102.66 287,-112.14 289.22,-101.78 282.27,-102.66"/> -<text text-anchor="middle" x="262.02" y="-70.87" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="red" d="M265.22,-67.95C271.77,-76.79 278.57,-88.22 281.39,-100.32"/> +<polygon fill="red" stroke="red" points="277.9,-100.55 282.57,-110.06 284.84,-99.72 277.9,-100.55"/> +<text text-anchor="middle" x="262.97" y="-70.1" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Phi_233->Phi_230 --> <g id="edge31" class="edge"> <title>Phi_233->Phi_230</title> -<path fill="none" stroke="blue" d="M70.91,-76.61C45.62,-129.65 -3.2,-254.21 47,-340.14 53.76,-351.71 82.3,-366.89 106.18,-378.04"/> -<polygon fill="blue" stroke="blue" points="74.08,-78.08 75.32,-67.56 67.79,-75.01 74.08,-78.08"/> -<polygon fill="blue" stroke="blue" points="104.83,-381.28 115.38,-382.25 107.74,-374.91 104.83,-381.28"/> -<text text-anchor="middle" x="72.32" y="-70.16" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="blue" d="M117.06,-66.52C146.85,-77.29 181.4,-91.65 192,-103.57 212.65,-126.78 191.64,-147.08 212,-170.57 237.01,-199.41 269.42,-174.4 290,-206.57 323.6,-259.09 310.68,-297.47 271,-345.57 256.18,-363.53 233.82,-375.83 213.13,-384.03"/> +<polygon fill="blue" stroke="blue" points="118.37,-63.28 107.78,-63.24 116.04,-69.87 118.37,-63.28"/> +<polygon fill="blue" stroke="blue" points="212.09,-380.68 203.93,-387.42 214.52,-387.25 212.09,-380.68"/> +<text text-anchor="middle" x="108.6" y="-64.88" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Phi_233->Store31 --> <g id="edge30" class="edge"> <title>Phi_233->Store31</title> -<path fill="none" stroke="blue" d="M84,-68.44C84,-114.16 84,-238.47 84,-293.87"/> -<polygon fill="blue" stroke="blue" points="80.5,-293.99 84,-303.99 87.5,-293.99 80.5,-293.99"/> -<text text-anchor="middle" x="81" y="-71.04" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="blue" d="M67.37,-67.83C63.02,-114.01 51.09,-240.72 45.7,-297.9"/> +<polygon fill="blue" stroke="blue" points="42.22,-297.52 44.77,-307.8 49.19,-298.17 42.22,-297.52"/> +<text text-anchor="middle" x="65.12" y="-69.98" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Phi_233->Region32 --> <g id="edge29" class="edge"> <title>Phi_233->Region32</title> -<path fill="none" stroke="black" stroke-dasharray="1,5" d="M97.7,-33.41C107.03,-23.4 120.31,-11.6 135,-6.14 157.5,2.22 166.32,1.7 189,-6.14 200.57,-10.14 211.56,-17.63 220.7,-25.31"/> -<polygon fill="black" stroke="black" points="218.43,-27.97 228.22,-32.02 223.09,-22.75 218.43,-27.97"/> -<text text-anchor="middle" x="94.7" y="-27.01" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="black" stroke-dasharray="1,5" d="M89.57,-32.51C103.01,-22.52 121.45,-10.85 140,-5.57 163.08,1.01 171.32,2.28 194,-5.57 205.22,-9.45 215.9,-16.6 224.86,-24.03"/> +<polygon fill="black" stroke="black" points="222.28,-26.42 232.09,-30.44 226.93,-21.19 222.28,-26.42"/> +<text text-anchor="middle" x="87.32" y="-24.91" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Sub35->Phi_arg13 --> <g id="edge32" class="edge"> <title>Sub35->Phi_arg13</title> -<path fill="none" stroke="black" d="M244.96,-341.46C259.41,-351.29 277.23,-363.39 291.98,-373.42"/> -<polygon fill="black" stroke="black" points="246.65,-338.38 236.41,-335.66 242.72,-344.17 246.65,-338.38"/> -<polygon fill="black" stroke="black" points="290.03,-376.33 300.27,-379.05 293.96,-370.54 290.03,-376.33"/> -<text text-anchor="middle" x="233.41" y="-338.26" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="black" d="M434.35,-349.61C424.46,-358.05 413.06,-367.79 403.03,-376.34"/> +<polygon fill="black" stroke="black" points="436.32,-352.53 441.65,-343.38 431.77,-347.21 436.32,-352.53"/> +<polygon fill="black" stroke="black" points="400.78,-373.66 395.45,-382.82 405.33,-378.98 400.78,-373.66"/> +<text text-anchor="middle" x="440.55" y="-344.55" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Con_36 --> -<g id="node20" class="node"> +<g id="node21" class="node"> <title>Con_36</title> -<ellipse fill="none" stroke="black" cx="219" cy="-394.14" rx="27" ry="18"/> -<text text-anchor="middle" x="219" y="-390.44" font-family="Times,serif" font-size="14.00">#1</text> +<ellipse fill="none" stroke="black" cx="459" cy="-399.57" rx="27" ry="18"/> +<text text-anchor="middle" x="459" y="-394.52" font-family="Times New Roman,serif" font-size="14.00">#1</text> </g> <!-- Sub35->Con_36 --> <g id="edge33" class="edge"> <title>Sub35->Con_36</title> -<path fill="none" stroke="black" d="M218.25,-340.45C218.36,-348.16 218.49,-357.43 218.61,-366.03"/> -<polygon fill="black" stroke="black" points="215.11,-366.09 218.76,-376.04 222.11,-365.99 215.11,-366.09"/> -<text text-anchor="middle" x="215.25" y="-343.05" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="black" d="M459,-345.87C459,-353.16 459,-361.84 459,-370.03"/> +<polygon fill="black" stroke="black" points="455.5,-369.95 459,-379.95 462.5,-369.95 455.5,-369.95"/> +<text text-anchor="middle" x="456.75" y="-348.02" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Return37->MemMerge --> +<g id="edge36" class="edge"> +<title>Return37->MemMerge</title> +<path fill="none" stroke="blue" d="M89,-241.07C86.12,-264.36 101.49,-285.96 117.39,-301.62"/> +<polygon fill="blue" stroke="blue" points="114.82,-304.01 124.55,-308.22 119.57,-298.86 114.82,-304.01"/> +<text text-anchor="middle" x="86.75" y="-233.47" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Return37->new9 --> <g id="edge35" class="edge"> <title>Return37->new9</title> -<path fill="none" stroke="black" d="M146.13,-154.21C122.96,-183.41 76.06,-245.48 47,-304.14 28.47,-341.55 26.02,-352.39 17,-393.14"/> -<path fill="none" stroke="black" d="M17,-395.14C5.47,-435.72 24.68,-483.11 39.73,-511.59"/> -<polygon fill="black" stroke="black" points="36.74,-513.41 44.63,-520.48 42.87,-510.03 36.74,-513.41"/> -<text text-anchor="middle" x="143.13" y="-156.81" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="black" d="M89,-241.07C84.7,-275.8 78.53,-365.17 89,-398.57"/> +<path fill="none" stroke="black" d="M137.42,-155.26C119.26,-173.82 93.67,-205.3 89,-239.07"/> +<text text-anchor="middle" x="86.75" y="-243.22" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Return37->If16 --> <g id="edge34" class="edge"> <title>Return37->If16:p1</title> -<path fill="none" stroke="red" d="M181.67,-154.25C188.31,-159.08 195.77,-164.11 203,-168.14 244.24,-191.16 299.97,-165.55 308.02,-202.1"/> -<polygon fill="red" stroke="red" points="304.54,-202.53 309,-212.14 311.51,-201.85 304.54,-202.53"/> -<text text-anchor="middle" x="178.67" y="-156.85" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> -</g> -<!-- Return37->Phi_230 --> -<g id="edge36" class="edge"> -<title>Return37->Phi_230</title> -<path fill="none" stroke="blue" d="M158.72,-154.34C155.49,-197.97 146.93,-313.25 143.01,-366.05"/> -<polygon fill="blue" stroke="blue" points="139.52,-365.79 142.27,-376.02 146.5,-366.31 139.52,-365.79"/> -<text text-anchor="middle" x="155.72" y="-156.94" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="red" d="M177.98,-155.47C198.53,-171.78 228.14,-195.38 242.42,-207.22"/> +<polygon fill="red" stroke="red" points="239.8,-209.58 249.61,-213.56 244.43,-204.33 239.8,-209.58"/> +<text text-anchor="middle" x="175.73" y="-157.62" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> </g> </svg> diff --git a/chapter11/docs/graph5.gv b/chapter11/docs/graph5.gv index f89a83ac..0baa512f 100644 --- a/chapter11/docs/graph5.gv +++ b/chapter11/docs/graph5.gv @@ -21,11 +21,12 @@ return v; <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></TR> + <TR><TD PORT="p0" BGCOLOR="yellow">$ctrl</TD><TD PORT="p1">$mem</TD><TD PORT="p2">arg</TD></TR> </TABLE> </TD></TR> </TABLE>> ]; + MemMerge [ shape=box label="MemMerge" ]; Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; Con_4 [ label="#0" ]; new9 [ label="new S" ]; @@ -57,10 +58,10 @@ return v; </TD></TR> </TABLE>> ]; - Phi_230 [ style=filled fillcolor=lightyellow label="φ_$2" ]; + Phi_230 [ style=filled fillcolor=lightyellow label="MemPhi_2" ]; Store31 [ label=".f =" ]; Region32 [ shape=box style=filled fillcolor=yellow label="Region" ]; - Phi_233 [ style=filled fillcolor=lightyellow label="φ_$2" ]; + Phi_233 [ style=filled fillcolor=lightyellow label="MemPhi_2" ]; Sub35 [ label="-" ]; Con_36 [ label="#1" ]; Return37 [ shape=box style=filled fillcolor=yellow label="Return" ]; @@ -71,13 +72,13 @@ return v; Stop3 -> Return37[taillabel=0 color=red]; new9 -> Start2:p0[taillabel=0 color=green]; Store10 -> Start2:p0[taillabel=0 color=red]; - Store10 -> Start2:p2[taillabel=1 color=blue]; + Store10 -> Start2:p1[taillabel=1 color=blue]; Store10 -> new9[taillabel=2]; Store10 -> Con_4[taillabel=3]; Loop11 -> Start2:p0[taillabel=1 color=red]; Loop11 -> Region32[taillabel=2 color=red constraint=false]; Phi_arg13 -> Loop11 [style=dotted taillabel=0]; - Phi_arg13 -> Start2:p1[taillabel=1]; + Phi_arg13 -> Start2:p2[taillabel=1]; Phi_arg13 -> Sub35[taillabel=2 constraint=false]; LT14 -> Loop11[taillabel=0 color=red]; LT14 -> Con_4[taillabel=1]; @@ -85,7 +86,7 @@ return v; If16 -> Loop11[taillabel=0 color=red]; If16 -> LT14[taillabel=1]; Div21 -> Start2[taillabel=0 color=red]; - Div21 -> Start2:p1[taillabel=1]; + Div21 -> Start2:p2[taillabel=1]; Div21 -> Con_22[taillabel=2]; EQ23 -> Loop11[taillabel=0 color=red]; EQ23 -> Phi_arg13[taillabel=1]; @@ -108,6 +109,9 @@ return v; Sub35 -> Phi_arg13[taillabel=1]; Sub35 -> Con_36[taillabel=2]; Return37 -> If16:p1[taillabel=0 color=red]; - Return37 -> new9[taillabel=1]; - Return37 -> Phi_230[taillabel=2 color=blue]; + Return37 -> new9[taillabel=2]; + Return37 -> MemMerge[taillabel=1 color=blue]; + MemMerge -> Start2:p1[taillabel=1 color=blue]; + MemMerge -> Phi_230[taillabel=2 color=blue]; + MemMerge -> Loop11[taillabel=0 color=green]; } diff --git a/chapter11/docs/graph5.svg b/chapter11/docs/graph5.svg index 6dbf29f9..9336ac3e 100644 --- a/chapter11/docs/graph5.svg +++ b/chapter11/docs/graph5.svg @@ -1,452 +1,472 @@ <?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) +<!-- Generated by graphviz version 12.2.0 (20241103.1931) --> <!-- Title: chapter11 Pages: 1 --> -<svg width="474pt" height="680pt" - viewBox="0.00 0.00 474.00 679.67" 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 675.67)"> +<svg width="526pt" height="689pt" + viewBox="0.00 0.00 526.00 688.57" 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 684.57)"> <title>chapter11</title> -<polygon fill="white" stroke="transparent" points="-4,4 -4,-675.67 470,-675.67 470,4 -4,4"/> +<polygon fill="white" stroke="none" points="-4,4 -4,-684.57 522,-684.57 522,4 -4,4"/> <g id="clust1" class="cluster"> <title>cluster_Nodes</title> -<polygon fill="none" stroke="black" points="8,-23.67 8,-663.67 458,-663.67 458,-23.67 8,-23.67"/> +<polygon fill="none" stroke="black" points="8,-23.57 8,-672.57 510,-672.57 510,-23.57 8,-23.57"/> </g> <!-- Start2 --> <g id="node1" class="node"> <title>Start2</title> -<polygon fill="yellow" stroke="transparent" points="109,-626.67 109,-651.67 215,-651.67 215,-626.67 109,-626.67"/> -<polygon fill="none" stroke="black" points="109,-626.67 109,-651.67 215,-651.67 215,-626.67 109,-626.67"/> -<text text-anchor="start" x="143.5" y="-635.47" font-family="Times,serif" font-size="14.00">Start</text> -<polygon fill="none" stroke="black" points="109,-595.67 109,-626.67 215,-626.67 215,-595.67 109,-595.67"/> -<polygon fill="yellow" stroke="transparent" points="114,-600.67 114,-621.67 154,-621.67 154,-600.67 114,-600.67"/> -<polygon fill="none" stroke="black" points="114,-600.67 114,-621.67 154,-621.67 154,-600.67 114,-600.67"/> -<text text-anchor="start" x="117" y="-607.47" font-family="Times,serif" font-size="14.00">$ctrl</text> -<polygon fill="none" stroke="black" points="154,-600.67 154,-621.67 185,-621.67 185,-600.67 154,-600.67"/> -<text text-anchor="start" x="157" y="-607.47" font-family="Times,serif" font-size="14.00">arg</text> -<polygon fill="none" stroke="black" points="185,-600.67 185,-621.67 210,-621.67 210,-600.67 185,-600.67"/> -<text text-anchor="start" x="188" y="-607.47" font-family="Times,serif" font-size="14.00">$2</text> +<polygon fill="yellow" stroke="none" points="146.38,-634.07 146.38,-660.57 251.62,-660.57 251.62,-634.07 146.38,-634.07"/> +<polygon fill="none" stroke="black" points="146.38,-634.07 146.38,-660.57 251.62,-660.57 251.62,-634.07 146.38,-634.07"/> +<text text-anchor="start" x="186.25" y="-642.27" font-family="Times New Roman,serif" font-size="14.00">Start</text> +<polygon fill="none" stroke="black" points="146.38,-601.57 146.38,-634.07 251.62,-634.07 251.62,-601.57 146.38,-601.57"/> +<polygon fill="yellow" stroke="none" points="151.38,-606.57 151.38,-629.07 182.12,-629.07 182.12,-606.57 151.38,-606.57"/> +<polygon fill="none" stroke="black" points="151.38,-606.57 151.38,-629.07 182.12,-629.07 182.12,-606.57 151.38,-606.57"/> +<text text-anchor="start" x="154.38" y="-612.77" font-family="Times New Roman,serif" font-size="14.00">$ctrl</text> +<polygon fill="none" stroke="black" points="182.12,-606.57 182.12,-629.07 223.38,-629.07 223.38,-606.57 182.12,-606.57"/> +<text text-anchor="start" x="185.12" y="-612.77" font-family="Times New Roman,serif" font-size="14.00">$mem</text> +<polygon fill="none" stroke="black" points="223.38,-606.57 223.38,-629.07 246.62,-629.07 246.62,-606.57 223.38,-606.57"/> +<text text-anchor="start" x="226.38" y="-612.77" font-family="Times New Roman,serif" font-size="14.00">arg</text> +</g> +<!-- MemMerge --> +<g id="node2" class="node"> +<title>MemMerge</title> +<polygon fill="none" stroke="black" points="190,-345.57 108,-345.57 108,-309.57 190,-309.57 190,-345.57"/> +<text text-anchor="middle" x="149" y="-322.52" font-family="Times New Roman,serif" font-size="14.00">MemMerge</text> +</g> +<!-- MemMerge->Start2 --> +<g id="edge43" class="edge"> +<title>MemMerge->Start2:p1</title> +<path fill="none" stroke="blue" d="M157.48,-345.84C162.13,-356.02 167.63,-369.27 171,-381.57 196.5,-474.76 202.31,-502.84 202.73,-594.11"/> +<polygon fill="blue" stroke="blue" points="199.22,-594.06 202.75,-604.05 206.22,-594.04 199.22,-594.06"/> +<text text-anchor="middle" x="155.23" y="-347.99" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Loop11 --> +<g id="node7" class="node"> +<title>Loop11</title> +<polygon fill="yellow" stroke="black" points="274,-417.57 220,-417.57 220,-381.57 274,-381.57 274,-417.57"/> +<text text-anchor="middle" x="247" y="-394.52" font-family="Times New Roman,serif" font-size="14.00">Loop</text> +</g> +<!-- MemMerge->Loop11 --> +<g id="edge45" class="edge"> +<title>MemMerge->Loop11</title> +<path fill="none" stroke="green" d="M173.48,-346.05C185.61,-354.72 200.45,-365.32 213.61,-374.72"/> +<polygon fill="green" stroke="green" points="211.43,-377.46 221.6,-380.43 215.5,-371.76 211.43,-377.46"/> +<text text-anchor="middle" x="171.23" y="-348.2" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Phi_230 --> +<g id="node16" class="node"> +<title>Phi_230</title> +<ellipse fill="lightyellow" stroke="black" cx="109" cy="-399.57" rx="52.89" ry="18"/> +<text text-anchor="middle" x="109" y="-394.52" font-family="Times New Roman,serif" font-size="14.00">MemPhi_2</text> +</g> +<!-- MemMerge->Phi_230 --> +<g id="edge44" class="edge"> +<title>MemMerge->Phi_230</title> +<path fill="none" stroke="blue" d="M139.11,-345.87C134.66,-353.67 129.29,-363.06 124.33,-371.74"/> +<polygon fill="blue" stroke="blue" points="121.45,-369.73 119.52,-380.15 127.53,-373.2 121.45,-369.73"/> +<text text-anchor="middle" x="136.86" y="-348.02" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Stop3 --> -<g id="node2" class="node"> +<g id="node3" class="node"> <title>Stop3</title> -<polygon fill="yellow" stroke="black" points="178,-67.67 124,-67.67 124,-31.67 178,-31.67 178,-67.67"/> -<text text-anchor="middle" x="151" y="-45.97" font-family="Times,serif" font-size="14.00">Stop</text> +<polygon fill="yellow" stroke="black" points="194,-67.57 140,-67.57 140,-31.57 194,-31.57 194,-67.57"/> +<text text-anchor="middle" x="167" y="-44.52" font-family="Times New Roman,serif" font-size="14.00">Stop</text> </g> <!-- Return37 --> -<g id="node21" class="node"> +<g id="node22" class="node"> <title>Return37</title> -<polygon fill="yellow" stroke="black" points="184,-153.67 118,-153.67 118,-117.67 184,-117.67 184,-153.67"/> -<text text-anchor="middle" x="151" y="-131.97" font-family="Times,serif" font-size="14.00">Return</text> +<polygon fill="yellow" stroke="black" points="183,-155.07 129,-155.07 129,-119.07 183,-119.07 183,-155.07"/> +<text text-anchor="middle" x="156" y="-132.02" font-family="Times New Roman,serif" font-size="14.00">Return</text> </g> <!-- Stop3->Return37 --> <g id="edge1" class="edge"> <title>Stop3->Return37</title> -<path fill="none" stroke="red" d="M151,-68.08C151,-79.42 151,-94.45 151,-107.36"/> -<polygon fill="red" stroke="red" points="147.5,-107.58 151,-117.58 154.5,-107.58 147.5,-107.58"/> -<text text-anchor="middle" x="148" y="-70.68" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="red" d="M164.77,-67.87C163.31,-79.22 161.37,-94.35 159.68,-107.47"/> +<polygon fill="red" stroke="red" points="156.22,-106.92 158.42,-117.29 163.16,-107.82 156.22,-106.92"/> +<text text-anchor="middle" x="162.52" y="-70.02" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Con_4 --> -<g id="node3" class="node"> +<g id="node4" class="node"> <title>Con_4</title> -<ellipse fill="none" stroke="black" cx="262" cy="-537.67" rx="27" ry="18"/> -<text text-anchor="middle" x="262" y="-533.97" font-family="Times,serif" font-size="14.00">#0</text> +<ellipse fill="none" stroke="black" cx="241" cy="-543.57" rx="27" ry="18"/> +<text text-anchor="middle" x="241" y="-538.52" font-family="Times New Roman,serif" font-size="14.00">#0</text> </g> <!-- new9 --> -<g id="node4" class="node"> +<g id="node5" class="node"> <title>new9</title> -<ellipse fill="none" stroke="black" cx="127" cy="-537.67" rx="38.99" ry="18"/> -<text text-anchor="middle" x="127" y="-533.97" font-family="Times,serif" font-size="14.00">new S</text> +<ellipse fill="none" stroke="black" cx="122" cy="-543.57" rx="33.95" ry="18"/> +<text text-anchor="middle" x="122" y="-538.52" font-family="Times New Roman,serif" font-size="14.00">new S</text> </g> <!-- new9->Start2 --> <g id="edge2" class="edge"> <title>new9->Start2:p0</title> -<path fill="none" stroke="green" d="M130.01,-555.71C131.49,-565.24 133.09,-577.6 133.72,-589.67"/> -<polygon fill="green" stroke="green" points="130.22,-589.78 134,-599.67 137.22,-589.58 130.22,-589.78"/> -<text text-anchor="middle" x="127.01" y="-558.31" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="green" d="M140.72,-558.99C150.49,-567.91 161.22,-580.35 165.17,-594.47"/> +<polygon fill="green" stroke="green" points="161.67,-594.66 166.54,-604.07 168.6,-593.67 161.67,-594.66"/> +<text text-anchor="middle" x="138.47" y="-561.14" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Store10 --> -<g id="node5" class="node"> +<g id="node6" class="node"> <title>Store10</title> -<ellipse fill="none" stroke="black" cx="138" cy="-465.67" rx="27.9" ry="18"/> -<text text-anchor="middle" x="138" y="-461.97" font-family="Times,serif" font-size="14.00">.f =</text> +<ellipse fill="none" stroke="black" cx="128" cy="-471.57" rx="27" ry="18"/> +<text text-anchor="middle" x="128" y="-466.52" font-family="Times New Roman,serif" font-size="14.00">.f =</text> </g> <!-- Store10->Start2 --> <g id="edge3" class="edge"> <title>Store10->Start2:p0</title> -<path fill="none" stroke="red" d="M154.15,-480.42C167.51,-493.39 184.39,-514.14 185,-536.67"/> -<path fill="none" stroke="red" d="M185,-538.67C185.75,-566.42 153.85,-566.41 140.14,-582.26"/> -<polygon fill="red" stroke="red" points="136.81,-581.05 135.08,-591.5 142.95,-584.41 136.81,-581.05"/> -<text text-anchor="middle" x="151.15" y="-483.02" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> -</g> -<!-- Store10->Start2 --> -<g id="edge4" class="edge"> -<title>Store10->Start2:p2</title> -<path fill="none" stroke="blue" d="M155.9,-479.6C167.73,-489.22 182.4,-503.45 190,-519.67 203.61,-548.75 199.22,-561.31 198.19,-589.48"/> -<polygon fill="blue" stroke="blue" points="194.69,-589.61 198,-599.67 201.69,-589.74 194.69,-589.61"/> -<text text-anchor="middle" x="158.9" y="-482.2" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="red" d="M142.02,-487.46C150.41,-497.42 160.33,-511.27 165,-525.57 174.79,-555.54 168.59,-567.3 167.06,-594.37"/> +<polygon fill="red" stroke="red" points="163.57,-593.96 166.79,-604.05 170.57,-594.15 163.57,-593.96"/> +<text text-anchor="middle" x="144.27" y="-489.61" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Store10->Con_4 --> <g id="edge6" class="edge"> <title>Store10->Con_4</title> -<path fill="none" stroke="black" d="M158.48,-478.23C178.79,-489.7 210.11,-507.38 232.98,-520.29"/> -<polygon fill="black" stroke="black" points="231.48,-523.46 241.9,-525.33 234.92,-517.37 231.48,-523.46"/> -<text text-anchor="middle" x="155.48" y="-471.83" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> +<path fill="none" stroke="black" d="M147.43,-484.6C165.24,-495.63 191.86,-512.12 212.15,-524.7"/> +<polygon fill="black" stroke="black" points="210.2,-527.6 220.54,-529.89 213.88,-521.65 210.2,-527.6"/> +<text text-anchor="middle" x="149.68" y="-486.75" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> </g> <!-- Store10->new9 --> <g id="edge5" class="edge"> <title>Store10->new9</title> -<path fill="none" stroke="black" d="M135.34,-483.62C134.13,-491.33 132.66,-500.65 131.3,-509.31"/> -<polygon fill="black" stroke="black" points="127.81,-508.98 129.71,-519.4 134.72,-510.06 127.81,-508.98"/> -<text text-anchor="middle" x="132.34" y="-486.22" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> -</g> -<!-- Loop11 --> -<g id="node6" class="node"> -<title>Loop11</title> -<polygon fill="yellow" stroke="black" points="194,-411.67 140,-411.67 140,-375.67 194,-375.67 194,-411.67"/> -<text text-anchor="middle" x="167" y="-389.97" font-family="Times,serif" font-size="14.00">Loop</text> +<path fill="none" stroke="black" d="M126.52,-489.87C125.89,-497.16 125.15,-505.84 124.45,-514.03"/> +<polygon fill="black" stroke="black" points="120.96,-513.69 123.6,-523.95 127.94,-514.29 120.96,-513.69"/> +<text text-anchor="middle" x="124.27" y="-492.02" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Loop11->Start2 --> <g id="edge7" class="edge"> <title>Loop11->Start2:p0</title> -<path fill="none" stroke="red" d="M170.34,-412.08C175.19,-438.77 183.78,-491.46 185,-536.67"/> -<text text-anchor="middle" x="167.34" y="-414.68" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="red" d="M257.71,-417.74C275.77,-449.11 307.46,-516.61 277,-561.57 249.83,-601.66 179.64,-560.76 168.31,-594.59"/> +<polygon fill="red" stroke="red" points="164.9,-593.67 166.96,-604.07 171.83,-594.66 164.9,-593.67"/> +<text text-anchor="middle" x="255.46" y="-419.89" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Region32 --> -<g id="node17" class="node"> +<g id="node18" class="node"> <title>Region32</title> -<polygon fill="yellow" stroke="black" points="268,-67.67 202,-67.67 202,-31.67 268,-31.67 268,-67.67"/> -<text text-anchor="middle" x="235" y="-45.97" font-family="Times,serif" font-size="14.00">Region</text> +<polygon fill="yellow" stroke="black" points="277.5,-67.57 222.5,-67.57 222.5,-31.57 277.5,-31.57 277.5,-67.57"/> +<text text-anchor="middle" x="250" y="-44.52" font-family="Times New Roman,serif" font-size="14.00">Region</text> </g> <!-- Loop11->Region32 --> <g id="edge8" class="edge"> <title>Loop11->Region32</title> -<path fill="none" stroke="red" d="M155.44,-375.36C125.2,-327.71 50.11,-192.34 109,-103.67 114.01,-96.13 157.85,-78.74 192.42,-65.93"/> -<polygon fill="red" stroke="red" points="193.71,-69.18 201.89,-62.45 191.3,-62.61 193.71,-69.18"/> -<text text-anchor="middle" x="152.44" y="-368.96" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="red" d="M259.49,-381.16C271.43,-362.17 285.63,-331.55 271,-309.57 248.04,-275.07 216.63,-300.33 185,-273.57 123.24,-221.32 74.87,-170.7 120,-103.57 122.76,-99.46 174.37,-79.22 211.72,-64.98"/> +<polygon fill="red" stroke="red" points="212.79,-68.32 220.89,-61.49 210.3,-61.77 212.79,-68.32"/> +<text text-anchor="middle" x="257.24" y="-373.56" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Phi_arg13 --> -<g id="node7" class="node"> +<g id="node8" class="node"> <title>Phi_arg13</title> -<ellipse fill="lightyellow" stroke="black" cx="341" cy="-393.67" rx="37.09" ry="18"/> -<text text-anchor="middle" x="341" y="-389.97" font-family="Times,serif" font-size="14.00">φ_arg</text> +<ellipse fill="lightyellow" stroke="black" cx="325" cy="-399.57" rx="32.93" ry="18"/> +<text text-anchor="middle" x="325" y="-394.52" font-family="Times New Roman,serif" font-size="14.00">φ_arg</text> </g> <!-- Phi_arg13->Start2 --> <g id="edge10" class="edge"> -<title>Phi_arg13->Start2:p1</title> -<path fill="none" stroke="black" d="M341.39,-411.71C341.18,-444.06 336.07,-514.83 298,-555.67 259.62,-596.84 181.02,-546.84 171.05,-589.48"/> -<polygon fill="black" stroke="black" points="167.54,-589.37 170,-599.67 174.5,-590.09 167.54,-589.37"/> -<text text-anchor="middle" x="338.39" y="-414.31" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<title>Phi_arg13->Start2:p2</title> +<path fill="none" stroke="black" d="M326.47,-417.98C329.72,-467.11 332.08,-604.13 258.97,-616.86"/> +<polygon fill="black" stroke="black" points="258.81,-613.36 249.13,-617.69 259.39,-620.34 258.81,-613.36"/> +<text text-anchor="middle" x="324.22" y="-420.13" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Phi_arg13->Loop11 --> <g id="edge9" class="edge"> <title>Phi_arg13->Loop11</title> -<path fill="none" stroke="black" stroke-dasharray="1,5" d="M321.44,-378.23C310.28,-370.41 295.63,-361.73 281,-357.67 251.45,-349.49 240.94,-347.52 212,-357.67 204.86,-360.18 198.01,-364.35 191.91,-368.97"/> -<polygon fill="black" stroke="black" points="189.55,-366.38 184.08,-375.46 194.01,-371.77 189.55,-366.38"/> -<text text-anchor="middle" x="318.44" y="-371.83" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="black" stroke-dasharray="1,5" d="M291.81,-399.57C289.82,-399.57 287.82,-399.57 285.83,-399.57"/> +<polygon fill="black" stroke="black" points="285.94,-396.07 275.94,-399.57 285.94,-403.07 285.94,-396.07"/> +<text text-anchor="middle" x="289.56" y="-391.97" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Sub35 --> -<g id="node19" class="node"> +<g id="node20" class="node"> <title>Sub35</title> -<ellipse fill="none" stroke="black" cx="217" cy="-321.67" rx="27" ry="18"/> -<text text-anchor="middle" x="217" y="-317.97" font-family="Times,serif" font-size="14.00">-</text> +<ellipse fill="none" stroke="black" cx="439" cy="-327.57" rx="27" ry="18"/> +<text text-anchor="middle" x="439" y="-322.52" font-family="Times New Roman,serif" font-size="14.00">-</text> </g> <!-- LT14 --> -<g id="node8" class="node"> +<g id="node9" class="node"> <title>LT14</title> -<ellipse fill="none" stroke="black" cx="289" cy="-321.67" rx="27" ry="18"/> -<text text-anchor="middle" x="289" y="-317.97" font-family="Times,serif" font-size="14.00"><</text> +<ellipse fill="none" stroke="black" cx="235" cy="-327.57" rx="27" ry="18"/> +<text text-anchor="middle" x="235" y="-322.52" font-family="Times New Roman,serif" font-size="14.00"><</text> </g> <!-- LT14->Con_4 --> <g id="edge13" class="edge"> <title>LT14->Con_4</title> -<path fill="none" stroke="black" d="M286.84,-339.83C282.17,-376.84 271.11,-464.49 265.46,-509.29"/> -<polygon fill="black" stroke="black" points="261.95,-509.08 264.17,-519.44 268.9,-509.96 261.95,-509.08"/> -<text text-anchor="middle" x="283.84" y="-342.43" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="black" d="M225.54,-344.65C220.11,-354.85 213.81,-368.53 211,-381.57 200.86,-428.55 217.2,-483.7 229.46,-515.73"/> +<polygon fill="black" stroke="black" points="226.06,-516.65 233.02,-524.64 232.56,-514.06 226.06,-516.65"/> +<text text-anchor="middle" x="223.29" y="-346.8" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- LT14->Loop11 --> <g id="edge12" class="edge"> <title>LT14->Loop11</title> -<path fill="none" stroke="red" d="M269.13,-334.08C251.28,-344.31 224.66,-359.59 203.06,-371.98"/> -<polygon fill="red" stroke="red" points="201.09,-369.08 194.16,-377.09 204.57,-375.15 201.09,-369.08"/> -<text text-anchor="middle" x="266.13" y="-336.68" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="red" d="M237.97,-345.87C239.23,-353.24 240.74,-362.04 242.16,-370.32"/> +<polygon fill="red" stroke="red" points="238.67,-370.7 243.81,-379.97 245.57,-369.52 238.67,-370.7"/> +<text text-anchor="middle" x="235.72" y="-348.02" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- LT14->Phi_arg13 --> <g id="edge14" class="edge"> <title>LT14->Phi_arg13</title> -<path fill="none" stroke="black" d="M300.54,-338.21C307.02,-346.93 315.26,-358.02 322.61,-367.91"/> -<polygon fill="black" stroke="black" points="319.99,-370.27 328.77,-376.21 325.61,-366.09 319.99,-370.27"/> -<text text-anchor="middle" x="297.54" y="-340.81" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="black" d="M251.93,-341.73C264.83,-351.77 282.86,-365.79 297.78,-377.4"/> +<polygon fill="black" stroke="black" points="295.36,-379.95 305.4,-383.32 299.66,-374.42 295.36,-379.95"/> +<text text-anchor="middle" x="249.68" y="-343.88" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- If16 --> -<g id="node9" class="node"> +<g id="node10" class="node"> <title>If16</title> -<polygon fill="yellow" stroke="transparent" points="205,-238.67 205,-263.67 299,-263.67 299,-238.67 205,-238.67"/> -<polygon fill="none" stroke="black" points="205,-238.67 205,-263.67 299,-263.67 299,-238.67 205,-238.67"/> -<text text-anchor="start" x="246.5" y="-247.47" font-family="Times,serif" font-size="14.00">If</text> -<polygon fill="none" stroke="black" points="205,-207.67 205,-238.67 299,-238.67 299,-207.67 205,-207.67"/> -<polygon fill="yellow" stroke="transparent" points="210,-212.67 210,-233.67 250,-233.67 250,-212.67 210,-212.67"/> -<polygon fill="none" stroke="black" points="210,-212.67 210,-233.67 250,-233.67 250,-212.67 210,-212.67"/> -<text text-anchor="start" x="213" y="-219.47" font-family="Times,serif" font-size="14.00">True</text> -<polygon fill="yellow" stroke="transparent" points="250,-212.67 250,-233.67 294,-233.67 294,-212.67 250,-212.67"/> -<polygon fill="none" stroke="black" points="250,-212.67 250,-233.67 294,-233.67 294,-212.67 250,-212.67"/> -<text text-anchor="start" x="253" y="-219.47" font-family="Times,serif" font-size="14.00">False</text> +<polygon fill="yellow" stroke="none" points="202,-243.07 202,-269.57 278,-269.57 278,-243.07 202,-243.07"/> +<polygon fill="none" stroke="black" points="202,-243.07 202,-269.57 278,-269.57 278,-243.07 202,-243.07"/> +<text text-anchor="start" x="235.5" y="-251.27" font-family="Times New Roman,serif" font-size="14.00">If</text> +<polygon fill="none" stroke="black" points="202,-210.57 202,-243.07 278,-243.07 278,-210.57 202,-210.57"/> +<polygon fill="yellow" stroke="none" points="207,-215.57 207,-238.07 238.5,-238.07 238.5,-215.57 207,-215.57"/> +<polygon fill="none" stroke="black" points="207,-215.57 207,-238.07 238.5,-238.07 238.5,-215.57 207,-215.57"/> +<text text-anchor="start" x="210" y="-221.77" font-family="Times New Roman,serif" font-size="14.00">True</text> +<polygon fill="yellow" stroke="none" points="238.5,-215.57 238.5,-238.07 273,-238.07 273,-215.57 238.5,-215.57"/> +<polygon fill="none" stroke="black" points="238.5,-215.57 238.5,-238.07 273,-238.07 273,-215.57 238.5,-215.57"/> +<text text-anchor="start" x="241.5" y="-221.77" font-family="Times New Roman,serif" font-size="14.00">False</text> </g> <!-- If16->Loop11 --> <g id="edge15" class="edge"> <title>If16->Loop11</title> -<path fill="none" stroke="red" d="M209.76,-267.98C198.76,-278.17 188.03,-290.33 181,-303.67 170.94,-322.76 167.65,-347.11 166.77,-365.52"/> -<polygon fill="red" stroke="red" points="163.27,-365.49 166.49,-375.59 170.26,-365.69 163.27,-365.49"/> -<text text-anchor="middle" x="206.76" y="-270.58" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="red" d="M267.48,-273.51C274.99,-284.3 282.12,-296.82 286,-309.57 290.66,-324.87 290.99,-330.36 286,-345.57 282.88,-355.08 277.32,-364.3 271.36,-372.3"/> +<polygon fill="red" stroke="red" points="268.78,-369.92 265.24,-379.91 274.24,-374.31 268.78,-369.92"/> +<text text-anchor="middle" x="265.23" y="-275.66" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- If16->LT14 --> <g id="edge16" class="edge"> <title>If16->LT14</title> -<path fill="none" stroke="black" d="M265.78,-267.95C269.68,-276.8 273.87,-286.34 277.62,-294.83"/> -<polygon fill="black" stroke="black" points="274.52,-296.48 281.75,-304.22 280.92,-293.65 274.52,-296.48"/> -<text text-anchor="middle" x="262.78" y="-270.55" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="black" d="M238.11,-273.39C237.63,-281.55 237.12,-290.26 236.66,-298.21"/> +<polygon fill="black" stroke="black" points="233.17,-297.84 236.08,-308.03 240.16,-298.25 233.17,-297.84"/> +<text text-anchor="middle" x="235.86" y="-275.54" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Div21 --> -<g id="node10" class="node"> +<g id="node11" class="node"> <title>Div21</title> -<ellipse fill="none" stroke="black" cx="43" cy="-537.67" rx="27" ry="18"/> -<text text-anchor="middle" x="43" y="-533.97" font-family="Times,serif" font-size="14.00">//</text> +<ellipse fill="none" stroke="black" cx="43" cy="-543.57" rx="27" ry="18"/> +<text text-anchor="middle" x="43" y="-538.52" font-family="Times New Roman,serif" font-size="14.00">//</text> </g> <!-- Div21->Start2 --> <g id="edge17" class="edge"> <title>Div21->Start2</title> -<path fill="none" stroke="red" d="M60.82,-551.25C73.8,-560.41 92.1,-573.33 109.58,-585.67"/> -<polygon fill="red" stroke="red" points="107.88,-588.75 118.07,-591.66 111.91,-583.04 107.88,-588.75"/> -<text text-anchor="middle" x="57.82" y="-553.85" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="red" d="M63.39,-555.74C80.45,-565.09 105.82,-578.99 130.01,-592.25"/> +<polygon fill="red" stroke="red" points="128.14,-595.22 138.59,-596.96 131.5,-589.08 128.14,-595.22"/> +<text text-anchor="middle" x="65.64" y="-557.89" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Div21->Start2 --> <g id="edge18" class="edge"> -<title>Div21->Start2:p1</title> -<path fill="none" stroke="black" d="M64.54,-548.87C92.71,-562.22 140.81,-585.08 160.83,-594.93"/> -<polygon fill="black" stroke="black" points="159.51,-598.19 170,-599.67 162.72,-591.97 159.51,-598.19"/> -<text text-anchor="middle" x="67.54" y="-551.47" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<title>Div21->Start2:p2</title> +<path fill="none" stroke="black" d="M64.07,-555.23C68.91,-557.48 74.08,-559.72 79,-561.57 142.52,-585.33 224.7,-539.5 234.11,-594.15"/> +<polygon fill="black" stroke="black" points="230.62,-594.36 234.88,-604.06 237.59,-593.81 230.62,-594.36"/> +<text text-anchor="middle" x="66.32" y="-547.63" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Con_22 --> -<g id="node11" class="node"> +<g id="node12" class="node"> <title>Con_22</title> -<ellipse fill="none" stroke="black" cx="43" cy="-623.67" rx="27" ry="18"/> -<text text-anchor="middle" x="43" y="-619.97" font-family="Times,serif" font-size="14.00">#3</text> +<ellipse fill="none" stroke="black" cx="43" cy="-631.07" rx="27" ry="18"/> +<text text-anchor="middle" x="43" y="-626.02" font-family="Times New Roman,serif" font-size="14.00">#3</text> </g> <!-- Div21->Con_22 --> <g id="edge19" class="edge"> <title>Div21->Con_22</title> -<path fill="none" stroke="black" d="M43,-556.08C43,-567.42 43,-582.45 43,-595.36"/> -<polygon fill="black" stroke="black" points="39.5,-595.58 43,-605.58 46.5,-595.58 39.5,-595.58"/> -<text text-anchor="middle" x="40" y="-558.68" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="black" d="M43,-561.87C43,-573.22 43,-588.35 43,-601.47"/> +<polygon fill="black" stroke="black" points="39.5,-601.28 43,-611.28 46.5,-601.28 39.5,-601.28"/> +<text text-anchor="middle" x="40.75" y="-564.02" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- EQ23 --> -<g id="node12" class="node"> +<g id="node13" class="node"> <title>EQ23</title> -<ellipse fill="none" stroke="black" cx="361" cy="-321.67" rx="27" ry="18"/> -<text text-anchor="middle" x="361" y="-317.97" font-family="Times,serif" font-size="14.00">==</text> +<ellipse fill="none" stroke="black" cx="367" cy="-327.57" rx="27" ry="18"/> +<text text-anchor="middle" x="367" y="-322.52" font-family="Times New Roman,serif" font-size="14.00">==</text> </g> <!-- EQ23->Loop11 --> <g id="edge20" class="edge"> <title>EQ23->Loop11</title> -<path fill="none" stroke="red" d="M339.92,-333.3C335.08,-335.55 329.91,-337.8 325,-339.67 274.96,-358.74 257.65,-355.93 203.69,-375.71"/> -<polygon fill="red" stroke="red" points="202.46,-372.44 194.33,-379.24 204.92,-378.99 202.46,-372.44"/> -<text text-anchor="middle" x="336.92" y="-335.9" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="red" d="M347.18,-340.12C330.14,-350.07 305.08,-364.69 284.29,-376.81"/> +<polygon fill="red" stroke="red" points="282.66,-373.71 275.79,-381.77 286.19,-379.76 282.66,-373.71"/> +<text text-anchor="middle" x="349.43" y="-342.27" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- EQ23->Phi_arg13 --> <g id="edge21" class="edge"> <title>EQ23->Phi_arg13</title> -<path fill="none" stroke="black" d="M356.16,-339.62C353.93,-347.41 351.23,-356.85 348.74,-365.6"/> -<polygon fill="black" stroke="black" points="345.32,-364.82 345.94,-375.4 352.05,-366.75 345.32,-364.82"/> -<text text-anchor="middle" x="353.16" y="-342.22" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="black" d="M357.26,-344.8C352.4,-352.9 346.4,-362.9 340.89,-372.07"/> +<polygon fill="black" stroke="black" points="337.9,-370.26 335.76,-380.63 343.9,-373.86 337.9,-370.26"/> +<text text-anchor="middle" x="355.01" y="-346.95" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Con_24 --> -<g id="node13" class="node"> +<g id="node14" class="node"> <title>Con_24</title> -<ellipse fill="none" stroke="black" cx="423" cy="-393.67" rx="27" ry="18"/> -<text text-anchor="middle" x="423" y="-389.97" font-family="Times,serif" font-size="14.00">#5</text> +<ellipse fill="none" stroke="black" cx="403" cy="-399.57" rx="27" ry="18"/> +<text text-anchor="middle" x="403" y="-394.52" font-family="Times New Roman,serif" font-size="14.00">#5</text> </g> <!-- EQ23->Con_24 --> <g id="edge22" class="edge"> <title>EQ23->Con_24</title> -<path fill="none" stroke="black" d="M374.15,-337.52C382.53,-346.99 393.56,-359.44 403.02,-370.11"/> -<polygon fill="black" stroke="black" points="400.62,-372.69 409.87,-377.85 405.86,-368.04 400.62,-372.69"/> -<text text-anchor="middle" x="371.15" y="-340.12" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="black" d="M375.35,-344.8C379.58,-353.02 384.81,-363.19 389.58,-372.47"/> +<polygon fill="black" stroke="black" points="386.32,-373.78 394,-381.07 392.54,-370.58 386.32,-373.78"/> +<text text-anchor="middle" x="373.1" y="-346.95" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- If25 --> -<g id="node14" class="node"> +<g id="node15" class="node"> <title>If25</title> -<polygon fill="yellow" stroke="transparent" points="210,-138.67 210,-163.67 304,-163.67 304,-138.67 210,-138.67"/> -<polygon fill="none" stroke="black" points="210,-138.67 210,-163.67 304,-163.67 304,-138.67 210,-138.67"/> -<text text-anchor="start" x="251.5" y="-147.47" font-family="Times,serif" font-size="14.00">If</text> -<polygon fill="none" stroke="black" points="210,-107.67 210,-138.67 304,-138.67 304,-107.67 210,-107.67"/> -<polygon fill="yellow" stroke="transparent" points="215,-112.67 215,-133.67 255,-133.67 255,-112.67 215,-112.67"/> -<polygon fill="none" stroke="black" points="215,-112.67 215,-133.67 255,-133.67 255,-112.67 215,-112.67"/> -<text text-anchor="start" x="218" y="-119.47" font-family="Times,serif" font-size="14.00">True</text> -<polygon fill="yellow" stroke="transparent" points="255,-112.67 255,-133.67 299,-133.67 299,-112.67 255,-112.67"/> -<polygon fill="none" stroke="black" points="255,-112.67 255,-133.67 299,-133.67 299,-112.67 255,-112.67"/> -<text text-anchor="start" x="258" y="-119.47" font-family="Times,serif" font-size="14.00">False</text> +<polygon fill="yellow" stroke="none" points="229,-140.07 229,-166.57 305,-166.57 305,-140.07 229,-140.07"/> +<polygon fill="none" stroke="black" points="229,-140.07 229,-166.57 305,-166.57 305,-140.07 229,-140.07"/> +<text text-anchor="start" x="262.5" y="-148.27" font-family="Times New Roman,serif" font-size="14.00">If</text> +<polygon fill="none" stroke="black" points="229,-107.57 229,-140.07 305,-140.07 305,-107.57 229,-107.57"/> +<polygon fill="yellow" stroke="none" points="234,-112.57 234,-135.07 265.5,-135.07 265.5,-112.57 234,-112.57"/> +<polygon fill="none" stroke="black" points="234,-112.57 234,-135.07 265.5,-135.07 265.5,-112.57 234,-112.57"/> +<text text-anchor="start" x="237" y="-118.77" font-family="Times New Roman,serif" font-size="14.00">True</text> +<polygon fill="yellow" stroke="none" points="265.5,-112.57 265.5,-135.07 300,-135.07 300,-112.57 265.5,-112.57"/> +<polygon fill="none" stroke="black" points="265.5,-112.57 265.5,-135.07 300,-135.07 300,-112.57 265.5,-112.57"/> +<text text-anchor="start" x="268.5" y="-118.77" font-family="Times New Roman,serif" font-size="14.00">False</text> </g> <!-- If25->If16 --> <g id="edge23" class="edge"> <title>If25->If16:p0</title> -<path fill="none" stroke="red" d="M240.36,-167.91C236.06,-178.07 232.22,-189.75 230.69,-201.44"/> -<polygon fill="red" stroke="red" points="227.18,-201.46 230,-211.67 234.17,-201.93 227.18,-201.46"/> -<text text-anchor="middle" x="237.36" y="-170.51" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="red" d="M238.48,-170.44C231.99,-180.18 226.3,-191.42 223.93,-203.17"/> +<polygon fill="red" stroke="red" points="220.45,-202.75 222.91,-213.06 227.42,-203.47 220.45,-202.75"/> +<text text-anchor="middle" x="236.23" y="-172.59" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- If25->EQ23 --> <g id="edge24" class="edge"> <title>If25->EQ23</title> -<path fill="none" stroke="black" d="M288.6,-167.9C298.28,-178.61 308.37,-191.08 316,-203.67 333.46,-232.5 346.27,-269.27 353.66,-293.82"/> -<polygon fill="black" stroke="black" points="350.33,-294.92 356.49,-303.54 357.05,-292.96 350.33,-294.92"/> -<text text-anchor="middle" x="285.6" y="-170.5" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> -</g> -<!-- Phi_230 --> -<g id="node15" class="node"> -<title>Phi_230</title> -<ellipse fill="lightyellow" stroke="black" cx="89" cy="-393.67" rx="33.29" ry="18"/> -<text text-anchor="middle" x="89" y="-389.97" font-family="Times,serif" font-size="14.00">φ_$2</text> +<path fill="none" stroke="black" d="M295.16,-170.52C303.84,-181.5 312.95,-194.14 320,-206.57 336.96,-236.47 350.49,-273.71 358.6,-298.71"/> +<polygon fill="black" stroke="black" points="355.23,-299.66 361.58,-308.14 361.91,-297.55 355.23,-299.66"/> +<text text-anchor="middle" x="292.91" y="-172.67" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Phi_230->Store10 --> <g id="edge26" class="edge"> <title>Phi_230->Store10</title> -<path fill="none" stroke="blue" d="M100.36,-410.91C106.57,-419.78 114.38,-430.94 121.28,-440.78"/> -<polygon fill="blue" stroke="blue" points="118.43,-442.82 127.03,-449.01 124.17,-438.81 118.43,-442.82"/> -<text text-anchor="middle" x="97.36" y="-413.51" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="blue" d="M113.7,-417.87C115.72,-425.33 118.14,-434.24 120.41,-442.6"/> +<polygon fill="blue" stroke="blue" points="116.96,-443.27 122.96,-452 123.72,-441.43 116.96,-443.27"/> +<text text-anchor="middle" x="111.45" y="-420.02" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Phi_230->Loop11 --> <g id="edge25" class="edge"> <title>Phi_230->Loop11</title> -<path fill="none" stroke="black" stroke-dasharray="1,5" d="M122.21,-393.67C124.67,-393.67 127.13,-393.67 129.59,-393.67"/> -<polygon fill="black" stroke="black" points="129.71,-397.17 139.71,-393.67 129.71,-390.17 129.71,-397.17"/> -<text text-anchor="middle" x="125.21" y="-387.27" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="black" stroke-dasharray="1,5" d="M162.37,-399.57C177.71,-399.57 193.05,-399.57 208.39,-399.57"/> +<polygon fill="black" stroke="black" points="208.05,-403.07 218.05,-399.57 208.05,-396.07 208.05,-403.07"/> +<text text-anchor="middle" x="164.62" y="-391.97" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Phi_233 --> -<g id="node18" class="node"> +<g id="node19" class="node"> <title>Phi_233</title> -<ellipse fill="lightyellow" stroke="black" cx="61" cy="-49.67" rx="33.29" ry="18"/> -<text text-anchor="middle" x="61" y="-45.97" font-family="Times,serif" font-size="14.00">φ_$2</text> +<ellipse fill="lightyellow" stroke="black" cx="69" cy="-49.57" rx="52.89" ry="18"/> +<text text-anchor="middle" x="69" y="-44.52" font-family="Times New Roman,serif" font-size="14.00">MemPhi_2</text> </g> <!-- Store31 --> -<g id="node16" class="node"> +<g id="node17" class="node"> <title>Store31</title> -<ellipse fill="none" stroke="black" cx="44" cy="-321.67" rx="27.9" ry="18"/> -<text text-anchor="middle" x="44" y="-317.97" font-family="Times,serif" font-size="14.00">.f =</text> +<ellipse fill="none" stroke="black" cx="43" cy="-327.57" rx="27" ry="18"/> +<text text-anchor="middle" x="43" y="-322.52" font-family="Times New Roman,serif" font-size="14.00">.f =</text> </g> <!-- Store31->new9 --> <g id="edge30" class="edge"> <title>Store31->new9</title> -<path fill="none" stroke="black" d="M30,-466.67C39.91,-492.39 65.61,-510.26 88.07,-521.5"/> -<polygon fill="black" stroke="black" points="86.58,-524.67 97.12,-525.77 89.57,-518.34 86.58,-524.67"/> -<text text-anchor="middle" x="27" y="-460.27" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="black" d="M30,-472.57C39.38,-497.6 63.9,-515.54 85.27,-526.98"/> +<polygon fill="black" stroke="black" points="83.57,-530.04 94.08,-531.4 86.71,-523.79 83.57,-530.04"/> +<text text-anchor="middle" x="27.75" y="-464.97" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Store31->Loop11 --> <g id="edge28" class="edge"> <title>Store31->Loop11</title> -<path fill="none" stroke="red" d="M64.31,-334.23C82.34,-344.49 109.11,-359.73 130.8,-372.07"/> -<polygon fill="red" stroke="red" points="129.32,-375.26 139.75,-377.16 132.79,-369.18 129.32,-375.26"/> -<text text-anchor="middle" x="61.31" y="-336.83" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="red" d="M66.8,-336.73C101.75,-348.72 167.31,-371.22 208.98,-385.52"/> +<polygon fill="red" stroke="red" points="207.77,-388.8 218.36,-388.74 210.04,-382.18 207.77,-388.8"/> +<text text-anchor="middle" x="64.55" y="-338.88" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Store31->Div21 --> <g id="edge31" class="edge"> <title>Store31->Div21</title> -<path fill="none" stroke="black" d="M37.26,-339.26C27.22,-366.66 11.4,-422.52 30,-464.67"/> -<path fill="none" stroke="black" d="M30,-466.67C35.26,-480.33 38.43,-496.32 40.33,-509.57"/> -<polygon fill="black" stroke="black" points="36.86,-510.03 41.59,-519.51 43.8,-509.15 36.86,-510.03"/> -<text text-anchor="middle" x="34.26" y="-341.86" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> +<path fill="none" stroke="black" d="M36.55,-345.12C26.96,-372.45 11.89,-428.23 30,-470.57"/> +<path fill="none" stroke="black" d="M30,-472.57C34.94,-485.77 38.06,-501.11 40,-514.05"/> +<polygon fill="black" stroke="black" points="36.49,-514.19 41.27,-523.64 43.43,-513.27 36.49,-514.19"/> +<text text-anchor="middle" x="34.3" y="-347.27" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> </g> <!-- Store31->Phi_230 --> <g id="edge29" class="edge"> <title>Store31->Phi_230</title> -<path fill="none" stroke="blue" d="M54.21,-338.56C59.77,-347.21 66.77,-358.1 73.04,-367.84"/> -<polygon fill="blue" stroke="blue" points="70.17,-369.85 78.52,-376.37 76.05,-366.06 70.17,-369.85"/> -<text text-anchor="middle" x="51.21" y="-341.16" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="blue" d="M57,-343.41C65.39,-352.32 76.28,-363.86 85.92,-374.08"/> +<polygon fill="blue" stroke="blue" points="83.2,-376.3 92.6,-381.17 88.29,-371.5 83.2,-376.3"/> +<text text-anchor="middle" x="54.75" y="-345.56" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Region32->If25 --> <g id="edge32" class="edge"> <title>Region32->If25:p0</title> -<path fill="none" stroke="red" d="M235,-67.77C235,-77.22 235,-89.45 235,-101.36"/> -<polygon fill="red" stroke="red" points="231.5,-101.67 235,-111.67 238.5,-101.67 231.5,-101.67"/> -<text text-anchor="middle" x="232" y="-70.37" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="red" d="M249.89,-67.92C249.84,-77.07 249.79,-88.77 249.76,-100.25"/> +<polygon fill="red" stroke="red" points="246.26,-100.05 249.75,-110.05 253.26,-100.06 246.26,-100.05"/> +<text text-anchor="middle" x="247.64" y="-70.07" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Region32->If25 --> <g id="edge33" class="edge"> <title>Region32->If25:p1</title> -<path fill="none" stroke="red" d="M255.02,-67.8C263.64,-76.74 272.48,-88.51 275.71,-101.52"/> -<polygon fill="red" stroke="red" points="272.27,-102.19 277,-111.67 279.22,-101.31 272.27,-102.19"/> -<text text-anchor="middle" x="252.02" y="-70.4" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="red" d="M265.22,-67.95C271.77,-76.79 278.57,-88.22 281.39,-100.32"/> +<polygon fill="red" stroke="red" points="277.9,-100.55 282.57,-110.06 284.84,-99.72 277.9,-100.55"/> +<text text-anchor="middle" x="262.97" y="-70.1" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Phi_233->Phi_230 --> <g id="edge36" class="edge"> <title>Phi_233->Phi_230</title> -<path fill="none" stroke="blue" d="M63.28,-78.47C68.55,-142.92 81.57,-301.9 86.79,-365.62"/> -<polygon fill="blue" stroke="blue" points="66.73,-77.79 62.43,-68.1 59.75,-78.36 66.73,-77.79"/> -<polygon fill="blue" stroke="blue" points="83.3,-366 87.61,-375.69 90.28,-365.43 83.3,-366"/> -<text text-anchor="middle" x="59.43" y="-70.7" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="blue" d="M117.06,-66.52C146.85,-77.29 181.4,-91.65 192,-103.57 212.65,-126.78 191.37,-147.32 212,-170.57 238.69,-200.64 272.94,-172.95 295,-206.57 329.4,-258.98 315.45,-301.36 271,-345.57 244.42,-372 204.48,-385.25 170.9,-391.89"/> +<polygon fill="blue" stroke="blue" points="118.37,-63.28 107.78,-63.24 116.04,-69.87 118.37,-63.28"/> +<polygon fill="blue" stroke="blue" points="170.5,-388.41 161.28,-393.63 171.74,-395.3 170.5,-388.41"/> +<text text-anchor="middle" x="108.6" y="-64.88" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Phi_233->Store31 --> <g id="edge35" class="edge"> <title>Phi_233->Store31</title> -<path fill="none" stroke="blue" d="M59.91,-67.97C57.03,-113.69 49.21,-238 45.72,-293.4"/> -<polygon fill="blue" stroke="blue" points="42.22,-293.32 45.08,-303.52 49.2,-293.76 42.22,-293.32"/> -<text text-anchor="middle" x="56.91" y="-70.57" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="blue" d="M67.37,-67.83C63.02,-114.01 51.09,-240.72 45.7,-297.9"/> +<polygon fill="blue" stroke="blue" points="42.22,-297.52 44.77,-307.8 49.19,-298.17 42.22,-297.52"/> +<text text-anchor="middle" x="65.12" y="-69.98" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Phi_233->Region32 --> <g id="edge34" class="edge"> <title>Phi_233->Region32</title> -<path fill="none" stroke="black" stroke-dasharray="1,5" d="M77.36,-33.99C89.26,-23.73 106.31,-11.24 124,-5.67 146.89,1.53 155.28,2.07 178,-5.67 189.71,-9.67 200.89,-17.16 210.2,-24.83"/> -<polygon fill="black" stroke="black" points="208.03,-27.59 217.86,-31.55 212.65,-22.33 208.03,-27.59"/> -<text text-anchor="middle" x="74.36" y="-27.59" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="black" stroke-dasharray="1,5" d="M89.57,-32.51C103.01,-22.52 121.45,-10.85 140,-5.57 163.08,1.01 171.32,2.28 194,-5.57 205.22,-9.45 215.9,-16.6 224.86,-24.03"/> +<polygon fill="black" stroke="black" points="222.28,-26.42 232.09,-30.44 226.93,-21.19 222.28,-26.42"/> +<text text-anchor="middle" x="87.32" y="-24.91" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Sub35->Loop11 --> <g id="edge37" class="edge"> <title>Sub35->Loop11</title> -<path fill="none" stroke="red" d="M205.9,-338.21C199.85,-346.68 192.2,-357.39 185.3,-367.06"/> -<polygon fill="red" stroke="red" points="182.44,-365.04 179.47,-375.21 188.13,-369.11 182.44,-365.04"/> -<text text-anchor="middle" x="202.9" y="-340.81" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="red" d="M417.91,-339.17C413.07,-341.42 407.9,-343.68 403,-345.57 354.16,-364.3 337.06,-362 284.88,-381.16"/> +<polygon fill="red" stroke="red" points="283.87,-377.8 275.75,-384.61 286.34,-384.35 283.87,-377.8"/> +<text text-anchor="middle" x="415.66" y="-341.32" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Sub35->Phi_arg13 --> <g id="edge38" class="edge"> <title>Sub35->Phi_arg13</title> -<path fill="none" stroke="black" d="M245.77,-338.92C264.66,-349.58 289.38,-363.54 308.91,-374.56"/> -<polygon fill="black" stroke="black" points="247.35,-335.79 236.92,-333.92 243.91,-341.89 247.35,-335.79"/> -<polygon fill="black" stroke="black" points="307.32,-377.68 317.75,-379.55 310.76,-371.58 307.32,-377.68"/> -<text text-anchor="middle" x="233.92" y="-336.52" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="black" d="M409.92,-346.42C393.35,-356.59 372.61,-369.33 355.78,-379.67"/> +<polygon fill="black" stroke="black" points="411.68,-349.45 418.37,-341.23 408.02,-343.48 411.68,-349.45"/> +<polygon fill="black" stroke="black" points="354.36,-376.43 347.67,-384.65 358.02,-382.4 354.36,-376.43"/> +<text text-anchor="middle" x="419.66" y="-342.59" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Con_36 --> -<g id="node20" class="node"> +<g id="node21" class="node"> <title>Con_36</title> -<ellipse fill="none" stroke="black" cx="239" cy="-393.67" rx="27" ry="18"/> -<text text-anchor="middle" x="239" y="-389.97" font-family="Times,serif" font-size="14.00">#1</text> +<ellipse fill="none" stroke="black" cx="475" cy="-399.57" rx="27" ry="18"/> +<text text-anchor="middle" x="475" y="-394.52" font-family="Times New Roman,serif" font-size="14.00">#1</text> </g> <!-- Sub35->Con_36 --> <g id="edge39" class="edge"> <title>Sub35->Con_36</title> -<path fill="none" stroke="black" d="M222.33,-339.62C224.84,-347.62 227.9,-357.34 230.7,-366.27"/> -<polygon fill="black" stroke="black" points="227.39,-367.39 233.72,-375.89 234.06,-365.3 227.39,-367.39"/> -<text text-anchor="middle" x="219.33" y="-342.22" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="black" d="M447.35,-344.8C451.58,-353.02 456.81,-363.19 461.58,-372.47"/> +<polygon fill="black" stroke="black" points="458.32,-373.78 466,-381.07 464.54,-370.58 458.32,-373.78"/> +<text text-anchor="middle" x="445.1" y="-346.95" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Return37->MemMerge --> +<g id="edge42" class="edge"> +<title>Return37->MemMerge</title> +<path fill="none" stroke="blue" d="M89,-241.07C86.12,-264.36 101.49,-285.96 117.39,-301.62"/> +<polygon fill="blue" stroke="blue" points="114.82,-304.01 124.55,-308.22 119.57,-298.86 114.82,-304.01"/> +<text text-anchor="middle" x="86.75" y="-233.47" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Return37->new9 --> <g id="edge41" class="edge"> <title>Return37->new9</title> -<path fill="none" stroke="black" d="M152.88,-153.84C154.6,-173.54 156.18,-206.74 151,-234.67"/> -<path fill="none" stroke="black" d="M151,-236.67C140.92,-291.1 115.67,-296.53 81,-339.67 67.22,-356.83 55.13,-355.22 47,-375.67 26.8,-426.48 71.69,-483.13 102.06,-513.85"/> -<polygon fill="black" stroke="black" points="99.83,-516.56 109.42,-521.08 104.74,-511.57 99.83,-516.56"/> -<text text-anchor="middle" x="149.88" y="-156.44" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="black" d="M137.23,-155.23C118.91,-173.78 93.19,-205.24 89,-239.07"/> +<path fill="none" stroke="black" d="M89,-241.07C83.27,-287.37 97.64,-302.79 79,-345.57 70.45,-365.19 54.61,-361.56 47,-381.57 41.31,-396.52 42.73,-402.15 47,-417.57 57.57,-455.72 83.23,-493.93 101.59,-517.87"/> +<polygon fill="black" stroke="black" points="98.84,-520.03 107.77,-525.73 104.34,-515.7 98.84,-520.03"/> +<text text-anchor="middle" x="134.98" y="-157.38" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Return37->If16 --> <g id="edge40" class="edge"> <title>Return37->If16:p1</title> -<path fill="none" stroke="red" d="M173.3,-153.95C179.56,-158.57 186.45,-163.46 193,-167.67 223.49,-187.31 263.28,-174.61 270.76,-201.47"/> -<polygon fill="red" stroke="red" points="267.32,-202.17 272,-211.67 274.27,-201.33 267.32,-202.17"/> -<text text-anchor="middle" x="170.3" y="-156.55" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> -</g> -<!-- Return37->Phi_230 --> -<g id="edge42" class="edge"> -<title>Return37->Phi_230</title> -<path fill="none" stroke="blue" d="M151,-236.67C145.42,-285.27 120.51,-337.2 104.01,-367.2"/> -<polygon fill="blue" stroke="blue" points="100.84,-365.71 98.99,-376.14 106.94,-369.14 100.84,-365.71"/> -<text text-anchor="middle" x="148" y="-230.27" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="red" d="M177.62,-155.38C183.85,-160.32 190.67,-165.69 197,-170.57 219.31,-187.76 246.88,-183.75 254.01,-203.32"/> +<polygon fill="red" stroke="red" points="250.53,-203.72 255.52,-213.07 257.45,-202.65 250.53,-203.72"/> +<text text-anchor="middle" x="175.37" y="-157.53" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> </g> </svg> diff --git a/chapter11/docs/graph6.gv b/chapter11/docs/graph6.gv index dfd07d7e..ff97eebc 100644 --- a/chapter11/docs/graph6.gv +++ b/chapter11/docs/graph6.gv @@ -21,11 +21,12 @@ return v; <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></TR> + <TR><TD PORT="p0" BGCOLOR="yellow">$ctrl</TD><TD PORT="p1">$mem</TD><TD PORT="p2">arg</TD></TR> </TABLE> </TD></TR> </TABLE>> ]; + MemMerge [ shape=box label="MemMerge" ]; Stop3 [ shape=box style=filled fillcolor=yellow label="Stop" ]; Con_4 [ label="#0" ]; new9 [ label="new S" ]; @@ -57,10 +58,10 @@ return v; </TD></TR> </TABLE>> ]; - Phi_230 [ style=filled fillcolor=lightyellow label="φ_$2" ]; + Phi_230 [ style=filled fillcolor=lightyellow label="MemPhi_2" ]; Store31 [ label=".f =" ]; Region32 [ shape=box style=filled fillcolor=yellow label="Region" ]; - Phi_233 [ style=filled fillcolor=lightyellow label="φ_$2" ]; + Phi_233 [ style=filled fillcolor=lightyellow label="MemPhi_2" ]; Sub35 [ label="-" ]; Con_36 [ label="#1" ]; Return37 [ shape=box style=filled fillcolor=yellow label="Return" ]; @@ -71,13 +72,13 @@ return v; Stop3 -> Return37[taillabel=0 color=red]; new9 -> Start2:p0[taillabel=0 color=green]; Store10 -> Start2:p0[taillabel=0 color=red]; - Store10 -> Start2:p2[taillabel=1 color=blue]; + Store10 -> Start2:p1[taillabel=1 color=blue]; Store10 -> new9[taillabel=2]; Store10 -> Con_4[taillabel=3]; Loop11 -> Start2:p0[taillabel=1 color=red]; Loop11 -> Region32[taillabel=2 color=red constraint=false]; Phi_arg13 -> Loop11 [style=dotted taillabel=0]; - Phi_arg13 -> Start2:p1[taillabel=1]; + Phi_arg13 -> Start2:p2[taillabel=1]; Phi_arg13 -> Sub35[taillabel=2 constraint=false]; LT14 -> Loop11[taillabel=0 color=red]; LT14 -> Con_4[taillabel=1]; @@ -85,7 +86,7 @@ return v; If16 -> Loop11[taillabel=0 color=red]; If16 -> LT14[taillabel=1]; Div21 -> Start2:p0[taillabel=0 color=red]; - Div21 -> Start2:p1[taillabel=1]; + Div21 -> Start2:p2[taillabel=1]; Div21 -> Con_22[taillabel=2]; EQ23 -> If16:p0[taillabel=0 color=red]; EQ23 -> Phi_arg13[taillabel=1]; @@ -108,6 +109,9 @@ return v; Sub35 -> Phi_arg13[taillabel=1]; Sub35 -> Con_36[taillabel=2]; Return37 -> If16:p1[taillabel=0 color=red]; - Return37 -> new9[taillabel=1]; - Return37 -> Phi_230[taillabel=2 color=blue]; + Return37 -> new9[taillabel=2]; + Return37 -> MemMerge[taillabel=1 color=blue]; + MemMerge -> Start2:p1[taillabel=1 color=blue]; + MemMerge -> Phi_230[taillabel=2 color=blue]; + MemMerge -> If16:p1[taillabel=0 color=green]; } diff --git a/chapter11/docs/graph6.svg b/chapter11/docs/graph6.svg index 354f5d2f..8b8bb190 100644 --- a/chapter11/docs/graph6.svg +++ b/chapter11/docs/graph6.svg @@ -1,437 +1,471 @@ <?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) +<!-- Generated by graphviz version 12.2.0 (20241103.1931) --> <!-- Title: chapter11 Pages: 1 --> -<svg width="382pt" height="880pt" - viewBox="0.00 0.00 382.00 880.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 876)"> +<svg width="384pt" height="889pt" + viewBox="0.00 0.00 384.00 889.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 885)"> <title>chapter11</title> -<polygon fill="white" stroke="transparent" points="-4,4 -4,-876 378,-876 378,4 -4,4"/> +<polygon fill="white" stroke="none" points="-4,4 -4,-885 380,-885 380,4 -4,4"/> <g id="clust1" class="cluster"> <title>cluster_Nodes</title> -<polygon fill="none" stroke="black" points="8,-8 8,-864 366,-864 366,-8 8,-8"/> +<polygon fill="none" stroke="black" points="8,-8 8,-873 368,-873 368,-8 8,-8"/> </g> <!-- Start2 --> <g id="node1" class="node"> <title>Start2</title> -<polygon fill="yellow" stroke="transparent" points="160,-827 160,-852 266,-852 266,-827 160,-827"/> -<polygon fill="none" stroke="black" points="160,-827 160,-852 266,-852 266,-827 160,-827"/> -<text text-anchor="start" x="194.5" y="-835.8" font-family="Times,serif" font-size="14.00">Start</text> -<polygon fill="none" stroke="black" points="160,-796 160,-827 266,-827 266,-796 160,-796"/> -<polygon fill="yellow" stroke="transparent" points="165,-801 165,-822 205,-822 205,-801 165,-801"/> -<polygon fill="none" stroke="black" points="165,-801 165,-822 205,-822 205,-801 165,-801"/> -<text text-anchor="start" x="168" y="-807.8" font-family="Times,serif" font-size="14.00">$ctrl</text> -<polygon fill="none" stroke="black" points="205,-801 205,-822 236,-822 236,-801 205,-801"/> -<text text-anchor="start" x="208" y="-807.8" font-family="Times,serif" font-size="14.00">arg</text> -<polygon fill="none" stroke="black" points="236,-801 236,-822 261,-822 261,-801 236,-801"/> -<text text-anchor="start" x="239" y="-807.8" font-family="Times,serif" font-size="14.00">$2</text> +<polygon fill="yellow" stroke="none" points="172.38,-834.5 172.38,-861 277.62,-861 277.62,-834.5 172.38,-834.5"/> +<polygon fill="none" stroke="black" points="172.38,-834.5 172.38,-861 277.62,-861 277.62,-834.5 172.38,-834.5"/> +<text text-anchor="start" x="212.25" y="-842.7" font-family="Times New Roman,serif" font-size="14.00">Start</text> +<polygon fill="none" stroke="black" points="172.38,-802 172.38,-834.5 277.62,-834.5 277.62,-802 172.38,-802"/> +<polygon fill="yellow" stroke="none" points="177.38,-807 177.38,-829.5 208.12,-829.5 208.12,-807 177.38,-807"/> +<polygon fill="none" stroke="black" points="177.38,-807 177.38,-829.5 208.12,-829.5 208.12,-807 177.38,-807"/> +<text text-anchor="start" x="180.38" y="-813.2" font-family="Times New Roman,serif" font-size="14.00">$ctrl</text> +<polygon fill="none" stroke="black" points="208.12,-807 208.12,-829.5 249.38,-829.5 249.38,-807 208.12,-807"/> +<text text-anchor="start" x="211.12" y="-813.2" font-family="Times New Roman,serif" font-size="14.00">$mem</text> +<polygon fill="none" stroke="black" points="249.38,-807 249.38,-829.5 272.62,-829.5 272.62,-807 249.38,-807"/> +<text text-anchor="start" x="252.38" y="-813.2" font-family="Times New Roman,serif" font-size="14.00">arg</text> +</g> +<!-- MemMerge --> +<g id="node2" class="node"> +<title>MemMerge</title> +<polygon fill="none" stroke="black" points="333,-371 251,-371 251,-335 333,-335 333,-371"/> +<text text-anchor="middle" x="292" y="-347.95" font-family="Times New Roman,serif" font-size="14.00">MemMerge</text> +</g> +<!-- MemMerge->Start2 --> +<g id="edge43" class="edge"> +<title>MemMerge->Start2:p1</title> +<path fill="none" stroke="blue" d="M307.5,-371.34C315.32,-381.11 324.15,-393.96 329,-407 361.8,-495.22 367.07,-526.05 347,-618 331.99,-686.78 323.62,-705.97 281,-762 265.35,-782.58 238.46,-778 230.83,-794.94"/> +<polygon fill="blue" stroke="blue" points="227.43,-794.04 229.03,-804.51 234.31,-795.33 227.43,-794.04"/> +<text text-anchor="middle" x="305.25" y="-373.49" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- If16 --> +<g id="node10" class="node"> +<title>If16</title> +<polygon fill="yellow" stroke="none" points="236,-443.5 236,-470 312,-470 312,-443.5 236,-443.5"/> +<polygon fill="none" stroke="black" points="236,-443.5 236,-470 312,-470 312,-443.5 236,-443.5"/> +<text text-anchor="start" x="269.5" y="-451.7" font-family="Times New Roman,serif" font-size="14.00">If</text> +<polygon fill="none" stroke="black" points="236,-411 236,-443.5 312,-443.5 312,-411 236,-411"/> +<polygon fill="yellow" stroke="none" points="241,-416 241,-438.5 272.5,-438.5 272.5,-416 241,-416"/> +<polygon fill="none" stroke="black" points="241,-416 241,-438.5 272.5,-438.5 272.5,-416 241,-416"/> +<text text-anchor="start" x="244" y="-422.2" font-family="Times New Roman,serif" font-size="14.00">True</text> +<polygon fill="yellow" stroke="none" points="272.5,-416 272.5,-438.5 307,-438.5 307,-416 272.5,-416"/> +<polygon fill="none" stroke="black" points="272.5,-416 272.5,-438.5 307,-438.5 307,-416 272.5,-416"/> +<text text-anchor="start" x="275.5" y="-422.2" font-family="Times New Roman,serif" font-size="14.00">False</text> +</g> +<!-- MemMerge->If16 --> +<g id="edge45" class="edge"> +<title>MemMerge->If16:p1</title> +<path fill="none" stroke="green" d="M291.02,-371.35C290.56,-380.5 290.08,-392.19 289.86,-403.68"/> +<polygon fill="green" stroke="green" points="286.37,-403.45 289.77,-413.49 293.36,-403.52 286.37,-403.45"/> +<text text-anchor="middle" x="288.77" y="-373.5" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +</g> +<!-- Phi_230 --> +<g id="node16" class="node"> +<title>Phi_230</title> +<ellipse fill="lightyellow" stroke="black" cx="109" cy="-600" rx="52.89" ry="18"/> +<text text-anchor="middle" x="109" y="-594.95" font-family="Times New Roman,serif" font-size="14.00">MemPhi_2</text> +</g> +<!-- MemMerge->Phi_230 --> +<g id="edge44" class="edge"> +<title>MemMerge->Phi_230</title> +<path fill="none" stroke="blue" d="M262.46,-371.47C248.23,-380.82 231.57,-393.24 219,-407 195.6,-432.6 205.05,-451.1 179,-474 134.6,-513.03 57.61,-467.89 57,-527"/> +<text text-anchor="middle" x="260.21" y="-373.62" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Stop3 --> -<g id="node2" class="node"> +<g id="node3" class="node"> <title>Stop3</title> -<polygon fill="yellow" stroke="black" points="150,-282 96,-282 96,-246 150,-246 150,-282"/> -<text text-anchor="middle" x="123" y="-260.3" font-family="Times,serif" font-size="14.00">Stop</text> +<polygon fill="yellow" stroke="black" points="260,-196 206,-196 206,-160 260,-160 260,-196"/> +<text text-anchor="middle" x="233" y="-172.95" font-family="Times New Roman,serif" font-size="14.00">Stop</text> </g> <!-- Return37 --> -<g id="node21" class="node"> +<g id="node22" class="node"> <title>Return37</title> -<polygon fill="yellow" stroke="black" points="142,-368 76,-368 76,-332 142,-332 142,-368"/> -<text text-anchor="middle" x="109" y="-346.3" font-family="Times,serif" font-size="14.00">Return</text> +<polygon fill="yellow" stroke="black" points="260,-283.5 206,-283.5 206,-247.5 260,-247.5 260,-283.5"/> +<text text-anchor="middle" x="233" y="-260.45" font-family="Times New Roman,serif" font-size="14.00">Return</text> </g> <!-- Stop3->Return37 --> <g id="edge1" class="edge"> <title>Stop3->Return37</title> -<path fill="none" stroke="red" d="M120.1,-282.4C118.21,-293.74 115.7,-308.77 113.55,-321.68"/> -<polygon fill="red" stroke="red" points="110.04,-321.47 111.85,-331.9 116.95,-322.62 110.04,-321.47"/> -<text text-anchor="middle" x="117.1" y="-285" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="red" d="M233,-196.3C233,-207.66 233,-222.78 233,-235.9"/> +<polygon fill="red" stroke="red" points="229.5,-235.71 233,-245.71 236.5,-235.71 229.5,-235.71"/> +<text text-anchor="middle" x="230.75" y="-198.45" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Con_4 --> -<g id="node3" class="node"> +<g id="node4" class="node"> <title>Con_4</title> -<ellipse fill="none" stroke="black" cx="231" cy="-738" rx="27" ry="18"/> -<text text-anchor="middle" x="231" y="-734.3" font-family="Times,serif" font-size="14.00">#0</text> +<ellipse fill="none" stroke="black" cx="239" cy="-744" rx="27" ry="18"/> +<text text-anchor="middle" x="239" y="-738.95" font-family="Times New Roman,serif" font-size="14.00">#0</text> </g> <!-- new9 --> -<g id="node4" class="node"> +<g id="node5" class="node"> <title>new9</title> -<ellipse fill="none" stroke="black" cx="127" cy="-738" rx="38.99" ry="18"/> -<text text-anchor="middle" x="127" y="-734.3" font-family="Times,serif" font-size="14.00">new S</text> +<ellipse fill="none" stroke="black" cx="140" cy="-744" rx="33.95" ry="18"/> +<text text-anchor="middle" x="140" y="-738.95" font-family="Times New Roman,serif" font-size="14.00">new S</text> </g> <!-- new9->Start2 --> <g id="edge2" class="edge"> <title>new9->Start2:p0</title> -<path fill="none" stroke="green" d="M128.38,-756.06C130.74,-774.19 137.07,-801.28 154.09,-808.93"/> -<polygon fill="green" stroke="green" points="153.5,-812.38 164,-811 154.93,-805.53 153.5,-812.38"/> -<text text-anchor="middle" x="125.38" y="-758.66" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="green" d="M161.46,-758.23C173.32,-767.03 186.56,-779.76 191.11,-795.03"/> +<polygon fill="green" stroke="green" points="187.59,-795.13 192.53,-804.5 194.51,-794.1 187.59,-795.13"/> +<text text-anchor="middle" x="163.71" y="-760.38" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Store10 --> -<g id="node5" class="node"> +<g id="node6" class="node"> <title>Store10</title> -<ellipse fill="none" stroke="black" cx="157" cy="-666" rx="27.9" ry="18"/> -<text text-anchor="middle" x="157" y="-662.3" font-family="Times,serif" font-size="14.00">.f =</text> +<ellipse fill="none" stroke="black" cx="166" cy="-672" rx="27" ry="18"/> +<text text-anchor="middle" x="166" y="-666.95" font-family="Times New Roman,serif" font-size="14.00">.f =</text> </g> <!-- Store10->Start2 --> <g id="edge3" class="edge"> <title>Store10->Start2:p0</title> -<path fill="none" stroke="red" d="M163.73,-683.93C167.57,-694.21 172.17,-707.69 175,-720 182.21,-751.38 184.54,-762.02 184.93,-789.85"/> -<polygon fill="red" stroke="red" points="181.44,-790.02 185,-800 188.44,-789.98 181.44,-790.02"/> -<text text-anchor="middle" x="160.73" y="-686.53" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="red" d="M172.35,-689.96C175.97,-700.25 180.31,-713.73 183,-726 189.8,-757.02 192.21,-767.77 192.66,-794.78"/> +<polygon fill="red" stroke="red" points="189.16,-794.51 192.74,-804.49 196.16,-794.46 189.16,-794.51"/> +<text text-anchor="middle" x="170.1" y="-692.11" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Store10->Con_4 --> <g id="edge6" class="edge"> <title>Store10->Con_4</title> -<path fill="none" stroke="black" d="M172.33,-681.5C182.82,-691.42 196.89,-704.74 208.63,-715.84"/> -<polygon fill="black" stroke="black" points="206.27,-718.42 215.94,-722.75 211.08,-713.34 206.27,-718.42"/> -<text text-anchor="middle" x="169.33" y="-684.1" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> +<path fill="none" stroke="black" d="M180.77,-687.17C190.8,-696.78 204.26,-709.69 215.72,-720.68"/> +<polygon fill="black" stroke="black" points="213.27,-723.17 222.91,-727.57 218.11,-718.12 213.27,-723.17"/> +<text text-anchor="middle" x="178.52" y="-689.32" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> </g> <!-- Store10->new9 --> <g id="edge5" class="edge"> <title>Store10->new9</title> -<path fill="none" stroke="black" d="M149.89,-683.59C146.43,-691.66 142.18,-701.57 138.29,-710.65"/> -<polygon fill="black" stroke="black" points="134.99,-709.47 134.27,-720.04 141.42,-712.23 134.99,-709.47"/> -<text text-anchor="middle" x="146.89" y="-686.19" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="black" d="M159.71,-689.95C156.86,-697.6 153.43,-706.84 150.24,-715.44"/> +<polygon fill="black" stroke="black" points="146.96,-714.2 146.76,-724.79 153.53,-716.64 146.96,-714.2"/> +<text text-anchor="middle" x="157.46" y="-692.1" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Loop11 --> -<g id="node6" class="node"> +<g id="node7" class="node"> <title>Loop11</title> -<polygon fill="yellow" stroke="black" points="246,-612 192,-612 192,-576 246,-576 246,-612"/> -<text text-anchor="middle" x="219" y="-590.3" font-family="Times,serif" font-size="14.00">Loop</text> +<polygon fill="yellow" stroke="black" points="338,-618 284,-618 284,-582 338,-582 338,-618"/> +<text text-anchor="middle" x="311" y="-594.95" font-family="Times New Roman,serif" font-size="14.00">Loop</text> </g> <!-- Loop11->Start2 --> <g id="edge7" class="edge"> <title>Loop11->Start2:p0</title> -<path fill="none" stroke="red" d="M232.6,-612.1C255.23,-642.79 295.45,-708.44 267,-756 247.84,-788.03 196.49,-764.41 186.65,-789.87"/> -<polygon fill="red" stroke="red" points="183.15,-789.57 185,-800 190.06,-790.69 183.15,-789.57"/> -<text text-anchor="middle" x="229.6" y="-614.7" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="red" d="M311.62,-618.44C311.85,-650.26 308.1,-718.44 275,-762 252.66,-791.4 204.87,-770.45 194.68,-794.8"/> +<polygon fill="red" stroke="red" points="191.26,-794.06 193.01,-804.51 198.16,-795.25 191.26,-794.06"/> +<text text-anchor="middle" x="309.37" y="-620.59" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Region32 --> -<g id="node17" class="node"> +<g id="node18" class="node"> <title>Region32</title> -<polygon fill="yellow" stroke="black" points="266,-124 200,-124 200,-88 266,-88 266,-124"/> -<text text-anchor="middle" x="233" y="-102.3" font-family="Times,serif" font-size="14.00">Region</text> +<polygon fill="yellow" stroke="black" points="215.5,-124 160.5,-124 160.5,-88 215.5,-88 215.5,-124"/> +<text text-anchor="middle" x="188" y="-100.95" font-family="Times New Roman,serif" font-size="14.00">Region</text> </g> <!-- Loop11->Region32 --> <g id="edge8" class="edge"> <title>Loop11->Region32</title> -<path fill="none" stroke="red" d="M191.95,-583.41C149.79,-568.45 73.1,-541.12 72,-540 38.13,-505.5 37,-485.35 37,-437 37,-349 37,-349 37,-349 37,-292.45 45.93,-270.87 87,-232 121.01,-199.82 150.7,-226.76 186,-196 204.96,-179.48 217.45,-153.41 224.76,-133.74"/> -<polygon fill="red" stroke="red" points="228.13,-134.72 228.12,-124.13 221.52,-132.42 228.13,-134.72"/> -<text text-anchor="middle" x="188.95" y="-577.01" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="red" d="M317.97,-581.54C334.1,-538.52 370.49,-424.48 342,-335 324.98,-281.54 311.59,-268.53 269,-232 241.84,-208.71 217.32,-225.45 197,-196 184.99,-178.59 183.21,-154.52 184.18,-135.8"/> +<polygon fill="red" stroke="red" points="187.66,-136.16 185,-125.91 180.68,-135.58 187.66,-136.16"/> +<text text-anchor="middle" x="315.72" y="-573.94" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Phi_arg13 --> -<g id="node7" class="node"> +<g id="node8" class="node"> <title>Phi_arg13</title> -<ellipse fill="lightyellow" stroke="black" cx="321" cy="-594" rx="37.09" ry="18"/> -<text text-anchor="middle" x="321" y="-590.3" font-family="Times,serif" font-size="14.00">φ_arg</text> +<ellipse fill="lightyellow" stroke="black" cx="213" cy="-600" rx="32.93" ry="18"/> +<text text-anchor="middle" x="213" y="-594.95" font-family="Times New Roman,serif" font-size="14.00">φ_arg</text> </g> <!-- Phi_arg13->Start2 --> <g id="edge10" class="edge"> -<title>Phi_arg13->Start2:p1</title> -<path fill="none" stroke="black" d="M319.63,-612.41C316.47,-643.6 306.42,-710.01 276,-756 261.03,-778.64 230.66,-771.56 222.86,-789.7"/> -<polygon fill="black" stroke="black" points="219.33,-789.54 221,-800 226.22,-790.78 219.33,-789.54"/> -<text text-anchor="middle" x="316.63" y="-615.01" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<title>Phi_arg13->Start2:p2</title> +<path fill="none" stroke="black" d="M224.02,-617.05C239.03,-639.99 265.16,-684.02 275,-726 278.65,-741.58 277.45,-746.19 275,-762 272.54,-777.84 265.38,-783.26 262.36,-794.66"/> +<polygon fill="black" stroke="black" points="258.9,-794.15 261.18,-804.5 265.85,-794.99 258.9,-794.15"/> +<text text-anchor="middle" x="221.77" y="-619.2" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Phi_arg13->Loop11 --> <g id="edge9" class="edge"> <title>Phi_arg13->Loop11</title> -<path fill="none" stroke="black" stroke-dasharray="1,5" d="M283.84,-594C274.6,-594 265.36,-594 256.12,-594"/> -<polygon fill="black" stroke="black" points="256.09,-590.5 246.09,-594 256.09,-597.5 256.09,-590.5"/> -<text text-anchor="middle" x="280.84" y="-587.6" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="black" stroke-dasharray="1,5" d="M246.3,-600C254.97,-600 263.64,-600 272.31,-600"/> +<polygon fill="black" stroke="black" points="272.19,-603.5 282.19,-600 272.19,-596.5 272.19,-603.5"/> +<text text-anchor="middle" x="248.55" y="-592.4" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Sub35 --> -<g id="node19" class="node"> +<g id="node20" class="node"> <title>Sub35</title> -<ellipse fill="none" stroke="black" cx="285" cy="-34" rx="27" ry="18"/> -<text text-anchor="middle" x="285" y="-30.3" font-family="Times,serif" font-size="14.00">-</text> +<ellipse fill="none" stroke="black" cx="188" cy="-34" rx="27" ry="18"/> +<text text-anchor="middle" x="188" y="-28.95" font-family="Times New Roman,serif" font-size="14.00">-</text> </g> <!-- LT14 --> -<g id="node8" class="node"> +<g id="node9" class="node"> <title>LT14</title> -<ellipse fill="none" stroke="black" cx="265" cy="-522" rx="27" ry="18"/> -<text text-anchor="middle" x="265" y="-518.3" font-family="Times,serif" font-size="14.00"><</text> +<ellipse fill="none" stroke="black" cx="265" cy="-528" rx="27" ry="18"/> +<text text-anchor="middle" x="265" y="-522.95" font-family="Times New Roman,serif" font-size="14.00"><</text> </g> <!-- LT14->Con_4 --> <g id="edge13" class="edge"> <title>LT14->Con_4</title> -<path fill="none" stroke="black" d="M263.48,-540.11C261.78,-558.07 258.78,-587.1 255,-612 249.83,-646.03 242.07,-684.92 236.78,-710.16"/> -<polygon fill="black" stroke="black" points="233.33,-709.58 234.68,-720.09 240.17,-711.03 233.33,-709.58"/> -<text text-anchor="middle" x="260.48" y="-542.71" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="black" d="M263.12,-546.4C261.17,-564.35 257.99,-593.15 255,-618 251.03,-651.04 246.13,-688.95 242.8,-714.27"/> +<polygon fill="black" stroke="black" points="239.34,-713.75 241.51,-724.12 246.28,-714.66 239.34,-713.75"/> +<text text-anchor="middle" x="260.87" y="-548.55" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- LT14->Loop11 --> <g id="edge12" class="edge"> <title>LT14->Loop11</title> -<path fill="none" stroke="red" d="M254.56,-538.88C249.04,-547.28 242.13,-557.8 235.87,-567.33"/> -<polygon fill="red" stroke="red" points="232.93,-565.43 230.36,-575.71 238.78,-569.27 232.93,-565.43"/> -<text text-anchor="middle" x="251.56" y="-541.48" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="red" d="M275.44,-544.88C280.73,-552.94 287.31,-562.95 293.36,-572.16"/> +<polygon fill="red" stroke="red" points="290.39,-574.01 298.81,-580.44 296.24,-570.16 290.39,-574.01"/> +<text text-anchor="middle" x="277.69" y="-547.03" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- LT14->Phi_arg13 --> <g id="edge14" class="edge"> <title>LT14->Phi_arg13</title> -<path fill="none" stroke="black" d="M277.15,-538.19C284.36,-547.2 293.66,-558.82 301.84,-569.05"/> -<polygon fill="black" stroke="black" points="299.11,-571.24 308.09,-576.87 304.58,-566.87 299.11,-571.24"/> -<text text-anchor="middle" x="274.15" y="-540.79" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> -</g> -<!-- If16 --> -<g id="node9" class="node"> -<title>If16</title> -<polygon fill="yellow" stroke="transparent" points="144,-439 144,-464 238,-464 238,-439 144,-439"/> -<polygon fill="none" stroke="black" points="144,-439 144,-464 238,-464 238,-439 144,-439"/> -<text text-anchor="start" x="185.5" y="-447.8" font-family="Times,serif" font-size="14.00">If</text> -<polygon fill="none" stroke="black" points="144,-408 144,-439 238,-439 238,-408 144,-408"/> -<polygon fill="yellow" stroke="transparent" points="149,-413 149,-434 189,-434 189,-413 149,-413"/> -<polygon fill="none" stroke="black" points="149,-413 149,-434 189,-434 189,-413 149,-413"/> -<text text-anchor="start" x="152" y="-419.8" font-family="Times,serif" font-size="14.00">True</text> -<polygon fill="yellow" stroke="transparent" points="189,-413 189,-434 233,-434 233,-413 189,-413"/> -<polygon fill="none" stroke="black" points="189,-413 189,-434 233,-434 233,-413 189,-413"/> -<text text-anchor="start" x="192" y="-419.8" font-family="Times,serif" font-size="14.00">False</text> +<path fill="none" stroke="black" d="M253.46,-544.54C247.02,-553.2 238.85,-564.21 231.53,-574.05"/> +<polygon fill="black" stroke="black" points="228.92,-571.69 225.77,-581.81 234.54,-575.87 228.92,-571.69"/> +<text text-anchor="middle" x="255.71" y="-546.69" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- If16->Loop11 --> <g id="edge15" class="edge"> <title>If16->Loop11</title> -<path fill="none" stroke="red" d="M196.6,-468.19C201.73,-496.77 209.24,-538.64 214.13,-565.86"/> -<polygon fill="red" stroke="red" points="210.71,-566.63 215.92,-575.86 217.6,-565.4 210.71,-566.63"/> -<text text-anchor="middle" x="193.6" y="-470.79" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="red" d="M288.71,-473.68C293.31,-484.96 297.98,-497.84 301,-510 305.94,-529.86 308.44,-552.83 309.71,-570.45"/> +<polygon fill="red" stroke="red" points="306.2,-570.42 310.31,-580.19 313.19,-569.99 306.2,-570.42"/> +<text text-anchor="middle" x="286.46" y="-475.83" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- If16->LT14 --> <g id="edge16" class="edge"> <title>If16->LT14</title> -<path fill="none" stroke="black" d="M218.55,-468.28C227.33,-478.24 236.86,-489.05 244.99,-498.29"/> -<polygon fill="black" stroke="black" points="242.49,-500.74 251.72,-505.93 247.74,-496.11 242.49,-500.74"/> -<text text-anchor="middle" x="215.55" y="-470.88" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="black" d="M270.6,-473.82C269.74,-481.99 268.82,-490.69 267.98,-498.65"/> +<polygon fill="black" stroke="black" points="264.52,-498.15 266.95,-508.47 271.48,-498.89 264.52,-498.15"/> +<text text-anchor="middle" x="268.35" y="-475.97" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Div21 --> -<g id="node10" class="node"> +<g id="node11" class="node"> <title>Div21</title> -<ellipse fill="none" stroke="black" cx="43" cy="-738" rx="27" ry="18"/> -<text text-anchor="middle" x="43" y="-734.3" font-family="Times,serif" font-size="14.00">//</text> +<ellipse fill="none" stroke="black" cx="61" cy="-744" rx="27" ry="18"/> +<text text-anchor="middle" x="61" y="-738.95" font-family="Times New Roman,serif" font-size="14.00">//</text> </g> <!-- Div21->Start2 --> <g id="edge17" class="edge"> <title>Div21->Start2:p0</title> -<path fill="none" stroke="red" d="M56.68,-753.92C76,-773.4 113.45,-805.6 153.93,-810.4"/> -<polygon fill="red" stroke="red" points="153.81,-813.89 164,-811 154.23,-806.91 153.81,-813.89"/> -<text text-anchor="middle" x="53.68" y="-756.52" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="red" d="M73.85,-760.18C91.84,-779.79 126.72,-812.07 165.38,-817.47"/> +<polygon fill="red" stroke="red" points="164.64,-820.93 174.87,-818.14 165.14,-813.95 164.64,-820.93"/> +<text text-anchor="middle" x="71.6" y="-762.33" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Div21->Start2 --> <g id="edge18" class="edge"> -<title>Div21->Start2:p1</title> -<path fill="none" stroke="black" d="M64.1,-749.57C68.94,-751.82 74.1,-754.1 79,-756 137.09,-778.59 211.95,-738.92 220.24,-789.82"/> -<polygon fill="black" stroke="black" points="216.77,-790.29 221,-800 223.75,-789.77 216.77,-790.29"/> -<text text-anchor="middle" x="61.1" y="-752.17" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<title>Div21->Start2:p2</title> +<path fill="none" stroke="black" d="M82.05,-755.71C86.89,-757.96 92.07,-760.19 97,-762 163.97,-786.57 250.88,-736.57 260.18,-794.78"/> +<polygon fill="black" stroke="black" points="256.67,-794.77 260.89,-804.49 263.65,-794.26 256.67,-794.77"/> +<text text-anchor="middle" x="84.3" y="-757.86" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Con_22 --> -<g id="node11" class="node"> +<g id="node12" class="node"> <title>Con_22</title> -<ellipse fill="none" stroke="black" cx="43" cy="-824" rx="27" ry="18"/> -<text text-anchor="middle" x="43" y="-820.3" font-family="Times,serif" font-size="14.00">#3</text> +<ellipse fill="none" stroke="black" cx="61" cy="-831.5" rx="27" ry="18"/> +<text text-anchor="middle" x="61" y="-826.45" font-family="Times New Roman,serif" font-size="14.00">#3</text> </g> <!-- Div21->Con_22 --> <g id="edge19" class="edge"> <title>Div21->Con_22</title> -<path fill="none" stroke="black" d="M43,-756.4C43,-767.74 43,-782.77 43,-795.68"/> -<polygon fill="black" stroke="black" points="39.5,-795.9 43,-805.9 46.5,-795.9 39.5,-795.9"/> -<text text-anchor="middle" x="40" y="-759" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="black" d="M61,-762.3C61,-773.66 61,-788.78 61,-801.9"/> +<polygon fill="black" stroke="black" points="57.5,-801.71 61,-811.71 64.5,-801.71 57.5,-801.71"/> +<text text-anchor="middle" x="58.75" y="-764.45" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- EQ23 --> -<g id="node12" class="node"> +<g id="node13" class="node"> <title>EQ23</title> -<ellipse fill="none" stroke="black" cx="284" cy="-350" rx="27" ry="18"/> -<text text-anchor="middle" x="284" y="-346.3" font-family="Times,serif" font-size="14.00">==</text> +<ellipse fill="none" stroke="black" cx="165" cy="-353" rx="27" ry="18"/> +<text text-anchor="middle" x="165" y="-347.95" font-family="Times New Roman,serif" font-size="14.00">==</text> </g> <!-- EQ23->Phi_arg13 --> <g id="edge21" class="edge"> <title>EQ23->Phi_arg13</title> -<path fill="none" stroke="black" d="M299.94,-365.01C309.81,-374.8 321.61,-388.81 327,-404 346.42,-458.77 336.12,-528.17 327.91,-565.78"/> -<polygon fill="black" stroke="black" points="324.4,-565.41 325.57,-575.94 331.22,-566.98 324.4,-565.41"/> -<text text-anchor="middle" x="296.94" y="-367.61" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="black" d="M169.9,-371.1C172.77,-381.44 176.34,-394.92 179,-407 191.71,-464.72 202.9,-533.02 208.73,-570.62"/> +<polygon fill="black" stroke="black" points="205.22,-570.85 210.2,-580.2 212.14,-569.78 205.22,-570.85"/> +<text text-anchor="middle" x="167.65" y="-373.25" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- EQ23->If16 --> <g id="edge20" class="edge"> <title>EQ23->If16:p0</title> -<path fill="none" stroke="red" d="M259.89,-358.47C231.44,-367.9 186.29,-385 172.88,-402.37"/> -<polygon fill="red" stroke="red" points="169.49,-401.42 169,-412 175.98,-404.03 169.49,-401.42"/> -<text text-anchor="middle" x="256.89" y="-352.07" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="red" d="M178.1,-368.82C192.8,-385.21 216.42,-410.75 230.4,-421.83"/> +<polygon fill="red" stroke="red" points="228.25,-424.63 238.68,-426.51 231.7,-418.54 228.25,-424.63"/> +<text text-anchor="middle" x="180.35" y="-370.97" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Con_24 --> -<g id="node13" class="node"> +<g id="node14" class="node"> <title>Con_24</title> -<ellipse fill="none" stroke="black" cx="291" cy="-436" rx="27" ry="18"/> -<text text-anchor="middle" x="291" y="-432.3" font-family="Times,serif" font-size="14.00">#5</text> +<ellipse fill="none" stroke="black" cx="143" cy="-440.5" rx="27" ry="18"/> +<text text-anchor="middle" x="143" y="-435.45" font-family="Times New Roman,serif" font-size="14.00">#5</text> </g> <!-- EQ23->Con_24 --> <g id="edge22" class="edge"> <title>EQ23->Con_24</title> -<path fill="none" stroke="black" d="M285.42,-368C286.36,-379.31 287.62,-394.42 288.7,-407.43"/> -<polygon fill="black" stroke="black" points="285.24,-408.06 289.56,-417.74 292.22,-407.48 285.24,-408.06"/> -<text text-anchor="middle" x="282.42" y="-370.6" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="black" d="M160.65,-370.89C157.69,-382.41 153.69,-397.94 150.25,-411.31"/> +<polygon fill="black" stroke="black" points="146.89,-410.33 147.79,-420.88 153.67,-412.07 146.89,-410.33"/> +<text text-anchor="middle" x="158.4" y="-373.04" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- If25 --> -<g id="node14" class="node"> +<g id="node15" class="node"> <title>If25</title> -<polygon fill="yellow" stroke="transparent" points="208,-267 208,-292 302,-292 302,-267 208,-267"/> -<polygon fill="none" stroke="black" points="208,-267 208,-292 302,-292 302,-267 208,-267"/> -<text text-anchor="start" x="249.5" y="-275.8" font-family="Times,serif" font-size="14.00">If</text> -<polygon fill="none" stroke="black" points="208,-236 208,-267 302,-267 302,-236 208,-236"/> -<polygon fill="yellow" stroke="transparent" points="213,-241 213,-262 253,-262 253,-241 213,-241"/> -<polygon fill="none" stroke="black" points="213,-241 213,-262 253,-262 253,-241 213,-241"/> -<text text-anchor="start" x="216" y="-247.8" font-family="Times,serif" font-size="14.00">True</text> -<polygon fill="yellow" stroke="transparent" points="253,-241 253,-262 297,-262 297,-241 253,-241"/> -<polygon fill="none" stroke="black" points="253,-241 253,-262 297,-262 297,-241 253,-241"/> -<text text-anchor="start" x="256" y="-247.8" font-family="Times,serif" font-size="14.00">False</text> +<polygon fill="yellow" stroke="none" points="104,-268.5 104,-295 180,-295 180,-268.5 104,-268.5"/> +<polygon fill="none" stroke="black" points="104,-268.5 104,-295 180,-295 180,-268.5 104,-268.5"/> +<text text-anchor="start" x="137.5" y="-276.7" font-family="Times New Roman,serif" font-size="14.00">If</text> +<polygon fill="none" stroke="black" points="104,-236 104,-268.5 180,-268.5 180,-236 104,-236"/> +<polygon fill="yellow" stroke="none" points="109,-241 109,-263.5 140.5,-263.5 140.5,-241 109,-241"/> +<polygon fill="none" stroke="black" points="109,-241 109,-263.5 140.5,-263.5 140.5,-241 109,-241"/> +<text text-anchor="start" x="112" y="-247.2" font-family="Times New Roman,serif" font-size="14.00">True</text> +<polygon fill="yellow" stroke="none" points="140.5,-241 140.5,-263.5 175,-263.5 175,-241 140.5,-241"/> +<polygon fill="none" stroke="black" points="140.5,-241 140.5,-263.5 175,-263.5 175,-241 140.5,-241"/> +<text text-anchor="start" x="143.5" y="-247.2" font-family="Times New Roman,serif" font-size="14.00">False</text> </g> <!-- If25->If16 --> <g id="edge23" class="edge"> <title>If25->If16:p0</title> -<path fill="none" stroke="red" d="M234.4,-296.29C213.03,-329.21 181.43,-379.35 171.85,-402.21"/> -<polygon fill="red" stroke="red" points="168.44,-401.42 169,-412 175.16,-403.38 168.44,-401.42"/> -<text text-anchor="middle" x="231.4" y="-298.89" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="red" d="M170.91,-298.68C180.72,-309.96 191.57,-322.85 201,-335 203.66,-338.43 237.81,-384.19 251.31,-405.07"/> +<polygon fill="red" stroke="red" points="248.15,-406.58 256.02,-413.67 254.29,-403.22 248.15,-406.58"/> +<text text-anchor="middle" x="168.66" y="-300.83" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- If25->EQ23 --> <g id="edge24" class="edge"> <title>If25->EQ23</title> -<path fill="none" stroke="black" d="M265.8,-296.28C268.78,-304.9 271.97,-314.17 274.85,-322.49"/> -<polygon fill="black" stroke="black" points="271.58,-323.76 278.16,-332.07 278.2,-321.48 271.58,-323.76"/> -<text text-anchor="middle" x="262.8" y="-298.88" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> -</g> -<!-- Phi_230 --> -<g id="node15" class="node"> -<title>Phi_230</title> -<ellipse fill="lightyellow" stroke="black" cx="129" cy="-594" rx="33.29" ry="18"/> -<text text-anchor="middle" x="129" y="-590.3" font-family="Times,serif" font-size="14.00">φ_$2</text> +<path fill="none" stroke="black" d="M150.7,-298.82C152.92,-307.08 155.29,-315.9 157.45,-323.93"/> +<polygon fill="black" stroke="black" points="154.05,-324.76 160.03,-333.51 160.81,-322.94 154.05,-324.76"/> +<text text-anchor="middle" x="148.45" y="-300.97" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Phi_230->Store10 --> <g id="edge26" class="edge"> <title>Phi_230->Store10</title> -<path fill="none" stroke="blue" d="M135.78,-611.95C139.01,-620.03 142.95,-629.88 146.56,-638.89"/> -<polygon fill="blue" stroke="blue" points="143.32,-640.23 150.28,-648.21 149.82,-637.63 143.32,-640.23"/> -<text text-anchor="middle" x="132.78" y="-614.55" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="blue" d="M122.51,-617.59C129.69,-626.41 138.66,-637.42 146.57,-647.14"/> +<polygon fill="blue" stroke="blue" points="143.77,-649.24 152.8,-654.79 149.2,-644.82 143.77,-649.24"/> +<text text-anchor="middle" x="120.26" y="-619.74" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Phi_230->Loop11 --> <g id="edge25" class="edge"> <title>Phi_230->Loop11</title> -<path fill="none" stroke="black" stroke-dasharray="1,5" d="M162.4,-594C168.78,-594 175.17,-594 181.55,-594"/> -<polygon fill="black" stroke="black" points="181.58,-597.5 191.58,-594 181.58,-590.5 181.58,-597.5"/> -<text text-anchor="middle" x="165.4" y="-587.6" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="black" stroke-dasharray="1,5" d="M133.84,-583.77C146.98,-576.14 163.74,-567.89 180,-564 215.01,-555.63 226.74,-552.95 261,-564 268.52,-566.43 275.87,-570.45 282.52,-574.95"/> +<polygon fill="black" stroke="black" points="280.35,-577.71 290.47,-580.85 284.52,-572.08 280.35,-577.71"/> +<text text-anchor="middle" x="131.59" y="-576.17" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Phi_233 --> -<g id="node18" class="node"> +<g id="node19" class="node"> <title>Phi_233</title> -<ellipse fill="lightyellow" stroke="black" cx="149" cy="-106" rx="33.29" ry="18"/> -<text text-anchor="middle" x="149" y="-102.3" font-family="Times,serif" font-size="14.00">φ_$2</text> +<ellipse fill="lightyellow" stroke="black" cx="69" cy="-106" rx="52.89" ry="18"/> +<text text-anchor="middle" x="69" y="-100.95" font-family="Times New Roman,serif" font-size="14.00">MemPhi_2</text> </g> <!-- Store31 --> -<g id="node16" class="node"> +<g id="node17" class="node"> <title>Store31</title> -<ellipse fill="none" stroke="black" cx="149" cy="-178" rx="27.9" ry="18"/> -<text text-anchor="middle" x="149" y="-174.3" font-family="Times,serif" font-size="14.00">.f =</text> +<ellipse fill="none" stroke="black" cx="58" cy="-178" rx="27" ry="18"/> +<text text-anchor="middle" x="58" y="-172.95" font-family="Times New Roman,serif" font-size="14.00">.f =</text> </g> <!-- Store31->new9 --> <g id="edge30" class="edge"> <title>Store31->new9</title> -<path fill="none" stroke="black" d="M125.79,-188.29C107.7,-196.82 83.59,-211.3 71,-232 43.78,-276.74 57,-296.63 57,-349 57,-349 57,-349 57,-437 57,-506.9 72.18,-523.27 77,-593"/> -<path fill="none" stroke="black" d="M77,-595C78.62,-637.52 98.4,-683.39 112.59,-711.15"/> -<polygon fill="black" stroke="black" points="109.59,-712.96 117.34,-720.18 115.78,-709.7 109.59,-712.96"/> -<text text-anchor="middle" x="122.79" y="-190.89" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="black" d="M57,-354C19.45,-421.68 37,-449.6 37,-527 37,-527 37,-527 37,-601 37,-651.78 80.7,-696.66 111.17,-721.77"/> +<polygon fill="black" stroke="black" points="108.95,-724.48 118.96,-727.98 113.32,-719.01 108.95,-724.48"/> +<path fill="none" stroke="black" d="M57,-266.5C57.21,-285.5 66.47,-335.53 57,-352"/> +<path fill="none" stroke="black" d="M57.56,-196.36C57.19,-213.63 56.74,-240.9 57,-264.5"/> +<text text-anchor="middle" x="54.75" y="-346.4" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Store31->Div21 --> <g id="edge31" class="edge"> <title>Store31->Div21</title> -<path fill="none" stroke="black" d="M122.62,-184.31C96.9,-190.92 58.72,-204.83 38,-232 5.97,-274.01 17,-296.17 17,-349 17,-349 17,-349 17,-595 17,-635.67 27.45,-681.83 35.1,-710.19"/> -<polygon fill="black" stroke="black" points="31.81,-711.44 37.86,-720.13 38.55,-709.56 31.81,-711.44"/> -<text text-anchor="middle" x="119.62" y="-177.91" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> +<path fill="none" stroke="black" d="M51.23,-195.87C39.58,-226.46 17,-293.35 17,-352 17,-352 17,-352 17,-601 17,-641.11 18.86,-652.1 32,-690 35.19,-699.2 39.88,-708.69 44.56,-717.06"/> +<polygon fill="black" stroke="black" points="41.52,-718.8 49.61,-725.64 47.56,-715.25 41.52,-718.8"/> +<text text-anchor="middle" x="48.98" y="-198.02" font-family="Helvetica,sans-Serif" font-size="8.00">3</text> </g> <!-- Store31->If25 --> <g id="edge28" class="edge"> <title>Store31->If25:p0</title> -<path fill="none" stroke="red" d="M160.65,-194.62C172.83,-210.67 191.77,-234.77 203.47,-245.51"/> -<polygon fill="red" stroke="red" points="201.7,-248.53 212,-251 205.49,-242.64 201.7,-248.53"/> -<text text-anchor="middle" x="157.65" y="-197.22" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="red" d="M61.66,-195.99C66.57,-214.42 77.07,-242.26 96.81,-250.12"/> +<polygon fill="red" stroke="red" points="96.03,-253.53 106.51,-251.97 97.35,-246.66 96.03,-253.53"/> +<text text-anchor="middle" x="59.41" y="-198.14" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Store31->Phi_230 --> <g id="edge29" class="edge"> <title>Store31->Phi_230</title> -<path fill="none" stroke="blue" d="M153.06,-196.07C155.31,-206.4 157.87,-219.88 159,-232 161.65,-260.32 160.57,-267.6 159,-296 157.22,-328.15 162.02,-337.75 151,-368 144.42,-386.07 132.57,-385.6 127,-404 118.76,-431.22 126.77,-439.56 127,-468 127.27,-501.46 127.94,-539.96 128.43,-565.29"/> -<polygon fill="blue" stroke="blue" points="124.94,-565.68 128.63,-575.61 131.94,-565.54 124.94,-565.68"/> -<text text-anchor="middle" x="150.06" y="-198.67" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="blue" d="M57,-529C56.5,-546.83 67.47,-563.07 79.46,-575.42"/> +<polygon fill="blue" stroke="blue" points="77.03,-577.93 86.7,-582.27 81.85,-572.85 77.03,-577.93"/> +<path fill="none" stroke="blue" d="M57,-266.5C69.32,-381.62 60.25,-411.27 57,-527"/> +<text text-anchor="middle" x="54.75" y="-521.4" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Region32->If25 --> <g id="edge32" class="edge"> <title>Region32->If25:p0</title> -<path fill="none" stroke="red" d="M233,-124.22C233,-147.49 233,-190.58 233,-229.64"/> -<polygon fill="red" stroke="red" points="229.5,-230 233,-240 236.5,-230 229.5,-230"/> -<text text-anchor="middle" x="230" y="-126.82" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="red" d="M173.65,-124.25C156.55,-146.45 129.75,-187.13 125.37,-228.59"/> +<polygon fill="red" stroke="red" points="121.87,-228.31 124.83,-238.49 128.86,-228.69 121.87,-228.31"/> +<text text-anchor="middle" x="171.4" y="-126.4" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +</g> +<!-- Region32->If25 --> +<g id="edge33" class="edge"> +<title>Region32->If25:p1</title> +<path fill="none" stroke="red" d="M181.44,-124.35C173.32,-147.27 160.3,-189.33 158.08,-228.71"/> +<polygon fill="red" stroke="red" points="154.59,-228.39 157.79,-238.49 161.58,-228.59 154.59,-228.39"/> +<text text-anchor="middle" x="179.19" y="-126.5" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Phi_233->Phi_230 --> <g id="edge36" class="edge"> <title>Phi_233->Phi_230</title> -<path fill="none" stroke="blue" d="M170.5,-129.96C185.43,-148.79 200.3,-175.58 186,-196 159.14,-234.35 113.86,-193.65 87,-232 70.68,-255.3 72.64,-271.45 87,-296 103.48,-324.17 134.52,-303.83 151,-332 159.08,-345.81 156.69,-353.04 151,-368 143.39,-388.01 126.61,-383.99 119,-404 98.19,-458.74 111.2,-528.88 121.03,-566.43"/> -<polygon fill="blue" stroke="blue" points="173.07,-127.58 163.96,-122.17 167.71,-132.08 173.07,-127.58"/> -<polygon fill="blue" stroke="blue" points="117.67,-567.4 123.69,-576.12 124.42,-565.55 117.67,-567.4"/> -<text text-anchor="middle" x="160.96" y="-124.77" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="blue" d="M84.51,-133.96C88.4,-142.07 92.02,-151.16 94,-160 97.5,-175.61 98.33,-180.6 94,-196 88.92,-214.05 77.08,-213.95 72,-232 55.48,-290.7 59.98,-384.37 92,-546 93.66,-554.38 96.2,-563.28 98.81,-571.36"/> +<polygon fill="blue" stroke="blue" points="87.75,-132.6 80.06,-125.32 81.53,-135.81 87.75,-132.6"/> +<polygon fill="blue" stroke="blue" points="95.44,-572.31 101.98,-580.64 102.06,-570.05 95.44,-572.31"/> +<text text-anchor="middle" x="77.12" y="-126.12" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Phi_233->Store31 --> <g id="edge35" class="edge"> <title>Phi_233->Store31</title> -<path fill="none" stroke="blue" d="M149,-124.3C149,-132.02 149,-141.29 149,-149.89"/> -<polygon fill="blue" stroke="blue" points="145.5,-149.9 149,-159.9 152.5,-149.9 145.5,-149.9"/> -<text text-anchor="middle" x="146" y="-126.9" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="blue" d="M66.28,-124.3C65.12,-131.68 63.74,-140.48 62.44,-148.75"/> +<polygon fill="blue" stroke="blue" points="59.02,-147.98 60.92,-158.4 65.93,-149.07 59.02,-147.98"/> +<text text-anchor="middle" x="64.03" y="-126.45" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Phi_233->Region32 --> <g id="edge34" class="edge"> <title>Phi_233->Region32</title> -<path fill="none" stroke="black" stroke-dasharray="1,5" d="M182.47,-106C184.91,-106 187.35,-106 189.8,-106"/> -<polygon fill="black" stroke="black" points="189.84,-109.5 199.84,-106 189.84,-102.5 189.84,-109.5"/> -<text text-anchor="middle" x="185.47" y="-99.6" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="black" stroke-dasharray="1,5" d="M121.99,-106C130.94,-106 139.88,-106 148.82,-106"/> +<polygon fill="black" stroke="black" points="148.64,-109.5 158.64,-106 148.64,-102.5 148.64,-109.5"/> +<text text-anchor="middle" x="124.24" y="-98.4" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Sub35->Phi_arg13 --> <g id="edge38" class="edge"> <title>Sub35->Phi_arg13</title> -<path fill="none" stroke="black" d="M287.31,-62.31C288.99,-79.97 291.57,-103.42 295,-124 303.09,-172.5 311.09,-183.47 319,-232 331.36,-307.86 331.84,-327.31 337,-404 339.98,-448.36 339.33,-459.54 340,-504 340.24,-520 342.74,-524.24 340,-540 338.44,-549 335.48,-558.49 332.36,-566.91"/> -<polygon fill="black" stroke="black" points="290.77,-61.78 286.38,-52.14 283.8,-62.41 290.77,-61.78"/> -<polygon fill="black" stroke="black" points="329.1,-565.63 328.7,-576.22 335.62,-568.2 329.1,-565.63"/> -<text text-anchor="middle" x="283.38" y="-54.74" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="black" d="M170.02,-59.56C163.96,-68.33 157.37,-78.41 152,-88 143.43,-103.32 90.69,-214.84 87,-232 73.48,-294.79 101.52,-461.34 107,-474 125.1,-515.81 162.03,-554.22 187.15,-577.13"/> +<polygon fill="black" stroke="black" points="172.77,-61.74 175.68,-51.55 167.05,-57.7 172.77,-61.74"/> +<polygon fill="black" stroke="black" points="184.57,-579.51 194.36,-583.56 189.22,-574.29 184.57,-579.51"/> +<text text-anchor="middle" x="174.31" y="-52.47" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Sub35->Region32 --> <g id="edge37" class="edge"> <title>Sub35->Region32</title> -<path fill="none" stroke="red" d="M273.46,-50.54C267.16,-59.01 259.21,-69.71 252.03,-79.39"/> -<polygon fill="red" stroke="red" points="249.12,-77.42 245.97,-87.54 254.74,-81.6 249.12,-77.42"/> -<text text-anchor="middle" x="270.46" y="-53.14" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="red" d="M188,-52.3C188,-59.59 188,-68.27 188,-76.46"/> +<polygon fill="red" stroke="red" points="184.5,-76.38 188,-86.38 191.5,-76.38 184.5,-76.38"/> +<text text-anchor="middle" x="185.75" y="-54.45" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Con_36 --> -<g id="node20" class="node"> +<g id="node21" class="node"> <title>Con_36</title> -<ellipse fill="none" stroke="black" cx="331" cy="-106" rx="27" ry="18"/> -<text text-anchor="middle" x="331" y="-102.3" font-family="Times,serif" font-size="14.00">#1</text> +<ellipse fill="none" stroke="black" cx="261" cy="-106" rx="27" ry="18"/> +<text text-anchor="middle" x="261" y="-100.95" font-family="Times New Roman,serif" font-size="14.00">#1</text> </g> <!-- Sub35->Con_36 --> <g id="edge39" class="edge"> <title>Sub35->Con_36</title> -<path fill="none" stroke="black" d="M295.44,-50.88C301.2,-59.66 308.48,-70.74 314.96,-80.58"/> -<polygon fill="black" stroke="black" points="312.19,-82.74 320.6,-89.18 318.04,-78.9 312.19,-82.74"/> -<text text-anchor="middle" x="292.44" y="-53.48" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="black" d="M202.77,-49.17C212.8,-58.78 226.26,-71.69 237.72,-82.68"/> +<polygon fill="black" stroke="black" points="235.27,-85.17 244.91,-89.57 240.11,-80.12 235.27,-85.17"/> +<text text-anchor="middle" x="200.52" y="-51.32" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +</g> +<!-- Return37->MemMerge --> +<g id="edge42" class="edge"> +<title>Return37->MemMerge</title> +<path fill="none" stroke="blue" d="M244.94,-283.8C253.18,-295.74 264.29,-311.84 273.64,-325.4"/> +<polygon fill="blue" stroke="blue" points="270.66,-327.24 279.22,-333.48 276.42,-323.26 270.66,-327.24"/> +<text text-anchor="middle" x="242.69" y="-285.95" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Return37->new9 --> <g id="edge41" class="edge"> <title>Return37->new9</title> -<path fill="none" stroke="black" d="M101.56,-368.35C89.32,-399.07 67.21,-465.09 77,-521"/> -<path fill="none" stroke="black" d="M77,-523C81.47,-553.79 74.85,-561.96 77,-593"/> -<text text-anchor="middle" x="98.56" y="-370.95" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="black" d="M216.48,-283.78C210.69,-289.16 203.9,-294.79 197,-299 140.21,-333.67 89.27,-293.82 57,-352"/> +<text text-anchor="middle" x="214.23" y="-285.93" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Return37->If16 --> <g id="edge40" class="edge"> <title>Return37->If16:p1</title> -<path fill="none" stroke="red" d="M142.12,-356.57C168.64,-362.93 202.56,-375.99 209.66,-401.87"/> -<polygon fill="red" stroke="red" points="206.22,-402.55 211,-412 213.16,-401.63 206.22,-402.55"/> -<text text-anchor="middle" x="145.12" y="-350.17" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> -</g> -<!-- Return37->Phi_230 --> -<g id="edge42" class="edge"> -<title>Return37->Phi_230</title> -<path fill="none" stroke="blue" d="M77,-523C80.24,-541.52 92.58,-558.55 104.43,-571.18"/> -<polygon fill="blue" stroke="blue" points="102.12,-573.82 111.65,-578.44 107.08,-568.88 102.12,-573.82"/> -<text text-anchor="middle" x="74" y="-516.6" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="red" d="M230.82,-283.79C228.88,-305.37 228.21,-342.88 242,-371 252.72,-392.86 279.35,-388.61 287.4,-404.08"/> +<polygon fill="red" stroke="red" points="283.91,-404.48 289.43,-413.52 290.75,-403.01 283.91,-404.48"/> +<text text-anchor="middle" x="228.57" y="-285.94" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> </g> </svg> diff --git a/chapter11/docs/graph8.gv b/chapter11/docs/graph8.gv index 1a7b231d..431cef46 100644 --- a/chapter11/docs/graph8.gv +++ b/chapter11/docs/graph8.gv @@ -39,7 +39,7 @@ return 0; Never18 -> Con_4[taillabel=1]; Stop3 -> Return21[taillabel=0 color=red]; Return21 -> Never18:p0[taillabel=0 color=red]; - Return21 -> Con_4[taillabel=1]; + Return21 -> Con_4[taillabel=2]; Loop8 -> Start2:p0[taillabel=1 color=red]; Loop8 -> Never18:p1[taillabel=2 color=red constraint=false]; } diff --git a/chapter11/docs/graph8.svg b/chapter11/docs/graph8.svg index c577cf84..a89eeeb5 100644 --- a/chapter11/docs/graph8.svg +++ b/chapter11/docs/graph8.svg @@ -1,115 +1,115 @@ <?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) +<!-- Generated by graphviz version 12.2.0 (20241103.1931) --> <!-- Title: chapter11 Pages: 1 --> -<svg width="170pt" height="420pt" - viewBox="0.00 0.00 170.00 420.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 416)"> +<svg width="166pt" height="426pt" + viewBox="0.00 0.00 166.00 426.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 422)"> <title>chapter11</title> -<polygon fill="white" stroke="transparent" points="-4,4 -4,-416 166,-416 166,4 -4,4"/> +<polygon fill="white" stroke="none" points="-4,4 -4,-422 162,-422 162,4 -4,4"/> <g id="clust1" class="cluster"> <title>cluster_Nodes</title> -<polygon fill="none" stroke="black" points="8,-8 8,-404 154,-404 154,-8 8,-8"/> +<polygon fill="none" stroke="black" points="8,-8 8,-410 150,-410 150,-8 8,-8"/> </g> <!-- Never18 --> <g id="node1" class="node"> <title>Never18</title> -<polygon fill="yellow" stroke="transparent" points="24,-195 24,-220 118,-220 118,-195 24,-195"/> -<polygon fill="none" stroke="black" points="24,-195 24,-220 118,-220 118,-195 24,-195"/> -<text text-anchor="start" x="49" y="-203.8" font-family="Times,serif" font-size="14.00">Never</text> -<polygon fill="none" stroke="black" points="24,-164 24,-195 118,-195 118,-164 24,-164"/> -<polygon fill="yellow" stroke="transparent" points="29,-169 29,-190 69,-190 69,-169 29,-169"/> -<polygon fill="none" stroke="black" points="29,-169 29,-190 69,-190 69,-169 29,-169"/> -<text text-anchor="start" x="32" y="-175.8" font-family="Times,serif" font-size="14.00">True</text> -<polygon fill="yellow" stroke="transparent" points="69,-169 69,-190 113,-190 113,-169 69,-169"/> -<polygon fill="none" stroke="black" points="69,-169 69,-190 113,-190 113,-169 69,-169"/> -<text text-anchor="start" x="72" y="-175.8" font-family="Times,serif" font-size="14.00">False</text> +<polygon fill="yellow" stroke="none" points="24,-196.5 24,-223 100,-223 100,-196.5 24,-196.5"/> +<polygon fill="none" stroke="black" points="24,-196.5 24,-223 100,-223 100,-196.5 24,-196.5"/> +<text text-anchor="start" x="45.5" y="-204.7" font-family="Times New Roman,serif" font-size="14.00">Never</text> +<polygon fill="none" stroke="black" points="24,-164 24,-196.5 100,-196.5 100,-164 24,-164"/> +<polygon fill="yellow" stroke="none" points="29,-169 29,-191.5 60.5,-191.5 60.5,-169 29,-169"/> +<polygon fill="none" stroke="black" points="29,-169 29,-191.5 60.5,-191.5 60.5,-169 29,-169"/> +<text text-anchor="start" x="32" y="-175.2" font-family="Times New Roman,serif" font-size="14.00">True</text> +<polygon fill="yellow" stroke="none" points="60.5,-169 60.5,-191.5 95,-191.5 95,-169 60.5,-169"/> +<polygon fill="none" stroke="black" points="60.5,-169 60.5,-191.5 95,-191.5 95,-169 60.5,-169"/> +<text text-anchor="start" x="63.5" y="-175.2" font-family="Times New Roman,serif" font-size="14.00">False</text> </g> <!-- Con_4 --> <g id="node4" class="node"> <title>Con_4</title> -<ellipse fill="none" stroke="black" cx="119" cy="-278" rx="27" ry="18"/> -<text text-anchor="middle" x="119" y="-274.3" font-family="Times,serif" font-size="14.00">#0</text> +<ellipse fill="none" stroke="black" cx="115" cy="-281" rx="27" ry="18"/> +<text text-anchor="middle" x="115" y="-275.95" font-family="Times New Roman,serif" font-size="14.00">#0</text> </g> <!-- Never18->Con_4 --> <g id="edge2" class="edge"> <title>Never18->Con_4</title> -<path fill="none" stroke="black" d="M88.87,-224.28C94.12,-233.46 99.78,-243.37 104.77,-252.1"/> -<polygon fill="black" stroke="black" points="101.86,-254.06 109.86,-261.01 107.94,-250.59 101.86,-254.06"/> -<text text-anchor="middle" x="85.87" y="-226.88" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="black" d="M82.04,-226.82C87.68,-235.93 93.75,-245.71 99.11,-254.37"/> +<polygon fill="black" stroke="black" points="95.99,-255.97 104.23,-262.63 101.94,-252.28 95.99,-255.97"/> +<text text-anchor="middle" x="79.79" y="-228.97" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> <!-- Loop8 --> <g id="node6" class="node"> <title>Loop8</title> -<polygon fill="yellow" stroke="black" points="74,-296 20,-296 20,-260 74,-260 74,-296"/> -<text text-anchor="middle" x="47" y="-274.3" font-family="Times,serif" font-size="14.00">Loop</text> +<polygon fill="yellow" stroke="black" points="70,-299 16,-299 16,-263 70,-263 70,-299"/> +<text text-anchor="middle" x="43" y="-275.95" font-family="Times New Roman,serif" font-size="14.00">Loop</text> </g> <!-- Never18->Loop8 --> <g id="edge1" class="edge"> <title>Never18->Loop8</title> -<path fill="none" stroke="red" d="M62.06,-224.28C59.66,-232.67 57.09,-241.68 54.76,-249.84"/> -<polygon fill="red" stroke="red" points="51.35,-249.02 51.97,-259.6 58.09,-250.94 51.35,-249.02"/> -<text text-anchor="middle" x="59.06" y="-226.88" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="red" d="M54.82,-226.82C52.98,-235.08 51.02,-243.9 49.24,-251.93"/> +<polygon fill="red" stroke="red" points="45.87,-250.97 47.11,-261.49 52.7,-252.49 45.87,-250.97"/> +<text text-anchor="middle" x="52.57" y="-228.97" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Start2 --> <g id="node2" class="node"> <title>Start2</title> -<polygon fill="yellow" stroke="transparent" points="24,-367 24,-392 74,-392 74,-367 24,-367"/> -<polygon fill="none" stroke="black" points="24,-367 24,-392 74,-392 74,-367 24,-367"/> -<text text-anchor="start" x="30.5" y="-375.8" font-family="Times,serif" font-size="14.00">Start</text> -<polygon fill="none" stroke="black" points="24,-336 24,-367 74,-367 74,-336 24,-336"/> -<polygon fill="yellow" stroke="transparent" points="29,-341 29,-362 69,-362 69,-341 29,-341"/> -<polygon fill="none" stroke="black" points="29,-341 29,-362 69,-362 69,-341 29,-341"/> -<text text-anchor="start" x="32" y="-347.8" font-family="Times,serif" font-size="14.00">$ctrl</text> +<polygon fill="yellow" stroke="none" points="23.62,-371.5 23.62,-398 64.38,-398 64.38,-371.5 23.62,-371.5"/> +<polygon fill="none" stroke="black" points="23.62,-371.5 23.62,-398 64.38,-398 64.38,-371.5 23.62,-371.5"/> +<text text-anchor="start" x="31.25" y="-379.7" font-family="Times New Roman,serif" font-size="14.00">Start</text> +<polygon fill="none" stroke="black" points="23.62,-339 23.62,-371.5 64.38,-371.5 64.38,-339 23.62,-339"/> +<polygon fill="yellow" stroke="none" points="28.62,-344 28.62,-366.5 59.38,-366.5 59.38,-344 28.62,-344"/> +<polygon fill="none" stroke="black" points="28.62,-344 28.62,-366.5 59.38,-366.5 59.38,-344 28.62,-344"/> +<text text-anchor="start" x="31.62" y="-350.2" font-family="Times New Roman,serif" font-size="14.00">$ctrl</text> </g> <!-- Stop3 --> <g id="node3" class="node"> <title>Stop3</title> -<polygon fill="yellow" stroke="black" points="108,-52 54,-52 54,-16 108,-16 108,-52"/> -<text text-anchor="middle" x="81" y="-30.3" font-family="Times,serif" font-size="14.00">Stop</text> +<polygon fill="yellow" stroke="black" points="107,-52 53,-52 53,-16 107,-16 107,-52"/> +<text text-anchor="middle" x="80" y="-28.95" font-family="Times New Roman,serif" font-size="14.00">Stop</text> </g> <!-- Return21 --> <g id="node5" class="node"> <title>Return21</title> -<polygon fill="yellow" stroke="black" points="114,-124 48,-124 48,-88 114,-88 114,-124"/> -<text text-anchor="middle" x="81" y="-102.3" font-family="Times,serif" font-size="14.00">Return</text> +<polygon fill="yellow" stroke="black" points="107,-124 53,-124 53,-88 107,-88 107,-124"/> +<text text-anchor="middle" x="80" y="-100.95" font-family="Times New Roman,serif" font-size="14.00">Return</text> </g> <!-- Stop3->Return21 --> <g id="edge3" class="edge"> <title>Stop3->Return21</title> -<path fill="none" stroke="red" d="M81,-52.3C81,-60.02 81,-69.29 81,-77.89"/> -<polygon fill="red" stroke="red" points="77.5,-77.9 81,-87.9 84.5,-77.9 77.5,-77.9"/> -<text text-anchor="middle" x="78" y="-54.9" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="red" d="M80,-52.3C80,-59.59 80,-68.27 80,-76.46"/> +<polygon fill="red" stroke="red" points="76.5,-76.38 80,-86.38 83.5,-76.38 76.5,-76.38"/> +<text text-anchor="middle" x="77.75" y="-54.45" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Return21->Never18 --> <g id="edge4" class="edge"> <title>Return21->Never18:p0</title> -<path fill="none" stroke="red" d="M66.31,-124.18C59.67,-133.33 52.73,-145.27 50.1,-157.83"/> -<polygon fill="red" stroke="red" points="46.6,-157.68 49,-168 53.56,-158.43 46.6,-157.68"/> -<text text-anchor="middle" x="63.31" y="-126.78" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> +<path fill="none" stroke="red" d="M63.41,-124.45C56.41,-133.22 49.19,-144.55 46.19,-156.68"/> +<polygon fill="red" stroke="red" points="42.73,-156.14 44.94,-166.5 49.68,-157.02 42.73,-156.14"/> +<text text-anchor="middle" x="61.16" y="-126.6" font-family="Helvetica,sans-Serif" font-size="8.00">0</text> </g> <!-- Return21->Con_4 --> <g id="edge5" class="edge"> <title>Return21->Con_4</title> -<path fill="none" stroke="black" d="M105.93,-124.14C117.06,-133.33 129,-145.71 135,-160 147.36,-189.44 138.76,-226.37 130.12,-250.77"/> -<polygon fill="black" stroke="black" points="126.74,-249.81 126.48,-260.41 133.29,-252.29 126.74,-249.81"/> -<text text-anchor="middle" x="102.93" y="-126.74" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="black" d="M96.04,-124.15C104.01,-133.88 112.83,-146.73 117,-160 126.51,-190.23 124.01,-226.9 120.42,-251.69"/> +<polygon fill="black" stroke="black" points="117,-250.9 118.86,-261.33 123.91,-252.02 117,-250.9"/> +<text text-anchor="middle" x="93.79" y="-126.3" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Loop8->Never18 --> <g id="edge7" class="edge"> <title>Loop8->Never18:p1</title> -<path fill="none" stroke="red" d="M74.25,-262.42C94.74,-250.81 120.25,-234.78 126,-224 137.5,-202.43 143.04,-186.53 126,-169 118.29,-161.07 103.85,-156.04 96.15,-159.39"/> -<polygon fill="red" stroke="red" points="93.13,-157.62 91,-168 99.14,-161.22 93.13,-157.62"/> -<text text-anchor="middle" x="77.25" y="-256.02" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> +<path fill="none" stroke="red" d="M66.89,-262.62C77.59,-253.37 89.1,-241.02 95,-227 100.86,-213.08 109.7,-194.71 106.13,-185.7"/> +<polygon fill="red" stroke="red" points="107.8,-182.63 97.33,-180.97 104.48,-188.79 107.8,-182.63"/> +<text text-anchor="middle" x="64.64" y="-255.02" font-family="Helvetica,sans-Serif" font-size="8.00">2</text> </g> <!-- Loop8->Start2 --> <g id="edge6" class="edge"> <title>Loop8->Start2:p0</title> -<path fill="none" stroke="red" d="M47.86,-296.09C48.28,-305.54 48.73,-317.77 48.92,-329.68"/> -<polygon fill="red" stroke="red" points="45.42,-330.03 49,-340 52.42,-329.97 45.42,-330.03"/> -<text text-anchor="middle" x="44.86" y="-298.69" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> +<path fill="none" stroke="red" d="M43.44,-299.35C43.64,-308.51 43.85,-320.2 43.95,-331.69"/> +<polygon fill="red" stroke="red" points="40.45,-331.5 43.99,-341.49 47.45,-331.47 40.45,-331.5"/> +<text text-anchor="middle" x="41.19" y="-301.5" font-family="Helvetica,sans-Serif" font-size="8.00">1</text> </g> </g> </svg> diff --git a/chapter11/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java b/chapter11/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java index 53f51f86..35b34576 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java @@ -146,7 +146,7 @@ public abstract class GlobalCodeMotion { // Loads need their memory inputs' uses also done if( n instanceof LoadNode ld ) for( Node memuse : ld.mem()._outputs ) - if( late[memuse._nid]==null && memuse._type instanceof TypeMem ) + if( antiUse(ld,memuse) && late[memuse._nid]==null ) continue outer; // All uses done, schedule @@ -217,6 +217,17 @@ public abstract class GlobalCodeMotion { best instanceof IfNode; } + // Only a store or memory Phi covering this alias can constrain a load. + // MemMerge packages slices without overwriting them. + private static boolean antiUse(LoadNode load, Node use) { + return switch( use ) { + case StoreNode st -> st._alias==load._alias; + case MemPhiNode phi -> phi._alias==load._alias; + case BulkMemPhiNode phi -> !phi.isSplit(load._alias); + default -> false; + }; + } + private static CFGNode find_anti_dep(CFGNode lca, LoadNode load, CFGNode early, CFGNode[] late, int[] anti) { // We could skip final-field loads here. // Walk LCA->early, flagging Load's block location choices @@ -224,6 +235,7 @@ public abstract class GlobalCodeMotion { anti[cfg._nid] = load._nid; // Walk load->mem uses, looking for Stores causing an anti-dep for( Node mem : load.mem()._outputs ) { + if( !antiUse(load,mem) ) continue; switch( mem ) { case StoreNode st: lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st,anti); @@ -235,8 +247,6 @@ public abstract class GlobalCodeMotion { if( phi.in(i)==load.mem() ) lca = anti_dep(load,phi.region().cfg(i),load.mem().cfg0(),lca,null,anti); break; - case LoadNode ld: break; // Loads do not cause anti-deps on other loads - case ReturnNode ret: break; // Load must already be ahead of Return default: throw Utils.TODO(); } } diff --git a/chapter11/src/main/java/com/seaofnodes/simple/Parser.java b/chapter11/src/main/java/com/seaofnodes/simple/Parser.java index 5f4922f4..fb4dfdf8 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/Parser.java @@ -36,6 +36,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; + // Debugger Printing. public static boolean SCHEDULED; // True if debug printer can use schedule info @@ -96,7 +100,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); ZERO = new ConstantNode(TypeInteger.constant(0)).peephole().keep(); XCTRL= new XCtrlNode().peephole().keep(); @@ -123,7 +127,8 @@ public class Parser { // Enter a new scope for the initial control and arguments _scope.push(); _scope.define(ScopeNode.CTRL, Type.CONTROL , new CProjNode(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(); if( ctrl()._type==Type.CONTROL ) STOP.addReturn(new ReturnNode(ctrl(), new ConstantNode(TypeInteger.constant(0)).peephole(), _scope).peephole()); @@ -211,7 +216,8 @@ public class Parser { // Build and install the TypeStruct TypeStruct ts = TypeStruct.make(typeName, fields); OBJS.put(typeName, ts); // Insert the struct name in the collection of all struct names - START.addMemProj(ts, _scope); // Insert memory edges + _aliases.put(typeName,_alias); + _alias += ts._fields.length; return parseStatement(); } @@ -599,21 +605,24 @@ public class Parser { */ private Node newStruct(TypeStruct obj) { Node n = new NewNode(TypeMemPtr.make(obj), ctrl()).peephole().keep(); - 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, ctrl(), memAlias(alias), n, ZERO).peephole()); + store(field._fname,alias,n,ZERO); 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,ctrl(),prior,ptr,val).peephole(); + mem(new MemMergeNode(prior,alias,st).peephole()); + prior.unkeep(); + } /** * Parse postfix expression. For now this is just a field @@ -632,20 +641,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, ctrl(), 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/chapter11/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java new file mode 100644 index 00000000..8d512f21 --- /dev/null +++ b/chapter11/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/chapter11/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/LoadNode.java index 39bbaff1..6d44c84a 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter11/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; @@ -25,6 +26,24 @@ public class LoadNode extends MemOpNode { _declaredType = glb; } + @Override boolean canDrop(MemOpNode other, Node dep) { + return super.canDrop(other,dep) && _declaredType==((LoadNode)other)._declaredType && !clobbered(dep); + } + + // Check only immediate memory users. Stores clobber memory; Phis and + // aggregates might lead to a clobber, so stop rather than search further. + private boolean clobbered(Node dep) { + Node mem = mem(); + mem.addDepForwards(dep); + for( Node use : mem._outputs ) { + if( use==null ) continue; + use.addDepForwards(dep); + if( use instanceof StoreNode || use instanceof PhiNode || + use instanceof MemMergeNode ) return true; + } + return false; + } + @Override public String label() { return "Load"; } @@ -38,10 +57,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 _name.equals(st._name); // Equiv class aliasing is perfect return st.val(); } @@ -53,7 +77,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 +94,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/chapter11/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java new file mode 100644 index 00000000..d7fed402 --- /dev/null +++ b/chapter11/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/chapter11/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/MemOpNode.java index 551d2aa9..5a57a8c8 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/MemOpNode.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/MemOpNode.java @@ -21,6 +21,13 @@ public abstract class MemOpNode extends Node { public Node mem() { return in(1); } public Node ptr() { return in(2); } + // Extra conditions for factoring matching memory operations through a Phi. + // The caller has already checked the opcode, input shape and control. + boolean canDrop(MemOpNode other, Node dep) { + ptr().addDep(dep); + return _alias==other._alias && err()==null; + } + @Override boolean eq(Node n) { MemOpNode mem = (MemOpNode)n; // Invariant diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java new file mode 100644 index 00000000..c806bede --- /dev/null +++ b/chapter11/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/chapter11/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter11/src/main/java/com/seaofnodes/simple/node/Node.java index 67c82546..2d09db40 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/Node.java @@ -14,14 +14,14 @@ import java.util.function.Function; * The Node class provides common functionality used by all subtypes. * Subtypes of Node specialize by overriding methods. */ -public abstract class Node implements OutNode { +public abstract class Node implements OutNode, Cloneable { /** * 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; + public int _nid; /** * Inputs to the node. These are use-def references to Nodes. @@ -30,7 +30,7 @@ public abstract class Node implements OutNode { * 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; + public ArrayList<Node> _inputs; /** * Outputs reference Nodes that are not null and have this Node as an @@ -41,7 +41,7 @@ public abstract class Node implements OutNode { * walked in either direction. These outputs are typically used for * efficient optimizations but otherwise have no semantics meaning. */ - public final ArrayList<Node> _outputs; + public ArrayList<Node> _outputs; /** @@ -208,6 +208,7 @@ public abstract class Node implements OutNode { // Error is 'use' does not exist; ok for 'use' to be null. protected boolean delUse( Node use ) { Utils.del(_outputs, Utils.find(_outputs, use)); + moveDepsToWorklist(); // User-count and anti-dependence queries can now change. return _outputs.isEmpty(); } @@ -263,6 +264,7 @@ public abstract class Node implements OutNode { n.unlock(); int idx = Utils.find(n._inputs, this); n._inputs.set(idx,nnn); + n.moveDepsToWorklist(); // Rewiring can change a dependent query without changing type. nnn.addUse(n); } kill(); @@ -448,6 +450,14 @@ public abstract class Node implements OutNode { * 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; @@ -455,8 +465,8 @@ public abstract class Node implements OutNode { 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( Utils.find(_inputs,dep) != -1 ) return this; // No need for deps on immediate neighbors - if( Utils.find(_outputs,dep)!= -1 ) return this; + if( !forwards && Utils.find(_inputs,dep) != -1 ) return this; + if( !forwards && Utils.find(_outputs,dep)!= -1 ) return this; _deps.add(dep); return this; } @@ -567,6 +577,20 @@ public abstract class Node implements OutNode { // Does not need to be implemented in isCFG() nodes. Node copy(Node lhs, Node rhs) { throw Utils.TODO("Binary ops need to implement copy"); } + // Exact-class copy preserving operation attributes, with fresh identity + // and no edges, dependencies, or GVN membership. + public final Node copyEmpty() { + Node n; + try { n = (Node)clone(); } + catch( CloneNotSupportedException e ) { throw new AssertionError(e); } + n._nid = UNIQUE_ID++; + n._inputs = new ArrayList<>(); + n._outputs = new ArrayList<>(); + n._deps = null; + n._hash = 0; + return n; + } + // Report any post-optimize errors String err() { return null; } diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/PhiNode.java index 52f098fa..886ef092 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -16,6 +16,10 @@ public class PhiNode extends Node { 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 @@ -65,23 +69,9 @@ public class PhiNode extends Node { if (live != null) return live; - // Pull "down" a common data op. One less op in the world. One more - // Phi, but Phis do not make code. - // Phi(op(A,B),op(Q,R),op(X,Y)) becomes - // op(Phi(A,Q,X), Phi(B,R,Y)). - Node op = in(1); - if( op.nIns()==3 && op.in(0)==null && !op.isCFG() && same_op() ) { - Node[] lhss = new Node[nIns()]; - Node[] rhss = new Node[nIns()]; - lhss[0] = rhss[0] = in(0); // Set Region - for( int i=1; i<nIns(); i++ ) { - lhss[i] = in(i).in(1); - rhss[i] = in(i).in(2); - } - Node phi_lhs = new PhiNode(_label, _declaredType,lhss).peephole(); - Node phi_rhs = new PhiNode(_label, _declaredType,rhss).peephole(); - return op.copy(phi_lhs,phi_rhs); - } + // Phi(op(a,b,...),op(c,d,...)) -> op(Phi(a,c),Phi(b,d),...). + Node down; + if( same_op() && (down=drop_same_op())!=null ) return down; // If merging Phi(N, cast(N)) - we are losing the cast JOIN effects, so just remove. if( nIns()==3 ) { @@ -111,12 +101,72 @@ public class PhiNode extends Node { } private boolean same_op() { - for( int i=2; i<nIns(); i++ ) - if( in(1).getClass() != in(i).getClass() ) - return false; + Node op = in(1); + if( op.isCFG() || op instanceof ConstantNode || op instanceof PhiNode || + op instanceof ProjNode || op instanceof NewNode || op instanceof ScopeNode || op instanceof MemMergeNode ) return false; + // A bulk Phi must split its aliases before factoring precise Stores. + if( this instanceof BulkMemPhiNode ) return false; + // Bulk splitting currently identifies parallel slices by Region/alias. + // Factoring can introduce another slice at a different memory point; + // wait until that Region's bulk partitioning has finished. + if( op instanceof MemOpNode ) + for( Node use : region()._outputs ) + if( use instanceof BulkMemPhiNode ) { + use.addDepForwards(this); + return false; + } + Node busy = null; + for( int i=1; i<nIns(); i++ ) { + Node n = in(i); + if( region().in(i).addDep(this)._type==Type.XCONTROL || + op.getClass()!=n.getClass() || n.nIns()!=op.nIns() || n.in(0)!=null || !op.eq(n) ) return false; + if( n instanceof MemOpNode mem && !mem.canDrop((MemOpNode)op,this) ) return false; + // Moving a Store must remove the old effect, not duplicate it. + // Allow one shared arm: the other arms disappear into the single + // factored operation, usually reducing the total operation count. + n.addDepForwards(this); + if( n.nOuts()>1 ) { + for( Node use : n._outputs ) + if( use!=null && use!=this ) use.addDepForwards(this); + if( n instanceof StoreNode || busy!=null ) return false; + busy=n; + } + for( int j=1; j<n.nIns(); j++ ) + if( n.in(j) instanceof ScopeNode || ((n.in(j)==null) != (op.in(j)==null)) ) return false; + } return true; } + private Node drop_same_op() { + Node op = in(1), cp = op.copyEmpty(); + cp._type = null; + cp.addDef(null); + for( int j=1; j<op.nIns(); j++ ) { + Node x = op.in(j); + boolean different = false; + for( int i=2; i<nIns(); i++ ) + if( in(i).in(j)!=x ) different=true; + if( different ) { + Node[] ins = new Node[nIns()]; + ins[0] = region(); + Type t = Type.TOP; + for( int i=1; i<nIns(); i++ ) { + ins[i] = in(i).in(j); + t = t.meet(ins[i]._type); + } + PhiNode phi = j==1 && op instanceof MemOpNode mem + ? new MemPhiNode(_label,mem._alias,ins) + : PhiNode.make(_label,t.glb(),ins); + x = phi.peephole(); + } + cp.addDef(x); + } + // Factoring must not widen the result (e.g. correlated And operands). + if( cp.compute().isa(compute()) ) return cp; + cp.kill(); + return null; + } + /** * If only single unique input, return it */ diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/RegionNode.java index 060dfb45..8933ea74 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/RegionNode.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/RegionNode.java @@ -68,7 +68,7 @@ public class RegionNode extends CFGNode { 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.addDepForwards(this).in(0)==p2.addDepForwards(this).in(0) && p1.in(0) instanceof IfNode iff ) return iff.ctrl(); diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/ReturnNode.java index 444b76a8..725c704f 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -4,32 +4,17 @@ 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 all preceding memory effects and the return value alive: {ctrl, $mem, value}. */ public class ReturnNode extends CFGNode { 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 - if( scope != null ) { - String[] names = scope.reverseNames(); - for (String name: names) { - if (!name.equals("$ctrl") && name.startsWith("$")) - addDef(scope.lookup(name)); - } - } + // A synthetic never-taken loop exit has no source scope. + super(ctrl, scope == null ? null : scope.lookup("$mem"), data); } public Node ctrl() { return in(0); } - public Node expr() { return in(1); } + public Node mem () { return in(1); } + public Node expr() { return in(2); } @Override public String label() { return "Return"; } @@ -43,7 +28,8 @@ public class ReturnNode extends CFGNode { @Override public Type compute() { - return TypeTuple.make(ctrl()._type,expr()._type); + // Return exposes the complete memory state. + return TypeTuple.make(ctrl()._type,TypeMem.BOT,expr()._type); } @Override diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/ScopeNode.java index 455f4b3a..b931e862 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -129,7 +129,7 @@ public class ScopeNode extends Node { // Set real Phi in the loop head // The phi takes its one input (no backedge yet) from a recursive // lookup, which might have insert a Phi in every loop nest. - : loop.setDef(idx,new PhiNode(name, lookupDeclaredType(name),loop.ctrl(),loop.update(name,null,nestingLevel),null).peephole()); + : 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 @@ -207,7 +207,7 @@ public class ScopeNode extends Node { if( in(i) != that.in(i) ) // No need for redundant Phis // If we are in lazy phi mode we need to a lookup // by name as it will trigger a phi creation - setDef(i, new PhiNode(ns[i], this.lookupDeclaredType(ns[i]), r, this.lookup(ns[i]), that.lookup(ns[i])).peephole()); + 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(); diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/StartNode.java index 62fc8a6b..b4fa549b 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ b/chapter11/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -1,59 +1,17 @@ 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; import java.util.HashSet; -/** - * 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. - */ +/** Start supplies control, whole memory, and the argument, in that order. */ public class StartNode extends CFGNode implements 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; - + private final TypeTuple _args; 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 diff --git a/chapter11/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter11/src/main/java/com/seaofnodes/simple/node/StoreNode.java index 5b30b3ec..b816762b 100644 --- a/chapter11/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter11/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/chapter11/src/test/java/com/seaofnodes/simple/Chapter04Test.java b/chapter11/src/test/java/com/seaofnodes/simple/Chapter04Test.java index a3109ff7..14d31245 100644 --- a/chapter11/src/test/java/com/seaofnodes/simple/Chapter04Test.java +++ b/chapter11/src/test/java/com/seaofnodes/simple/Chapter04Test.java @@ -84,7 +84,10 @@ public class Chapter04Test { StopNode stop = parser.parse(); ReturnNode ret = stop.ret(); assertTrue(ret.in(0) instanceof CProjNode); - assertTrue(ret.in(1) instanceof ProjNode); + assertTrue(ret.in(1) instanceof ProjNode); + assertEquals(1, ((ProjNode)ret.in(1))._idx); + assertTrue(ret.in(2) instanceof ProjNode); + assertEquals(2, ((ProjNode)ret.in(2))._idx); } @Test diff --git a/chapter11/src/test/java/com/seaofnodes/simple/Chapter05Test.java b/chapter11/src/test/java/com/seaofnodes/simple/Chapter05Test.java index b01ffe0a..7beeb4d9 100644 --- a/chapter11/src/test/java/com/seaofnodes/simple/Chapter05Test.java +++ b/chapter11/src/test/java/com/seaofnodes/simple/Chapter05Test.java @@ -22,7 +22,7 @@ else { return a;"""); StopNode ret = parser.parse().iterate(); - assertEquals("return Phi(Region20,(arg+2),(arg-3));", ret.toString()); + assertEquals("return Phi(Region21,(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(Region18,4,3);", ret.toString()); + assertEquals("return Phi(Region19,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(Region23,2,3));", ret.toString()); + assertEquals("return ((arg*2)+Phi(Region24,2,3));", ret.toString()); } @Test @@ -80,7 +80,7 @@ else a=b+1; return a+b;"""); StopNode ret = parser.parse().iterate(); - assertEquals("return ((Phi(Region34,(arg*2),arg)+arg)+Phi(Region,4,5));", ret.toString()); + assertEquals("return ((Phi(Region35,(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(Region38,Phi(Region24,2,3),Phi(Region36,4,5));", stop.toString()); + assertEquals("return Phi(Region39,Phi(Region25,2,3),Phi(Region37,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(Region15,1,0))+Phi(Region30,2,0));", stop.toString()); + assertEquals("return ((arg+Phi(Region16,1,0))+Phi(Region31,2,0));", stop.toString()); } @Test @@ -130,7 +130,7 @@ if( arg==1 ) } return a;"""); StopNode ret = parser.parse().iterate(); - assertEquals("return (arg==Phi(Region18,3,2));", ret.toString()); + assertEquals("return (arg==Phi(Region19,3,2));", ret.toString()); } @Test diff --git a/chapter11/src/test/java/com/seaofnodes/simple/Chapter06Test.java b/chapter11/src/test/java/com/seaofnodes/simple/Chapter06Test.java index 7b11bc35..df0f2848 100644 --- a/chapter11/src/test/java/com/seaofnodes/simple/Chapter06Test.java +++ b/chapter11/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(Region14,2,1))<3);", stop.toString()); + assertEquals("return ((arg<Phi(Region15,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(Region39,42,5);", stop.toString()); + assertEquals("return Phi(Region40,42,5);", stop.toString()); } @Test @@ -84,7 +84,7 @@ else b=5; return b;""", TypeInteger.BOT); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region32,2,5);", stop.toString()); + assertEquals("return Phi(Region33,2,5);", stop.toString()); } @Test @@ -144,7 +144,7 @@ else return a; """, TypeInteger.BOT); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region43,3,Phi(Region41,4,5));", stop.toString()); + assertEquals("return Phi(Region44,3,Phi(Region42,4,5));", stop.toString()); } @Test @@ -200,7 +200,7 @@ if( arg ) { return a+b; """); StopNode ret = parser.parse().iterate(); - assertEquals("return Phi(Region26,4,1);", ret.toString()); + assertEquals("return Phi(Region27,4,1);", ret.toString()); } @@ -251,7 +251,7 @@ if( arg ) { return a+b+c; """); StopNode ret = parser.parse().iterate(); - assertEquals("return (Phi(Region38,Phi(Region25,2,3),0)+Phi(Region,3,1));", ret.toString()); + assertEquals("return (Phi(Region39,Phi(Region26,2,3),0)+Phi(Region,3,1));", ret.toString()); } diff --git a/chapter11/src/test/java/com/seaofnodes/simple/Chapter07Test.java b/chapter11/src/test/java/com/seaofnodes/simple/Chapter07Test.java index 177b54d1..222982e7 100644 --- a/chapter11/src/test/java/com/seaofnodes/simple/Chapter07Test.java +++ b/chapter11/src/test/java/com/seaofnodes/simple/Chapter07Test.java @@ -18,7 +18,7 @@ public class Chapter07Test { return arg; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop8,arg,(Phi_arg+1));", stop.toString()); + assertEquals("return Phi(Loop9,arg,(Phi_arg+1));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -35,7 +35,7 @@ if(arg){}else{ return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region26,1,Phi(Loop15,1,(Phi_a+1)));", stop.toString()); + assertEquals("return Phi(Region27,1,Phi(Loop16,1,(Phi_a+1)));", stop.toString()); } @Test @@ -55,7 +55,7 @@ while(i < arg) { return sum; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,0,Phi(Loop22,Phi_sum,(Phi_sum+Phi(Loop,0,(Phi_j+1)))));", stop.toString()); + assertEquals("return Phi(Loop11,0,Phi(Loop23,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(Loop10,2,Phi(Region29,Phi_b,4));", stop.toString()); + assertEquals("return Phi(Loop11,2,Phi(Region30,Phi_b,4));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -90,7 +90,7 @@ while(a < 10) { return b; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,2,(Phi(Region29,Phi_b,4)+1));", stop.toString()); + assertEquals("return Phi(Loop11,2,(Phi(Region30,Phi_b,4)+1));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -107,7 +107,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop9,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -123,7 +123,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -136,7 +136,7 @@ while(arg) a = 2; return a; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop9,1,2);", stop.toString()); + assertEquals("return Phi(Loop10,1,2);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -149,7 +149,7 @@ while(arg) a = 2; return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,1,2);", stop.toString()); + assertEquals("return Phi(Loop10,1,2);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -165,7 +165,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop9,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -181,7 +181,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -198,7 +198,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop11,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -215,7 +215,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop11,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } diff --git a/chapter11/src/test/java/com/seaofnodes/simple/Chapter08Test.java b/chapter11/src/test/java/com/seaofnodes/simple/Chapter08Test.java index 98fd181b..8fcb29a0 100644 --- a/chapter11/src/test/java/com/seaofnodes/simple/Chapter08Test.java +++ b/chapter11/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(Region26,Phi(Loop8,arg,(Phi_arg+1)),Add),Add);", stop.toString()); + assertEquals("return Phi(Region37,Phi(Region27,Phi(Loop9,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(Loop9,1,Phi(Region41,Phi_a,(Phi_a+1)));", stop.toString()); + assertEquals("return Phi(Loop10,1,Phi(Region42,Phi_a,(Phi_a+1)));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -66,7 +66,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region34,Phi(Loop8,arg,(Phi_arg+1)),Add);", stop.toString()); + assertEquals("return Phi(Region35,Phi(Loop9,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(Region26,Phi(Loop8,arg,(Phi_arg+1)),Add);", stop.toString()); + assertEquals("return Phi(Region27,Phi(Loop9,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(Loop8,arg,(Phi_arg+1));", stop.toString()); + assertEquals("return Phi(Loop9,arg,(Phi_arg+1));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -118,7 +118,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop8,arg,(Phi_arg+1));", stop.toString()); + assertEquals("return Phi(Loop9,arg,(Phi_arg+1));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -186,7 +186,7 @@ while(arg < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region29,Phi(Loop9,1,(Phi_a+1)),Add);", stop.toString()); + assertEquals("return Phi(Region30,Phi(Loop10,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(Loop9,1,Add)+1);", stop.toString()); + assertEquals("return (Phi(Loop10,1,Add)+1);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode prj && prj._idx==1 ); } diff --git a/chapter11/src/test/java/com/seaofnodes/simple/Chapter09Test.java b/chapter11/src/test/java/com/seaofnodes/simple/Chapter09Test.java index 2abce3cb..7af2fa3d 100644 --- a/chapter11/src/test/java/com/seaofnodes/simple/Chapter09Test.java +++ b/chapter11/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(Loop9,arg,(Phi_arg+2));", stop.toString()); + assertEquals("return Phi(Loop10,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(Loop10,arg,(Phi_arg+4));", stop.toString()); + assertEquals("return Phi(Loop11,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(Loop16,arg,(Phi_arg+1));", stop.toString()); + assertEquals("return Phi(Loop17,arg,(Phi_arg+1));", stop.toString()); } @Test @@ -186,7 +186,7 @@ while(arg) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop11,0,(-(Phi_a+3)));", stop.toString()); + assertEquals("return Phi(Loop12,0,(-(Phi_a+3)));", stop.toString()); } @Test @@ -284,7 +284,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(Loop8,arg,(Phi_arg-1));", stop.toString()); + assertEquals("return Phi(Loop9,arg,(Phi_arg-1));", stop.toString()); } @Test diff --git a/chapter11/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter11/src/test/java/com/seaofnodes/simple/Chapter10Test.java index 8dc489f0..0da695a0 100644 --- a/chapter11/src/test/java/com/seaofnodes/simple/Chapter10Test.java +++ b/chapter11/src/test/java/com/seaofnodes/simple/Chapter10Test.java @@ -1,6 +1,11 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.node.Node; import com.seaofnodes.simple.node.StopNode; +import com.seaofnodes.simple.node.LoadNode; +import com.seaofnodes.simple.node.StoreNode; +import com.seaofnodes.simple.evaluator.Evaluator; +import java.util.BitSet; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -160,7 +165,7 @@ while (arg) { return bar.a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop11,0,(Phi_a+2));", stop.toString()); + assertEquals("return Phi(Loop12,0,(Phi_a+2));", stop.toString()); } @Test @@ -212,7 +217,7 @@ if( bar ) bar.a = 1; return bar; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region16,null,new Bar);", stop.toString()); + assertEquals("return Phi(Region17,null,new Bar);", stop.toString()); } @Test @@ -228,7 +233,7 @@ else bar.a = 1; return rez; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region33,4,3);", stop.toString()); + assertEquals("return Phi(Region35,4,3);", stop.toString()); } @Test @@ -282,7 +287,7 @@ while( i.x < i.len ) { return sum; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop15,0,(Phi(Loop,0,(Phi_x+1))+Phi_sum));", stop.toString()); + assertEquals("return Phi(Loop17,0,(Phi(Loop,0,(Phi_x+1))+Phi_sum));", stop.toString()); } @@ -303,7 +308,7 @@ while(arg) { return ret; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop11,new s0,Phi(Region31,new s0,Phi_ret));", stop.toString()); + assertEquals("return Phi(Loop12,new s0,Phi(Region34,new s0,Phi_ret));", stop.toString()); } @Test @@ -321,7 +326,7 @@ while(arg) { return ret; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop13,new s0,Phi(Region32,new s0,Phi_ret));", stop.toString()); + assertEquals("return Phi(Loop15,new s0,Phi(Region35,new s0,Phi_ret));", stop.toString()); } @@ -338,7 +343,7 @@ while(arg < 10) { return ret; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop11,new s0,Phi(Region30,new s0,Phi_ret));", stop.toString()); + assertEquals("return Phi(Loop12,new s0,Phi(Region32,new s0,Phi_ret));", stop.toString()); } @Test @@ -456,4 +461,121 @@ 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)); + } + + 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()); + } + } + + @Test public void testKeepLoadsAtMerge() { + StopNode stop = new Parser(""" + struct S { int x; } + S a = new S; S b = new S; + a.x = arg; b.x = arg+1; + S p = a; S q = b; + if (arg<0) { p=b; q=a; } + int v; + if (arg>1) v=p.x; else v=q.x; + return v; + """).parse().iterate(); + // The one-step safety check stops at the memory aggregate. + assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet())); + assertEquals(-1L,Evaluator.evaluate(stop,-1)); + assertEquals(1L,Evaluator.evaluate(stop,0)); + assertEquals(3L,Evaluator.evaluate(stop,3)); + } + + @Test public void testDropStores() { + StopNode stop = new Parser(""" + struct S { int x; } + S s = new S; + if (arg) s.x=arg+1; else s.x=arg+2; + return s; + """).parse().iterate(); + // These chapters still bind struct Stores to branch control. + assertEquals(3,countMemoryNodes(stop,StoreNode.class,new BitSet())); + assertEquals(2L,((Evaluator.Obj)Evaluator.evaluate(stop,0)).fields()[0]); + assertEquals(4L,((Evaluator.Obj)Evaluator.evaluate(stop,3)).fields()[0]); + } + + @Test public void testKeepReadsBeforeWrites() { + StopNode stop = new Parser(""" + struct S { int x; int y; } + S a = new S; S b = new S; + a.x=arg; b.x=arg+1; + S p=a; S q=b; + if (arg<0) { p=b; q=a; } + int v; + if (arg>1) { v=p.x; p.x=41; p.y=5; } + else { v=q.x; q.x=42; } + return v; + """).parse().iterate(); + assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet())); + assertEquals(-1L,Evaluator.evaluate(stop,-1)); + assertEquals(1L,Evaluator.evaluate(stop,0)); + assertEquals(3L,Evaluator.evaluate(stop,3)); + } + + private static int countMemoryNodes(Node n, Class<?> kind, BitSet seen) { + if (n==null || seen.get(n._nid)) return 0; + seen.set(n._nid); + int cnt=kind.isInstance(n) ? 1 : 0; + for (Node def : n._inputs) cnt+=countMemoryNodes(def,kind,seen); + return cnt; + } } diff --git a/chapter11/src/test/java/com/seaofnodes/simple/Chapter10bTest.java b/chapter11/src/test/java/com/seaofnodes/simple/Chapter10bTest.java new file mode 100644 index 00000000..6f094cfb --- /dev/null +++ b/chapter11/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/chapter11/src/test/java/com/seaofnodes/simple/Chapter11Test.java b/chapter11/src/test/java/com/seaofnodes/simple/Chapter11Test.java index 3e03d1f7..4c2b42f1 100644 --- a/chapter11/src/test/java/com/seaofnodes/simple/Chapter11Test.java +++ b/chapter11/src/test/java/com/seaofnodes/simple/Chapter11Test.java @@ -326,7 +326,7 @@ while( prime <= arg ) { return primeCount; """); StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ return 0; return Phi(Loop19,1,Phi(Region81,Phi_primeCount,Phi(Region76,(Phi_primeCount+1),Phi_primeCount))); ]", stop.toString()); + assertEquals("Stop[ return 0; return Phi(Loop20,1,Phi(Region82,Phi_primeCount,Phi(Region77,(Phi_primeCount+1),Phi_primeCount))); ]", stop.toString()); assertEquals(0L, Evaluator.evaluate(stop, 1)); // No primes 1 or below assertEquals(1L, Evaluator.evaluate(stop, 2)); // 2 assertEquals(2L, Evaluator.evaluate(stop, 3)); // 2, 3 @@ -368,7 +368,7 @@ if (arg) { return i; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region31,.f,0);", stop.toString()); + assertEquals("return Phi(Region34,.f,0);", stop.toString()); } @Test @@ -474,7 +474,7 @@ while(arg) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop13,arg,Phi(Region35,.f,0));", stop.toString()); + assertEquals("return Phi(Loop15,arg,Phi(Region39,.f,0));", stop.toString()); } @Test @@ -525,7 +525,7 @@ else if (arg == 1) s.f = 1; return s.f; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region35,1,Phi(Region33,1,0));", stop.toString()); + assertEquals("return Phi(Region41,1,Phi(Region36,1,0));", stop.toString()); assertEquals(1L, Evaluator.evaluate(stop, 0)); assertEquals(1L, Evaluator.evaluate(stop, 1)); assertEquals(0L, Evaluator.evaluate(stop, 2)); diff --git a/chapter11/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/chapter11/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java index 4a405a3b..355d906b 100644 --- a/chapter11/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java +++ b/chapter11/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java @@ -138,6 +138,7 @@ public class Evaluator { 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 CProjNode cproj -> ((Object[])val(cproj.ctrl()))[cproj._idx]; case ProjNode proj -> ((Object[])val( proj.in(0) ))[ proj._idx]; @@ -151,8 +152,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; @@ -163,7 +164,7 @@ public class Evaluator { case null: return Status.FALLTHROUGH; case ReturnNode ret: - return val(ret.in(1)); + return val(ret.expr()); case IfNode ifn: block = block.next()[isTrue(val(ifn.in(1))) ? 0 : 1]; if (block == null) return Status.FALLTHROUGH; diff --git a/chapter11/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java b/chapter11/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java index 8b86d67a..d16f4300 100644 --- a/chapter11/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java +++ b/chapter11/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java @@ -250,6 +250,12 @@ public class Scheduler { // 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++) { @@ -271,7 +277,7 @@ public class Scheduler { // 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) od(out).ifPresent(this::decUsers); + if (out instanceof LoadNode l && l._alias==s._alias) od(out).ifPresent(this::decUsers); } } } @@ -469,7 +475,8 @@ public class Scheduler { var data = mem.pop(); node = data.node; for(var out:node.in(1)._outputs) { - if (out instanceof LoadNode) od(out).ifPresent(d->d.users++); + if (out instanceof LoadNode l && l._alias==((StoreNode)node)._alias) + od(out).ifPresent(d->d.users++); } } } diff --git a/chapter12/README.md b/chapter12/README.md index 38a6eed8..28fbb6dc 100644 --- a/chapter12/README.md +++ b/chapter12/README.md @@ -1,5 +1,10 @@ # Chapter 12: Floats +Memory keeps Chapter 11's single `$mem` binding and lazy `MemMerge`, `MemPhi`, +and `BulkMemPhi` partitions. Start produces `{ctrl, $mem, arg}` and Return +consumes `{ctrl, $mem, result}`; floating-point arithmetic needs no new memory +representation. + # Table of Contents 1. [Float](#floats) @@ -55,4 +60,3 @@ Within the Type Lattice, we now add the following domain: with a TOP, BOT and constants  - diff --git a/chapter12/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java b/chapter12/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java index bf7d1baa..74b58e6b 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java @@ -145,7 +145,7 @@ public abstract class GlobalCodeMotion { // Loads need their memory inputs' uses also done if( n instanceof LoadNode ld ) for( Node memuse : ld.mem()._outputs ) - if( late[memuse._nid]==null && memuse._type instanceof TypeMem ) + if( antiUse(ld,memuse) && late[memuse._nid]==null ) continue outer; // All uses done, schedule @@ -216,6 +216,17 @@ public abstract class GlobalCodeMotion { best instanceof IfNode; } + // Only a store or memory Phi covering this alias can constrain a load. + // MemMerge packages slices without overwriting them. + private static boolean antiUse(LoadNode load, Node use) { + return switch( use ) { + case StoreNode st -> st._alias==load._alias; + case MemPhiNode phi -> phi._alias==load._alias; + case BulkMemPhiNode phi -> !phi.isSplit(load._alias); + default -> false; + }; + } + private static CFGNode find_anti_dep(CFGNode lca, LoadNode load, CFGNode early, CFGNode[] late, int[] anti) { // We could skip final-field loads here. // Walk LCA->early, flagging Load's block location choices @@ -223,6 +234,7 @@ public abstract class GlobalCodeMotion { anti[cfg._nid] = load._nid; // Walk load->mem uses, looking for Stores causing an anti-dep for( Node mem : load.mem()._outputs ) { + if( !antiUse(load,mem) ) continue; switch( mem ) { case StoreNode st: lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st,anti); @@ -234,9 +246,6 @@ public abstract class GlobalCodeMotion { if( phi.in(i)==load.mem() ) lca = anti_dep(load,phi.region().cfg(i),load.mem().cfg0(),lca,null,anti); break; - case LoadNode ld: break; // Loads do not cause anti-deps on other loads - case ReturnNode ret: break; // Load must already be ahead of Return - case NeverNode never: break; default: throw Utils.TODO(); } } diff --git a/chapter12/src/main/java/com/seaofnodes/simple/Parser.java b/chapter12/src/main/java/com/seaofnodes/simple/Parser.java index f5c63cc7..9c213165 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/Parser.java @@ -36,6 +36,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; + // Debugger Printing. public static boolean SCHEDULED; // True if debug printer can use schedule info @@ -99,7 +103,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); ZERO = new ConstantNode(TypeInteger.constant(0)).peephole().keep(); XCTRL= new XCtrlNode().peephole().keep(); @@ -126,7 +130,8 @@ public class Parser { // Enter a new scope for the initial control and arguments _scope.push(); _scope.define(ScopeNode.CTRL, Type.CONTROL , new CProjNode(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(); if( ctrl()._type==Type.CONTROL ) STOP.addReturn(new ReturnNode(ctrl(), new ConstantNode(TypeInteger.constant(0)).peephole(), _scope).peephole()); @@ -214,7 +219,8 @@ public class Parser { // Build and install the TypeStruct TypeStruct ts = TypeStruct.make(typeName, fields); TYPES.put(typeName, ts); // Insert the struct name in the collection of all struct names - START.addMemProj(ts, _scope); // Insert memory edges + _aliases.put(typeName,_alias); + _alias += ts._fields.length; return parseStatement(); } @@ -582,21 +588,24 @@ public class Parser { */ private Node newStruct(TypeStruct obj) { Node n = new NewNode(TypeMemPtr.make(obj), ctrl()).peephole().keep(); - 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, ctrl(), memAlias(alias), n, ZERO).peephole()); + store(field._fname,alias,n,ZERO); 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,ctrl(),prior,ptr,val).peephole(); + mem(new MemMergeNode(prior,alias,st).peephole()); + prior.unkeep(); + } /** * Parse postfix expression. For now this is just a field @@ -616,20 +625,20 @@ public class Parser { if( expr._type == TypeMemPtr.TOP ) throw error("Accessing field '" + name + "' from null"); 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, ctrl(), 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/chapter12/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java new file mode 100644 index 00000000..8d512f21 --- /dev/null +++ b/chapter12/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/chapter12/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/LoadNode.java index a56678df..72af8101 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter12/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; @@ -25,6 +26,24 @@ public class LoadNode extends MemOpNode { _declaredType = glb; } + @Override boolean canDrop(MemOpNode other, Node dep) { + return super.canDrop(other,dep) && _declaredType==((LoadNode)other)._declaredType && !clobbered(dep); + } + + // Check only immediate memory users. Stores clobber memory; Phis and + // aggregates might lead to a clobber, so stop rather than search further. + private boolean clobbered(Node dep) { + Node mem = mem(); + mem.addDepForwards(dep); + for( Node use : mem._outputs ) { + if( use==null ) continue; + use.addDepForwards(dep); + if( use instanceof StoreNode || use instanceof PhiNode || + use instanceof MemMergeNode ) return true; + } + return false; + } + @Override public String label() { return "Load"; } @@ -38,10 +57,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 _name.equals(st._name); // Equiv class aliasing is perfect return st.val(); } @@ -53,7 +77,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. @@ -74,6 +98,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/chapter12/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java new file mode 100644 index 00000000..d7fed402 --- /dev/null +++ b/chapter12/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/chapter12/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/MemOpNode.java index 551d2aa9..5a57a8c8 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/MemOpNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/MemOpNode.java @@ -21,6 +21,13 @@ public abstract class MemOpNode extends Node { public Node mem() { return in(1); } public Node ptr() { return in(2); } + // Extra conditions for factoring matching memory operations through a Phi. + // The caller has already checked the opcode, input shape and control. + boolean canDrop(MemOpNode other, Node dep) { + ptr().addDep(dep); + return _alias==other._alias && err()==null; + } + @Override boolean eq(Node n) { MemOpNode mem = (MemOpNode)n; // Invariant diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java new file mode 100644 index 00000000..c806bede --- /dev/null +++ b/chapter12/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/chapter12/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter12/src/main/java/com/seaofnodes/simple/node/Node.java index 53ab0482..ff8415c5 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/Node.java @@ -16,14 +16,14 @@ import java.util.function.Function; * The Node class provides common functionality used by all subtypes. * Subtypes of Node specialize by overriding methods. */ -public abstract class Node implements OutNode { +public abstract class Node implements OutNode, Cloneable { /** * 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; + public int _nid; /** * Inputs to the node. These are use-def references to Nodes. @@ -32,7 +32,7 @@ public abstract class Node implements OutNode { * 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; + public ArrayList<Node> _inputs; /** * Outputs reference Nodes that are not null and have this Node as an @@ -43,7 +43,7 @@ public abstract class Node implements OutNode { * walked in either direction. These outputs are typically used for * efficient optimizations but otherwise have no semantics meaning. */ - public final ArrayList<Node> _outputs; + public ArrayList<Node> _outputs; /** @@ -210,6 +210,7 @@ public abstract class Node implements OutNode { // Error is 'use' does not exist; ok for 'use' to be null. protected boolean delUse( Node use ) { Utils.del(_outputs, Utils.find(_outputs, use)); + moveDepsToWorklist(); // User-count and anti-dependence queries can now change. return _outputs.isEmpty(); } @@ -266,6 +267,7 @@ public abstract class Node implements OutNode { n.unlock(); int idx = Utils.find(n._inputs, this); n._inputs.set(idx,nnn); + n.moveDepsToWorklist(); // Rewiring can change a dependent query without changing type. nnn.addUse(n); } kill(); @@ -451,6 +453,14 @@ public abstract class Node implements OutNode { * 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; @@ -458,8 +468,8 @@ public abstract class Node implements OutNode { 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( Utils.find(_inputs,dep) != -1 ) return this; // No need for deps on immediate neighbors - if( Utils.find(_outputs,dep)!= -1 ) return this; + if( !forwards && Utils.find(_inputs,dep) != -1 ) return this; + if( !forwards && Utils.find(_outputs,dep)!= -1 ) return this; _deps.add(dep); return this; } @@ -591,6 +601,20 @@ public abstract class Node implements OutNode { // Does not need to be implemented in isCFG() nodes. Node copy(Node lhs, Node rhs) { throw Utils.TODO("Binary ops need to implement copy"); } + // Exact-class copy preserving operation attributes, with fresh identity + // and no edges, dependencies, or GVN membership. + public final Node copyEmpty() { + Node n; + try { n = (Node)clone(); } + catch( CloneNotSupportedException e ) { throw new AssertionError(e); } + n._nid = UNIQUE_ID++; + n._inputs = new ArrayList<>(); + n._outputs = new ArrayList<>(); + n._deps = null; + n._hash = 0; + return n; + } + // Report any post-optimize errors String err() { return null; } diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/PhiNode.java index dbe74f57..277c16d1 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -16,6 +16,10 @@ public class PhiNode extends Node { 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 @@ -65,23 +69,9 @@ public class PhiNode extends Node { if (live != null) return live; - // Pull "down" a common data op. One less op in the world. One more - // Phi, but Phis do not make code. - // Phi(op(A,B),op(Q,R),op(X,Y)) becomes - // op(Phi(A,Q,X), Phi(B,R,Y)). - Node op = in(1); - if( op.nIns()==3 && op.in(0)==null && !op.isCFG() && same_op() ) { - Node[] lhss = new Node[nIns()]; - Node[] rhss = new Node[nIns()]; - lhss[0] = rhss[0] = in(0); // Set Region - for( int i=1; i<nIns(); i++ ) { - lhss[i] = in(i).in(1); - rhss[i] = in(i).in(2); - } - Node phi_lhs = new PhiNode(_label, _declaredType,lhss).peephole(); - Node phi_rhs = new PhiNode(_label, _declaredType,rhss).peephole(); - return op.copy(phi_lhs,phi_rhs); - } + // Phi(op(a,b,...),op(c,d,...)) -> op(Phi(a,c),Phi(b,d),...). + Node down; + if( same_op() && (down=drop_same_op())!=null ) return down; // If merging Phi(N, cast(N)) - we are losing the cast JOIN effects, so just remove. if( nIns()==3 ) { @@ -111,12 +101,72 @@ public class PhiNode extends Node { } private boolean same_op() { - for( int i=2; i<nIns(); i++ ) - if( in(1).getClass() != in(i).getClass() ) - return false; + Node op = in(1); + if( op.isCFG() || op instanceof ConstantNode || op instanceof PhiNode || + op instanceof ProjNode || op instanceof NewNode || op instanceof ScopeNode || op instanceof MemMergeNode ) return false; + // A bulk Phi must split its aliases before factoring precise Stores. + if( this instanceof BulkMemPhiNode ) return false; + // Bulk splitting currently identifies parallel slices by Region/alias. + // Factoring can introduce another slice at a different memory point; + // wait until that Region's bulk partitioning has finished. + if( op instanceof MemOpNode ) + for( Node use : region()._outputs ) + if( use instanceof BulkMemPhiNode ) { + use.addDepForwards(this); + return false; + } + Node busy = null; + for( int i=1; i<nIns(); i++ ) { + Node n = in(i); + if( region().in(i).addDep(this)._type==Type.XCONTROL || + op.getClass()!=n.getClass() || n.nIns()!=op.nIns() || n.in(0)!=null || !op.eq(n) ) return false; + if( n instanceof MemOpNode mem && !mem.canDrop((MemOpNode)op,this) ) return false; + // Moving a Store must remove the old effect, not duplicate it. + // Allow one shared arm: the other arms disappear into the single + // factored operation, usually reducing the total operation count. + n.addDepForwards(this); + if( n.nOuts()>1 ) { + for( Node use : n._outputs ) + if( use!=null && use!=this ) use.addDepForwards(this); + if( n instanceof StoreNode || busy!=null ) return false; + busy=n; + } + for( int j=1; j<n.nIns(); j++ ) + if( n.in(j) instanceof ScopeNode || ((n.in(j)==null) != (op.in(j)==null)) ) return false; + } return true; } + private Node drop_same_op() { + Node op = in(1), cp = op.copyEmpty(); + cp._type = null; + cp.addDef(null); + for( int j=1; j<op.nIns(); j++ ) { + Node x = op.in(j); + boolean different = false; + for( int i=2; i<nIns(); i++ ) + if( in(i).in(j)!=x ) different=true; + if( different ) { + Node[] ins = new Node[nIns()]; + ins[0] = region(); + Type t = Type.TOP; + for( int i=1; i<nIns(); i++ ) { + ins[i] = in(i).in(j); + t = t.meet(ins[i]._type); + } + PhiNode phi = j==1 && op instanceof MemOpNode mem + ? new MemPhiNode(_label,mem._alias,ins) + : PhiNode.make(_label,t.glb(),ins); + x = phi.peephole(); + } + cp.addDef(x); + } + // Factoring must not widen the result (e.g. correlated And operands). + if( cp.compute().isa(compute()) ) return cp; + cp.kill(); + return null; + } + /** * If only single unique input, return it */ diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/RegionNode.java index fb934511..d7e934d7 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/RegionNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/RegionNode.java @@ -68,7 +68,7 @@ public class RegionNode extends CFGNode { 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.addDepForwards(this).in(0)==p2.addDepForwards(this).in(0) && p1.in(0) instanceof IfNode iff ) return iff.ctrl(); diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/ReturnNode.java index 444b76a8..725c704f 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -4,32 +4,17 @@ 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 all preceding memory effects and the return value alive: {ctrl, $mem, value}. */ public class ReturnNode extends CFGNode { 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 - if( scope != null ) { - String[] names = scope.reverseNames(); - for (String name: names) { - if (!name.equals("$ctrl") && name.startsWith("$")) - addDef(scope.lookup(name)); - } - } + // A synthetic never-taken loop exit has no source scope. + super(ctrl, scope == null ? null : scope.lookup("$mem"), data); } public Node ctrl() { return in(0); } - public Node expr() { return in(1); } + public Node mem () { return in(1); } + public Node expr() { return in(2); } @Override public String label() { return "Return"; } @@ -43,7 +28,8 @@ public class ReturnNode extends CFGNode { @Override public Type compute() { - return TypeTuple.make(ctrl()._type,expr()._type); + // Return exposes the complete memory state. + return TypeTuple.make(ctrl()._type,TypeMem.BOT,expr()._type); } @Override diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/ScopeNode.java index 455f4b3a..b931e862 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -129,7 +129,7 @@ public class ScopeNode extends Node { // Set real Phi in the loop head // The phi takes its one input (no backedge yet) from a recursive // lookup, which might have insert a Phi in every loop nest. - : loop.setDef(idx,new PhiNode(name, lookupDeclaredType(name),loop.ctrl(),loop.update(name,null,nestingLevel),null).peephole()); + : 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 @@ -207,7 +207,7 @@ public class ScopeNode extends Node { if( in(i) != that.in(i) ) // No need for redundant Phis // If we are in lazy phi mode we need to a lookup // by name as it will trigger a phi creation - setDef(i, new PhiNode(ns[i], this.lookupDeclaredType(ns[i]), r, this.lookup(ns[i]), that.lookup(ns[i])).peephole()); + 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(); diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/StartNode.java index 5f205ffd..2cedf0f6 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ b/chapter12/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -1,59 +1,17 @@ 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; import java.util.HashSet; -/** - * 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. - */ +/** Start supplies control, whole memory, and the argument, in that order. */ public class StartNode extends CFGNode implements 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; - + private final TypeTuple _args; 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 diff --git a/chapter12/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter12/src/main/java/com/seaofnodes/simple/node/StoreNode.java index 5b30b3ec..b816762b 100644 --- a/chapter12/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter12/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/chapter12/src/test/java/com/seaofnodes/simple/Chapter04Test.java b/chapter12/src/test/java/com/seaofnodes/simple/Chapter04Test.java index a3109ff7..14d31245 100644 --- a/chapter12/src/test/java/com/seaofnodes/simple/Chapter04Test.java +++ b/chapter12/src/test/java/com/seaofnodes/simple/Chapter04Test.java @@ -84,7 +84,10 @@ public class Chapter04Test { StopNode stop = parser.parse(); ReturnNode ret = stop.ret(); assertTrue(ret.in(0) instanceof CProjNode); - assertTrue(ret.in(1) instanceof ProjNode); + assertTrue(ret.in(1) instanceof ProjNode); + assertEquals(1, ((ProjNode)ret.in(1))._idx); + assertTrue(ret.in(2) instanceof ProjNode); + assertEquals(2, ((ProjNode)ret.in(2))._idx); } @Test diff --git a/chapter12/src/test/java/com/seaofnodes/simple/Chapter05Test.java b/chapter12/src/test/java/com/seaofnodes/simple/Chapter05Test.java index b01ffe0a..7beeb4d9 100644 --- a/chapter12/src/test/java/com/seaofnodes/simple/Chapter05Test.java +++ b/chapter12/src/test/java/com/seaofnodes/simple/Chapter05Test.java @@ -22,7 +22,7 @@ else { return a;"""); StopNode ret = parser.parse().iterate(); - assertEquals("return Phi(Region20,(arg+2),(arg-3));", ret.toString()); + assertEquals("return Phi(Region21,(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(Region18,4,3);", ret.toString()); + assertEquals("return Phi(Region19,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(Region23,2,3));", ret.toString()); + assertEquals("return ((arg*2)+Phi(Region24,2,3));", ret.toString()); } @Test @@ -80,7 +80,7 @@ else a=b+1; return a+b;"""); StopNode ret = parser.parse().iterate(); - assertEquals("return ((Phi(Region34,(arg*2),arg)+arg)+Phi(Region,4,5));", ret.toString()); + assertEquals("return ((Phi(Region35,(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(Region38,Phi(Region24,2,3),Phi(Region36,4,5));", stop.toString()); + assertEquals("return Phi(Region39,Phi(Region25,2,3),Phi(Region37,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(Region15,1,0))+Phi(Region30,2,0));", stop.toString()); + assertEquals("return ((arg+Phi(Region16,1,0))+Phi(Region31,2,0));", stop.toString()); } @Test @@ -130,7 +130,7 @@ if( arg==1 ) } return a;"""); StopNode ret = parser.parse().iterate(); - assertEquals("return (arg==Phi(Region18,3,2));", ret.toString()); + assertEquals("return (arg==Phi(Region19,3,2));", ret.toString()); } @Test diff --git a/chapter12/src/test/java/com/seaofnodes/simple/Chapter06Test.java b/chapter12/src/test/java/com/seaofnodes/simple/Chapter06Test.java index 7b11bc35..df0f2848 100644 --- a/chapter12/src/test/java/com/seaofnodes/simple/Chapter06Test.java +++ b/chapter12/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(Region14,2,1))<3);", stop.toString()); + assertEquals("return ((arg<Phi(Region15,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(Region39,42,5);", stop.toString()); + assertEquals("return Phi(Region40,42,5);", stop.toString()); } @Test @@ -84,7 +84,7 @@ else b=5; return b;""", TypeInteger.BOT); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region32,2,5);", stop.toString()); + assertEquals("return Phi(Region33,2,5);", stop.toString()); } @Test @@ -144,7 +144,7 @@ else return a; """, TypeInteger.BOT); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region43,3,Phi(Region41,4,5));", stop.toString()); + assertEquals("return Phi(Region44,3,Phi(Region42,4,5));", stop.toString()); } @Test @@ -200,7 +200,7 @@ if( arg ) { return a+b; """); StopNode ret = parser.parse().iterate(); - assertEquals("return Phi(Region26,4,1);", ret.toString()); + assertEquals("return Phi(Region27,4,1);", ret.toString()); } @@ -251,7 +251,7 @@ if( arg ) { return a+b+c; """); StopNode ret = parser.parse().iterate(); - assertEquals("return (Phi(Region38,Phi(Region25,2,3),0)+Phi(Region,3,1));", ret.toString()); + assertEquals("return (Phi(Region39,Phi(Region26,2,3),0)+Phi(Region,3,1));", ret.toString()); } diff --git a/chapter12/src/test/java/com/seaofnodes/simple/Chapter07Test.java b/chapter12/src/test/java/com/seaofnodes/simple/Chapter07Test.java index 177b54d1..222982e7 100644 --- a/chapter12/src/test/java/com/seaofnodes/simple/Chapter07Test.java +++ b/chapter12/src/test/java/com/seaofnodes/simple/Chapter07Test.java @@ -18,7 +18,7 @@ public class Chapter07Test { return arg; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop8,arg,(Phi_arg+1));", stop.toString()); + assertEquals("return Phi(Loop9,arg,(Phi_arg+1));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -35,7 +35,7 @@ if(arg){}else{ return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region26,1,Phi(Loop15,1,(Phi_a+1)));", stop.toString()); + assertEquals("return Phi(Region27,1,Phi(Loop16,1,(Phi_a+1)));", stop.toString()); } @Test @@ -55,7 +55,7 @@ while(i < arg) { return sum; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,0,Phi(Loop22,Phi_sum,(Phi_sum+Phi(Loop,0,(Phi_j+1)))));", stop.toString()); + assertEquals("return Phi(Loop11,0,Phi(Loop23,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(Loop10,2,Phi(Region29,Phi_b,4));", stop.toString()); + assertEquals("return Phi(Loop11,2,Phi(Region30,Phi_b,4));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -90,7 +90,7 @@ while(a < 10) { return b; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,2,(Phi(Region29,Phi_b,4)+1));", stop.toString()); + assertEquals("return Phi(Loop11,2,(Phi(Region30,Phi_b,4)+1));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -107,7 +107,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop9,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -123,7 +123,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -136,7 +136,7 @@ while(arg) a = 2; return a; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop9,1,2);", stop.toString()); + assertEquals("return Phi(Loop10,1,2);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -149,7 +149,7 @@ while(arg) a = 2; return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,1,2);", stop.toString()); + assertEquals("return Phi(Loop10,1,2);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -165,7 +165,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop9,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -181,7 +181,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -198,7 +198,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop11,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -215,7 +215,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop11,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } diff --git a/chapter12/src/test/java/com/seaofnodes/simple/Chapter08Test.java b/chapter12/src/test/java/com/seaofnodes/simple/Chapter08Test.java index 6aa74125..8c644977 100644 --- a/chapter12/src/test/java/com/seaofnodes/simple/Chapter08Test.java +++ b/chapter12/src/test/java/com/seaofnodes/simple/Chapter08Test.java @@ -23,7 +23,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region36,Phi(Region26,Phi(Loop8,arg,(Phi_arg+1)),Add),Add);", stop.toString()); + assertEquals("return Phi(Region37,Phi(Region27,Phi(Loop9,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)); @@ -46,7 +46,7 @@ while(arg < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,1,Phi(Region41,Phi_a,(Phi_a+1)));", stop.toString()); + assertEquals("return Phi(Loop10,1,Phi(Region42,Phi_a,(Phi_a+1)));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -65,7 +65,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region34,Phi(Loop8,arg,(Phi_arg+1)),Add);", stop.toString()); + assertEquals("return Phi(Region35,Phi(Loop9,arg,(Phi_arg+1)),Add);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof RegionNode); } @@ -81,7 +81,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region26,Phi(Loop8,arg,(Phi_arg+1)),Add);", stop.toString()); + assertEquals("return Phi(Region27,Phi(Loop9,arg,(Phi_arg+1)),Add);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof RegionNode); } @@ -100,7 +100,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop8,arg,(Phi_arg+1));", stop.toString()); + assertEquals("return Phi(Loop9,arg,(Phi_arg+1));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -117,7 +117,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop8,arg,(Phi_arg+1));", stop.toString()); + assertEquals("return Phi(Loop9,arg,(Phi_arg+1));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -185,7 +185,7 @@ while(arg < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region29,Phi(Loop9,1,(Phi_a+1)),Add);", stop.toString()); + assertEquals("return Phi(Region30,Phi(Loop10,1,(Phi_a+1)),Add);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof RegionNode); } @@ -201,7 +201,7 @@ while(1) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return (Phi(Loop9,1,Add)+1);", stop.toString()); + assertEquals("return (Phi(Loop10,1,Add)+1);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode prj && prj._idx==1 ); } diff --git a/chapter12/src/test/java/com/seaofnodes/simple/Chapter09Test.java b/chapter12/src/test/java/com/seaofnodes/simple/Chapter09Test.java index 64bbf245..49ee75a2 100644 --- a/chapter12/src/test/java/com/seaofnodes/simple/Chapter09Test.java +++ b/chapter12/src/test/java/com/seaofnodes/simple/Chapter09Test.java @@ -69,7 +69,7 @@ while (arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,arg,(Phi_arg+2));", stop.toString()); + assertEquals("return Phi(Loop10,arg,(Phi_arg+2));", stop.toString()); Assert.assertEquals(11L, Evaluator.evaluate(stop, 1)); } @@ -86,7 +86,7 @@ while (arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,arg,(Phi_arg+4));", stop.toString()); + assertEquals("return Phi(Loop11,arg,(Phi_arg+4));", stop.toString()); Assert.assertEquals(13L, Evaluator.evaluate(stop, 1)); } @@ -115,7 +115,7 @@ while (arg) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop16,arg,(Phi_arg+1));", stop.toString()); + assertEquals("return Phi(Loop17,arg,(Phi_arg+1));", stop.toString()); } @Test @@ -185,7 +185,7 @@ while(arg) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop11,0,(-(Phi_a+3)));", stop.toString()); + assertEquals("return Phi(Loop12,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(Loop8,arg,(Phi_arg-1));", stop.toString()); + assertEquals("return Phi(Loop9,arg,(Phi_arg-1));", stop.toString()); } @Test diff --git a/chapter12/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter12/src/test/java/com/seaofnodes/simple/Chapter10Test.java index 9ac1f1e6..38aceb84 100644 --- a/chapter12/src/test/java/com/seaofnodes/simple/Chapter10Test.java +++ b/chapter12/src/test/java/com/seaofnodes/simple/Chapter10Test.java @@ -3,6 +3,10 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.node.Node; import com.seaofnodes.simple.node.StopNode; import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.node.LoadNode; +import com.seaofnodes.simple.node.StoreNode; +import com.seaofnodes.simple.evaluator.Evaluator; +import java.util.BitSet; import org.junit.Test; import java.util.Arrays; @@ -165,7 +169,7 @@ while (arg) { return bar.a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop11,0,(Phi_a+2));", stop.toString()); + assertEquals("return Phi(Loop12,0,(Phi_a+2));", stop.toString()); } @Test @@ -217,7 +221,7 @@ if( bar ) bar.a = 1; return bar; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region16,null,new Bar);", stop.toString()); + assertEquals("return Phi(Region17,null,new Bar);", stop.toString()); } @Test @@ -233,7 +237,7 @@ else bar.a = 1; return rez; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region33,4,3);", stop.toString()); + assertEquals("return Phi(Region35,4,3);", stop.toString()); } @Test @@ -287,7 +291,7 @@ while( i.x < i.len ) { return sum; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop15,0,(Phi(Loop,0,(Phi_x+1))+Phi_sum));", stop.toString()); + assertEquals("return Phi(Loop17,0,(Phi(Loop,0,(Phi_x+1))+Phi_sum));", stop.toString()); } @@ -308,7 +312,7 @@ while(arg) { return ret; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop11,new s0,Phi(Region31,new s0,Phi_ret));", stop.toString()); + assertEquals("return Phi(Loop12,new s0,Phi(Region34,new s0,Phi_ret));", stop.toString()); } @Test @@ -325,7 +329,7 @@ while(arg) { return ret; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop13,new s0,Phi(Region32,new s0,Phi_ret));", stop.toString()); + assertEquals("return Phi(Loop15,new s0,Phi(Region35,new s0,Phi_ret));", stop.toString()); } @@ -342,7 +346,7 @@ while(arg < 10) { return ret; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop11,new s0,Phi(Region30,new s0,Phi_ret));", stop.toString()); + assertEquals("return Phi(Loop12,new s0,Phi(Region32,new s0,Phi_ret));", stop.toString()); } @Test @@ -460,4 +464,121 @@ 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)); + } + + 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()); + } + } + + @Test public void testKeepLoadsAtMerge() { + StopNode stop = new Parser(""" + struct S { int x; } + S a = new S; S b = new S; + a.x = arg; b.x = arg+1; + S p = a; S q = b; + if (arg<0) { p=b; q=a; } + int v; + if (arg>1) v=p.x; else v=q.x; + return v; + """).parse().iterate(); + // The one-step safety check stops at the memory aggregate. + assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet())); + assertEquals(-1L,Evaluator.evaluate(stop,-1)); + assertEquals(1L,Evaluator.evaluate(stop,0)); + assertEquals(3L,Evaluator.evaluate(stop,3)); + } + + @Test public void testDropStores() { + StopNode stop = new Parser(""" + struct S { int x; } + S s = new S; + if (arg) s.x=arg+1; else s.x=arg+2; + return s; + """).parse().iterate(); + // These chapters still bind struct Stores to branch control. + assertEquals(3,countMemoryNodes(stop,StoreNode.class,new BitSet())); + assertEquals(2L,((Evaluator.Obj)Evaluator.evaluate(stop,0)).fields()[0]); + assertEquals(4L,((Evaluator.Obj)Evaluator.evaluate(stop,3)).fields()[0]); + } + + @Test public void testKeepReadsBeforeWrites() { + StopNode stop = new Parser(""" + struct S { int x; int y; } + S a = new S; S b = new S; + a.x=arg; b.x=arg+1; + S p=a; S q=b; + if (arg<0) { p=b; q=a; } + int v; + if (arg>1) { v=p.x; p.x=41; p.y=5; } + else { v=q.x; q.x=42; } + return v; + """).parse().iterate(); + assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet())); + assertEquals(-1L,Evaluator.evaluate(stop,-1)); + assertEquals(1L,Evaluator.evaluate(stop,0)); + assertEquals(3L,Evaluator.evaluate(stop,3)); + } + + private static int countMemoryNodes(Node n, Class<?> kind, BitSet seen) { + if (n==null || seen.get(n._nid)) return 0; + seen.set(n._nid); + int cnt=kind.isInstance(n) ? 1 : 0; + for (Node def : n._inputs) cnt+=countMemoryNodes(def,kind,seen); + return cnt; + } } diff --git a/chapter12/src/test/java/com/seaofnodes/simple/Chapter10bTest.java b/chapter12/src/test/java/com/seaofnodes/simple/Chapter10bTest.java new file mode 100644 index 00000000..6f094cfb --- /dev/null +++ b/chapter12/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/chapter12/src/test/java/com/seaofnodes/simple/Chapter11Test.java b/chapter12/src/test/java/com/seaofnodes/simple/Chapter11Test.java index 5a60c8b4..0e492591 100644 --- a/chapter12/src/test/java/com/seaofnodes/simple/Chapter11Test.java +++ b/chapter12/src/test/java/com/seaofnodes/simple/Chapter11Test.java @@ -325,7 +325,7 @@ while( prime <= arg ) { return primeCount; """); StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ return 0; return Phi(Loop19,1,Phi(Region81,Phi_primeCount,Phi(Region76,(Phi_primeCount+1),Phi_primeCount))); ]", stop.toString()); + assertEquals("Stop[ return 0; return Phi(Loop20,1,Phi(Region82,Phi_primeCount,Phi(Region77,(Phi_primeCount+1),Phi_primeCount))); ]", stop.toString()); assertEquals(0L, Evaluator.evaluate(stop, 1)); // No primes 1 or below assertEquals(1L, Evaluator.evaluate(stop, 2)); // 2 assertEquals(2L, Evaluator.evaluate(stop, 3)); // 2, 3 @@ -367,7 +367,7 @@ if (arg) { return i; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region31,.f,0);", stop.toString()); + assertEquals("return Phi(Region34,.f,0);", stop.toString()); } @Test @@ -473,7 +473,7 @@ while(arg) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop13,arg,Phi(Region35,.f,0));", stop.toString()); + assertEquals("return Phi(Loop15,arg,Phi(Region39,.f,0));", stop.toString()); } @Test @@ -524,7 +524,7 @@ else if (arg == 1) s.f = 1; return s.f; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region35,1,Phi(Region33,1,0));", stop.toString()); + assertEquals("return Phi(Region41,1,Phi(Region36,1,0));", stop.toString()); assertEquals(1L, Evaluator.evaluate(stop, 0)); assertEquals(1L, Evaluator.evaluate(stop, 1)); assertEquals(0L, Evaluator.evaluate(stop, 2)); diff --git a/chapter12/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter12/src/test/java/com/seaofnodes/simple/Chapter12Test.java index 9da4a4b8..e577c8c3 100644 --- a/chapter12/src/test/java/com/seaofnodes/simple/Chapter12Test.java +++ b/chapter12/src/test/java/com/seaofnodes/simple/Chapter12Test.java @@ -65,7 +65,7 @@ while( 1 ) { return guess; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,(flt)arg,(((ToFloat/Phi_guess)+Phi_guess)/2.0));", stop.toString()); + assertEquals("return Phi(Loop10,(flt)arg,(((ToFloat/Phi_guess)+Phi_guess)/2.0));", stop.toString()); assertEquals(3.0, Evaluator.evaluate(stop, 9)); assertEquals(1.414213562373095, Evaluator.evaluate(stop, 2)); } diff --git a/chapter12/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/chapter12/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java index 3512d2e9..40a2f78d 100644 --- a/chapter12/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java +++ b/chapter12/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java @@ -154,6 +154,7 @@ public class Evaluator { case ToFloatNode cast -> (double)vall(cast.in(1)); case LoadNode load -> load(load); case StoreNode store -> store(store); + case MemMergeNode merge -> MEMORY; case NewNode alloc -> alloc(alloc); case CProjNode cproj -> ((Object[])val(cproj.ctrl()))[cproj._idx]; case ProjNode proj -> ((Object[])val( proj.in(0) ))[ proj._idx]; @@ -167,8 +168,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; @@ -179,7 +180,7 @@ public class Evaluator { case null: return Status.FALLTHROUGH; case ReturnNode ret: - return val(ret.in(1)); + return val(ret.expr()); case IfNode ifn: block = block.next()[isTrue(val(ifn.in(1))) ? 0 : 1]; if (block == null) return Status.FALLTHROUGH; diff --git a/chapter12/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java b/chapter12/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java index 8b86d67a..d16f4300 100644 --- a/chapter12/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java +++ b/chapter12/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java @@ -250,6 +250,12 @@ public class Scheduler { // 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++) { @@ -271,7 +277,7 @@ public class Scheduler { // 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) od(out).ifPresent(this::decUsers); + if (out instanceof LoadNode l && l._alias==s._alias) od(out).ifPresent(this::decUsers); } } } @@ -469,7 +475,8 @@ public class Scheduler { var data = mem.pop(); node = data.node; for(var out:node.in(1)._outputs) { - if (out instanceof LoadNode) od(out).ifPresent(d->d.users++); + if (out instanceof LoadNode l && l._alias==((StoreNode)node)._alias) + od(out).ifPresent(d->d.users++); } } } diff --git a/chapter13/README.md b/chapter13/README.md index c54bfa70..1c492468 100644 --- a/chapter13/README.md +++ b/chapter13/README.md @@ -1,5 +1,10 @@ # Chapter 13: References +Memory retains one `$mem` binding and lazy partitions. Typed initialization +stores now include each field's declared type and initialization flag, while +`MemMerge` preserves the other fields. Start and Return keep control in slot 0 +and memory in slot 1. + # Table of Contents 1. [References](#references) @@ -64,21 +69,23 @@ p.tree.la = null; // Accessing unknown field 'la' from 'null' It turns out `p.tree` is `null`, so we can't access the field `la` from it. ```java // name = "tree" -// memAlias(alias) = StoreNode +// mem() is the current aggregate; its tree alias selects the initializing Store. // in(3) = {ConstantNode@1651} "null" -return parsePostfix(new LoadNode(name, alias, declaredType.glb(), memAlias(alias), expr).peephole()); +return parsePostfix(new LoadNode(name, alias, declaredType.glb(), mem(), expr).peephole()); ``` `LoadNode::idealize` -In LoadNode idealize we return st.val(). +LoadNode first selects its alias through any MemMerge, then returns `st.val()` +when it finds a matching Store to the same object. ```java // 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 ) { // Same object and field assert _name.equals(st._name); // Equiv class aliasing is perfect return st.val(); // // <<-- {ConstantNode@1651} "null" } ``` -`mem(): ` refers to `memAlias(alias)`. Therefore, in the next iteration, we will end up with: +After alias selection, the Load's `mem()` refers to that initializing Store. +In the next iteration, we will end up with: ```java ... @@ -98,9 +105,9 @@ When we are creating the struct, we set up the initial value to Null: ```java private Node newStruct(TypeStruct obj) { Node n = new NewNode(TypeMemPtr.make(obj), ctrl()).peephole().keep(); - 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, field._type, ctrl(), memAlias(alias), n, new ConstantNode(field._type.makeInit()).peephole(),true).peephole()); + store(field._fname,alias,field._type,n,new ConstantNode(field._type.makeInit()).peephole(),true); alias++; } return n.unkeep(); @@ -114,7 +121,7 @@ for( Field field : obj._fields ) { // field._type = *FamilyTree? // REMEMBER: // @Override public TypeMemPtr makeInit() { return NULLPTR; } - memAlias(..., new ConstantNode(field._type.makeInit()).peephole(),true).peephole()); + store(field._fname,alias,field._type,n,new ConstantNode(field._type.makeInit()).peephole(),true); alias++; } ``` @@ -256,4 +263,4 @@ for( ; lca != early.idom(); lca = lca.idom() ) if( better(lca,best) ) best = lca; assert !(best instanceof IfNode); -```
\ No newline at end of file +``` diff --git a/chapter13/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java b/chapter13/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java index a270f026..78a0dcde 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java @@ -149,7 +149,7 @@ public abstract class GlobalCodeMotion { // Loads need their memory inputs' uses also done if( n instanceof LoadNode ld ) for( Node memuse : ld.mem()._outputs ) - if( memuse._type instanceof TypeMem && late[memuse._nid]==null ) + if( antiUse(ld,memuse) && late[memuse._nid]==null ) continue outer; // All uses done, schedule @@ -219,6 +219,17 @@ public abstract class GlobalCodeMotion { best instanceof IfNode; } + // Only a store or memory Phi covering this alias can constrain a load. + // MemMerge packages slices without overwriting them. + private static boolean antiUse(LoadNode load, Node use) { + return switch( use ) { + case StoreNode st -> st._alias==load._alias; + case MemPhiNode phi -> phi._alias==load._alias; + case BulkMemPhiNode phi -> !phi.isSplit(load._alias); + default -> false; + }; + } + private static CFGNode find_anti_dep(CFGNode lca, LoadNode load, CFGNode early, CFGNode[] late, int[] anti) { // We could skip final-field loads here. // Walk LCA->early, flagging Load's block location choices @@ -226,6 +237,7 @@ public abstract class GlobalCodeMotion { anti[cfg._nid] = load._nid; // Walk load->mem uses, looking for Stores causing an anti-dep for( Node mem : load.mem()._outputs ) { + if( !antiUse(load,mem) ) continue; switch( mem ) { case StoreNode st: assert late[st._nid]!=null; @@ -238,9 +250,6 @@ public abstract class GlobalCodeMotion { if( phi.in(i)==load.mem() ) lca = anti_dep(load,phi.region().cfg(i),load.mem().cfg0(),lca,null,anti); break; - case LoadNode ld: break; // Loads do not cause anti-deps on other loads - case ReturnNode ret: break; // Load must already be ahead of Return - case NeverNode never: break; default: throw Utils.TODO(); } } diff --git a/chapter13/src/main/java/com/seaofnodes/simple/Parser.java b/chapter13/src/main/java/com/seaofnodes/simple/Parser.java index b3bd1ada..cc668f18 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/Parser.java @@ -36,6 +36,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; + // Debugger Printing. public static boolean SCHEDULED; // True if debug printer can use schedule info @@ -99,7 +103,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); ZERO = new ConstantNode(TypeInteger.constant(0)).peephole().keep(); XCTRL= new XCtrlNode().peephole().keep(); @@ -126,7 +130,8 @@ public class Parser { // Enter a new scope for the initial control and arguments _scope.push(); _scope.define(ScopeNode.CTRL, Type.CONTROL , new CProjNode(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(); if( ctrl()._type==Type.CONTROL ) STOP.addReturn(new ReturnNode(ctrl(), new ConstantNode(TypeInteger.constant(0)).peephole(), _scope).peephole()); @@ -214,8 +219,9 @@ public class Parser { require("}"); // Build and install the TypeStruct TypeStruct ts = TypeStruct.make(typeName, fields); - TYPES.put(typeName, ts); - START.addMemProj(ts, _scope); // Insert memory edges + TYPES.put(typeName, ts); // Insert the struct name in the collection of all struct names + _aliases.put(typeName,_alias); + _alias += ts._fields.length; return null; } @@ -596,21 +602,24 @@ public class Parser { */ private Node newStruct(TypeStruct obj) { Node n = new NewNode(TypeMemPtr.make(obj), ctrl()).peephole().keep(); - 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, field._type, ctrl(), memAlias(alias), n, new ConstantNode(field._type.makeInit()).peephole(),true).peephole()); + store(field._fname,alias,field._type,n,new ConstantNode(field._type.makeInit()).peephole(),true); 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, Type glb, Node ptr, Node val, boolean init) { + Node prior = mem().keep(); + Node st = new StoreNode(name,alias,glb,ctrl(),prior,ptr,val,init).peephole(); + mem(new MemMergeNode(prior,alias,st).peephole()); + prior.unkeep(); + } /** * Parse postfix expression. For now this is just a field @@ -632,7 +641,7 @@ public class Parser { TypeStruct base = (TypeStruct)TYPES.get(ptr._obj._name); int idx = base==null ? -1 : base.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 @@ -640,13 +649,13 @@ public class Parser { else { Node val = parseExpression(); Type glb = base._fields[idx]._type; - memAlias(alias, new StoreNode(name, alias,glb, ctrl(), memAlias(alias), expr, val, false).peephole()); + store(name,alias,glb,expr,val,false); return expr; // "obj.a = expr" returns the expression while updating memory } } Type declaredType = base._fields[idx]._type; - return parsePostfix(new LoadNode(name, alias, declaredType.glb(), memAlias(alias), expr).peephole()); + return parsePostfix(new LoadNode(name, alias, declaredType.glb(), mem(), expr).peephole()); } /** diff --git a/chapter13/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java new file mode 100644 index 00000000..8d512f21 --- /dev/null +++ b/chapter13/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/chapter13/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/LoadNode.java index ff468b72..f1d4884a 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter13/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 java.util.BitSet; @@ -22,6 +23,24 @@ public class LoadNode extends MemOpNode { super(name, alias, glb, null, memSlice, memPtr); } + @Override boolean canDrop(MemOpNode other, Node dep) { + return super.canDrop(other,dep) && !clobbered(dep); + } + + // Check only immediate memory users. Stores clobber memory; Phis and + // aggregates might lead to a clobber, so stop rather than search further. + private boolean clobbered(Node dep) { + Node mem = mem(); + mem.addDepForwards(dep); + for( Node use : mem._outputs ) { + if( use==null ) continue; + use.addDepForwards(dep); + if( use instanceof StoreNode || use instanceof PhiNode || + use instanceof MemMergeNode ) return true; + } + return false; + } + @Override public String label() { return _name; } @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(".").append(_name); } @@ -33,10 +52,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 _name.equals(st._name); // Equiv class aliasing is perfect return st.val(); } @@ -48,7 +72,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. @@ -69,6 +93,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/chapter13/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java new file mode 100644 index 00000000..d7fed402 --- /dev/null +++ b/chapter13/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/chapter13/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/MemOpNode.java index 95319c24..7c6afb0c 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/MemOpNode.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/node/MemOpNode.java @@ -24,6 +24,13 @@ public abstract class MemOpNode extends Node { public Node mem() { return in(1); } public Node ptr() { return in(2); } + // Extra conditions for factoring matching memory operations through a Phi. + // The caller has already checked the opcode, input shape and control. + boolean canDrop(MemOpNode other, Node dep) { + ptr().addDep(dep); + return _alias==other._alias && _declaredType==other._declaredType && err()==null; + } + @Override boolean eq(Node n) { MemOpNode mem = (MemOpNode)n; // Invariant diff --git a/chapter13/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java new file mode 100644 index 00000000..c806bede --- /dev/null +++ b/chapter13/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/chapter13/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter13/src/main/java/com/seaofnodes/simple/node/Node.java index ce4b2d34..2082168a 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/node/Node.java @@ -16,14 +16,14 @@ import java.util.function.Function; * The Node class provides common functionality used by all subtypes. * Subtypes of Node specialize by overriding methods. */ -public abstract class Node implements OutNode { +public abstract class Node implements OutNode, Cloneable { /** * 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; + public int _nid; /** * Inputs to the node. These are use-def references to Nodes. @@ -32,7 +32,7 @@ public abstract class Node implements OutNode { * 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; + public ArrayList<Node> _inputs; /** * Outputs reference Nodes that are not null and have this Node as an @@ -43,7 +43,7 @@ public abstract class Node implements OutNode { * walked in either direction. These outputs are typically used for * efficient optimizations but otherwise have no semantics meaning. */ - public final ArrayList<Node> _outputs; + public ArrayList<Node> _outputs; /** @@ -210,6 +210,7 @@ public abstract class Node implements OutNode { // Error is 'use' does not exist; ok for 'use' to be null. protected boolean delUse( Node use ) { Utils.del(_outputs, Utils.find(_outputs, use)); + moveDepsToWorklist(); // User-count and anti-dependence queries can now change. return _outputs.isEmpty(); } @@ -266,6 +267,7 @@ public abstract class Node implements OutNode { n.unlock(); int idx = Utils.find(n._inputs, this); n._inputs.set(idx,nnn); + n.moveDepsToWorklist(); // Rewiring can change a dependent query without changing type. nnn.addUse(n); } kill(); @@ -451,6 +453,14 @@ public abstract class Node implements OutNode { * 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; @@ -458,8 +468,8 @@ public abstract class Node implements OutNode { 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( Utils.find(_inputs,dep) != -1 ) return this; // No need for deps on immediate neighbors - if( Utils.find(_outputs,dep)!= -1 ) return this; + if( !forwards && Utils.find(_inputs,dep) != -1 ) return this; + if( !forwards && Utils.find(_outputs,dep)!= -1 ) return this; _deps.add(dep); return this; } @@ -591,6 +601,20 @@ public abstract class Node implements OutNode { // Does not need to be implemented in isCFG() nodes. Node copy(Node lhs, Node rhs) { throw Utils.TODO("Binary ops need to implement copy"); } + // Exact-class copy preserving operation attributes, with fresh identity + // and no edges, dependencies, or GVN membership. + public final Node copyEmpty() { + Node n; + try { n = (Node)clone(); } + catch( CloneNotSupportedException e ) { throw new AssertionError(e); } + n._nid = UNIQUE_ID++; + n._inputs = new ArrayList<>(); + n._outputs = new ArrayList<>(); + n._deps = null; + n._hash = 0; + return n; + } + // Report any post-optimize errors String err() { return null; } diff --git a/chapter13/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/PhiNode.java index dbe74f57..277c16d1 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -16,6 +16,10 @@ public class PhiNode extends Node { 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 @@ -65,23 +69,9 @@ public class PhiNode extends Node { if (live != null) return live; - // Pull "down" a common data op. One less op in the world. One more - // Phi, but Phis do not make code. - // Phi(op(A,B),op(Q,R),op(X,Y)) becomes - // op(Phi(A,Q,X), Phi(B,R,Y)). - Node op = in(1); - if( op.nIns()==3 && op.in(0)==null && !op.isCFG() && same_op() ) { - Node[] lhss = new Node[nIns()]; - Node[] rhss = new Node[nIns()]; - lhss[0] = rhss[0] = in(0); // Set Region - for( int i=1; i<nIns(); i++ ) { - lhss[i] = in(i).in(1); - rhss[i] = in(i).in(2); - } - Node phi_lhs = new PhiNode(_label, _declaredType,lhss).peephole(); - Node phi_rhs = new PhiNode(_label, _declaredType,rhss).peephole(); - return op.copy(phi_lhs,phi_rhs); - } + // Phi(op(a,b,...),op(c,d,...)) -> op(Phi(a,c),Phi(b,d),...). + Node down; + if( same_op() && (down=drop_same_op())!=null ) return down; // If merging Phi(N, cast(N)) - we are losing the cast JOIN effects, so just remove. if( nIns()==3 ) { @@ -111,12 +101,72 @@ public class PhiNode extends Node { } private boolean same_op() { - for( int i=2; i<nIns(); i++ ) - if( in(1).getClass() != in(i).getClass() ) - return false; + Node op = in(1); + if( op.isCFG() || op instanceof ConstantNode || op instanceof PhiNode || + op instanceof ProjNode || op instanceof NewNode || op instanceof ScopeNode || op instanceof MemMergeNode ) return false; + // A bulk Phi must split its aliases before factoring precise Stores. + if( this instanceof BulkMemPhiNode ) return false; + // Bulk splitting currently identifies parallel slices by Region/alias. + // Factoring can introduce another slice at a different memory point; + // wait until that Region's bulk partitioning has finished. + if( op instanceof MemOpNode ) + for( Node use : region()._outputs ) + if( use instanceof BulkMemPhiNode ) { + use.addDepForwards(this); + return false; + } + Node busy = null; + for( int i=1; i<nIns(); i++ ) { + Node n = in(i); + if( region().in(i).addDep(this)._type==Type.XCONTROL || + op.getClass()!=n.getClass() || n.nIns()!=op.nIns() || n.in(0)!=null || !op.eq(n) ) return false; + if( n instanceof MemOpNode mem && !mem.canDrop((MemOpNode)op,this) ) return false; + // Moving a Store must remove the old effect, not duplicate it. + // Allow one shared arm: the other arms disappear into the single + // factored operation, usually reducing the total operation count. + n.addDepForwards(this); + if( n.nOuts()>1 ) { + for( Node use : n._outputs ) + if( use!=null && use!=this ) use.addDepForwards(this); + if( n instanceof StoreNode || busy!=null ) return false; + busy=n; + } + for( int j=1; j<n.nIns(); j++ ) + if( n.in(j) instanceof ScopeNode || ((n.in(j)==null) != (op.in(j)==null)) ) return false; + } return true; } + private Node drop_same_op() { + Node op = in(1), cp = op.copyEmpty(); + cp._type = null; + cp.addDef(null); + for( int j=1; j<op.nIns(); j++ ) { + Node x = op.in(j); + boolean different = false; + for( int i=2; i<nIns(); i++ ) + if( in(i).in(j)!=x ) different=true; + if( different ) { + Node[] ins = new Node[nIns()]; + ins[0] = region(); + Type t = Type.TOP; + for( int i=1; i<nIns(); i++ ) { + ins[i] = in(i).in(j); + t = t.meet(ins[i]._type); + } + PhiNode phi = j==1 && op instanceof MemOpNode mem + ? new MemPhiNode(_label,mem._alias,ins) + : PhiNode.make(_label,t.glb(),ins); + x = phi.peephole(); + } + cp.addDef(x); + } + // Factoring must not widen the result (e.g. correlated And operands). + if( cp.compute().isa(compute()) ) return cp; + cp.kill(); + return null; + } + /** * If only single unique input, return it */ diff --git a/chapter13/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/RegionNode.java index fb934511..d7e934d7 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/RegionNode.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/node/RegionNode.java @@ -68,7 +68,7 @@ public class RegionNode extends CFGNode { 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.addDepForwards(this).in(0)==p2.addDepForwards(this).in(0) && p1.in(0) instanceof IfNode iff ) return iff.ctrl(); diff --git a/chapter13/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/ReturnNode.java index 444b76a8..725c704f 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -4,32 +4,17 @@ 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 all preceding memory effects and the return value alive: {ctrl, $mem, value}. */ public class ReturnNode extends CFGNode { 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 - if( scope != null ) { - String[] names = scope.reverseNames(); - for (String name: names) { - if (!name.equals("$ctrl") && name.startsWith("$")) - addDef(scope.lookup(name)); - } - } + // A synthetic never-taken loop exit has no source scope. + super(ctrl, scope == null ? null : scope.lookup("$mem"), data); } public Node ctrl() { return in(0); } - public Node expr() { return in(1); } + public Node mem () { return in(1); } + public Node expr() { return in(2); } @Override public String label() { return "Return"; } @@ -43,7 +28,8 @@ public class ReturnNode extends CFGNode { @Override public Type compute() { - return TypeTuple.make(ctrl()._type,expr()._type); + // Return exposes the complete memory state. + return TypeTuple.make(ctrl()._type,TypeMem.BOT,expr()._type); } @Override diff --git a/chapter13/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/ScopeNode.java index 455f4b3a..b931e862 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -129,7 +129,7 @@ public class ScopeNode extends Node { // Set real Phi in the loop head // The phi takes its one input (no backedge yet) from a recursive // lookup, which might have insert a Phi in every loop nest. - : loop.setDef(idx,new PhiNode(name, lookupDeclaredType(name),loop.ctrl(),loop.update(name,null,nestingLevel),null).peephole()); + : 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 @@ -207,7 +207,7 @@ public class ScopeNode extends Node { if( in(i) != that.in(i) ) // No need for redundant Phis // If we are in lazy phi mode we need to a lookup // by name as it will trigger a phi creation - setDef(i, new PhiNode(ns[i], this.lookupDeclaredType(ns[i]), r, this.lookup(ns[i]), that.lookup(ns[i])).peephole()); + 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(); diff --git a/chapter13/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/StartNode.java index 5f205ffd..2cedf0f6 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ b/chapter13/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -1,59 +1,17 @@ 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; import java.util.HashSet; -/** - * 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. - */ +/** Start supplies control, whole memory, and the argument, in that order. */ public class StartNode extends CFGNode implements 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; - + private final TypeTuple _args; 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 diff --git a/chapter13/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter13/src/main/java/com/seaofnodes/simple/node/StoreNode.java index 142aebc3..031240ce 100644 --- a/chapter13/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter13/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; @@ -24,6 +25,10 @@ public class StoreNode extends MemOpNode { _init = init; } + @Override boolean canDrop(MemOpNode other, Node dep) { + return super.canDrop(other,dep) && _init==((StoreNode)other)._init; + } + @Override public String label() { return "." +_name+"="; } @Override public boolean isMem() { return true; } @@ -39,11 +44,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/chapter13/src/test/java/com/seaofnodes/simple/Chapter04Test.java b/chapter13/src/test/java/com/seaofnodes/simple/Chapter04Test.java index a3109ff7..14d31245 100644 --- a/chapter13/src/test/java/com/seaofnodes/simple/Chapter04Test.java +++ b/chapter13/src/test/java/com/seaofnodes/simple/Chapter04Test.java @@ -84,7 +84,10 @@ public class Chapter04Test { StopNode stop = parser.parse(); ReturnNode ret = stop.ret(); assertTrue(ret.in(0) instanceof CProjNode); - assertTrue(ret.in(1) instanceof ProjNode); + assertTrue(ret.in(1) instanceof ProjNode); + assertEquals(1, ((ProjNode)ret.in(1))._idx); + assertTrue(ret.in(2) instanceof ProjNode); + assertEquals(2, ((ProjNode)ret.in(2))._idx); } @Test diff --git a/chapter13/src/test/java/com/seaofnodes/simple/Chapter05Test.java b/chapter13/src/test/java/com/seaofnodes/simple/Chapter05Test.java index 70f4ccea..cd646126 100644 --- a/chapter13/src/test/java/com/seaofnodes/simple/Chapter05Test.java +++ b/chapter13/src/test/java/com/seaofnodes/simple/Chapter05Test.java @@ -22,7 +22,7 @@ else { return a;"""); StopNode ret = parser.parse().iterate(); - assertEquals("return Phi(Region20,(arg+2),(arg-3));", ret.toString()); + assertEquals("return Phi(Region21,(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(Region19,4,3);", ret.toString()); + assertEquals("return Phi(Region20,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(Region23,2,3));", ret.toString()); + assertEquals("return ((arg*2)+Phi(Region24,2,3));", ret.toString()); } @Test @@ -80,7 +80,7 @@ else a=b+1; return a+b;"""); StopNode ret = parser.parse().iterate(); - assertEquals("return ((Phi(Region34,(arg*2),arg)+arg)+Phi(Region,4,5));", ret.toString()); + assertEquals("return ((Phi(Region35,(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(Region38,Phi(Region24,2,3),Phi(Region36,4,5));", stop.toString()); + assertEquals("return Phi(Region39,Phi(Region25,2,3),Phi(Region37,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(Region17,1,0))+Phi(Region33,2,0));", stop.toString()); + assertEquals("return ((arg+Phi(Region18,1,0))+Phi(Region34,2,0));", stop.toString()); } @Test @@ -130,7 +130,7 @@ if( arg==1 ) } return a;"""); StopNode ret = parser.parse().iterate(); - assertEquals("return (arg==Phi(Region19,3,2));", ret.toString()); + assertEquals("return (arg==Phi(Region20,3,2));", ret.toString()); } @Test diff --git a/chapter13/src/test/java/com/seaofnodes/simple/Chapter06Test.java b/chapter13/src/test/java/com/seaofnodes/simple/Chapter06Test.java index 633b25e0..43e46a5d 100644 --- a/chapter13/src/test/java/com/seaofnodes/simple/Chapter06Test.java +++ b/chapter13/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(Region16,2,1))<3);", stop.toString()); + assertEquals("return ((arg<Phi(Region17,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(Region39,42,5);", stop.toString()); + assertEquals("return Phi(Region40,42,5);", stop.toString()); } @Test @@ -84,7 +84,7 @@ else b=5; return b;""", TypeInteger.BOT); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region32,2,5);", stop.toString()); + assertEquals("return Phi(Region33,2,5);", stop.toString()); } @Test @@ -144,7 +144,7 @@ else return a; """, TypeInteger.BOT); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region43,3,Phi(Region41,4,5));", stop.toString()); + assertEquals("return Phi(Region44,3,Phi(Region42,4,5));", stop.toString()); } @Test @@ -200,7 +200,7 @@ if( arg ) { return a+b; """); StopNode ret = parser.parse().iterate(); - assertEquals("return Phi(Region28,4,1);", ret.toString()); + assertEquals("return Phi(Region29,4,1);", ret.toString()); } @@ -251,7 +251,7 @@ if( arg ) { return a+b+c; """); StopNode ret = parser.parse().iterate(); - assertEquals("return (Phi(Region40,Phi(Region25,2,3),0)+Phi(Region,3,1));", ret.toString()); + assertEquals("return (Phi(Region41,Phi(Region26,2,3),0)+Phi(Region,3,1));", ret.toString()); } diff --git a/chapter13/src/test/java/com/seaofnodes/simple/Chapter07Test.java b/chapter13/src/test/java/com/seaofnodes/simple/Chapter07Test.java index 177b54d1..222982e7 100644 --- a/chapter13/src/test/java/com/seaofnodes/simple/Chapter07Test.java +++ b/chapter13/src/test/java/com/seaofnodes/simple/Chapter07Test.java @@ -18,7 +18,7 @@ public class Chapter07Test { return arg; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop8,arg,(Phi_arg+1));", stop.toString()); + assertEquals("return Phi(Loop9,arg,(Phi_arg+1));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -35,7 +35,7 @@ if(arg){}else{ return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region26,1,Phi(Loop15,1,(Phi_a+1)));", stop.toString()); + assertEquals("return Phi(Region27,1,Phi(Loop16,1,(Phi_a+1)));", stop.toString()); } @Test @@ -55,7 +55,7 @@ while(i < arg) { return sum; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,0,Phi(Loop22,Phi_sum,(Phi_sum+Phi(Loop,0,(Phi_j+1)))));", stop.toString()); + assertEquals("return Phi(Loop11,0,Phi(Loop23,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(Loop10,2,Phi(Region29,Phi_b,4));", stop.toString()); + assertEquals("return Phi(Loop11,2,Phi(Region30,Phi_b,4));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -90,7 +90,7 @@ while(a < 10) { return b; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,2,(Phi(Region29,Phi_b,4)+1));", stop.toString()); + assertEquals("return Phi(Loop11,2,(Phi(Region30,Phi_b,4)+1));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -107,7 +107,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop9,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -123,7 +123,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -136,7 +136,7 @@ while(arg) a = 2; return a; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop9,1,2);", stop.toString()); + assertEquals("return Phi(Loop10,1,2);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -149,7 +149,7 @@ while(arg) a = 2; return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,1,2);", stop.toString()); + assertEquals("return Phi(Loop10,1,2);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -165,7 +165,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop9,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -181,7 +181,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -198,7 +198,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop11,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -215,7 +215,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop11,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } diff --git a/chapter13/src/test/java/com/seaofnodes/simple/Chapter08Test.java b/chapter13/src/test/java/com/seaofnodes/simple/Chapter08Test.java index 7760ae09..89ce8313 100644 --- a/chapter13/src/test/java/com/seaofnodes/simple/Chapter08Test.java +++ b/chapter13/src/test/java/com/seaofnodes/simple/Chapter08Test.java @@ -23,7 +23,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region37,Phi(Region26,Phi(Loop8,arg,(Phi_arg+1)),Add),Add);", stop.toString()); + assertEquals("return Phi(Region38,Phi(Region27,Phi(Loop9,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)); @@ -46,7 +46,7 @@ while(arg < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,1,Phi(Region43,Phi_a,(Phi_a+1)));", stop.toString()); + assertEquals("return Phi(Loop10,1,Phi(Region44,Phi_a,(Phi_a+1)));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -65,7 +65,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region35,Phi(Loop8,arg,(Phi_arg+1)),Add);", stop.toString()); + assertEquals("return Phi(Region36,Phi(Loop9,arg,(Phi_arg+1)),Add);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof RegionNode); } @@ -81,7 +81,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region26,Phi(Loop8,arg,(Phi_arg+1)),Add);", stop.toString()); + assertEquals("return Phi(Region27,Phi(Loop9,arg,(Phi_arg+1)),Add);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof RegionNode); } @@ -100,7 +100,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop8,arg,(Phi_arg+1));", stop.toString()); + assertEquals("return Phi(Loop9,arg,(Phi_arg+1));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -117,7 +117,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop8,arg,(Phi_arg+1));", stop.toString()); + assertEquals("return Phi(Loop9,arg,(Phi_arg+1));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -185,7 +185,7 @@ while(arg < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region29,Phi(Loop9,1,(Phi_a+1)),Add);", stop.toString()); + assertEquals("return Phi(Region30,Phi(Loop10,1,(Phi_a+1)),Add);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof RegionNode); } @@ -201,7 +201,7 @@ while(1) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return (Phi(Loop9,1,Add)+1);", stop.toString()); + assertEquals("return (Phi(Loop10,1,Add)+1);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode prj && prj._idx==1 ); } diff --git a/chapter13/src/test/java/com/seaofnodes/simple/Chapter09Test.java b/chapter13/src/test/java/com/seaofnodes/simple/Chapter09Test.java index 64bbf245..49ee75a2 100644 --- a/chapter13/src/test/java/com/seaofnodes/simple/Chapter09Test.java +++ b/chapter13/src/test/java/com/seaofnodes/simple/Chapter09Test.java @@ -69,7 +69,7 @@ while (arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,arg,(Phi_arg+2));", stop.toString()); + assertEquals("return Phi(Loop10,arg,(Phi_arg+2));", stop.toString()); Assert.assertEquals(11L, Evaluator.evaluate(stop, 1)); } @@ -86,7 +86,7 @@ while (arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,arg,(Phi_arg+4));", stop.toString()); + assertEquals("return Phi(Loop11,arg,(Phi_arg+4));", stop.toString()); Assert.assertEquals(13L, Evaluator.evaluate(stop, 1)); } @@ -115,7 +115,7 @@ while (arg) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop16,arg,(Phi_arg+1));", stop.toString()); + assertEquals("return Phi(Loop17,arg,(Phi_arg+1));", stop.toString()); } @Test @@ -185,7 +185,7 @@ while(arg) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop11,0,(-(Phi_a+3)));", stop.toString()); + assertEquals("return Phi(Loop12,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(Loop8,arg,(Phi_arg-1));", stop.toString()); + assertEquals("return Phi(Loop9,arg,(Phi_arg-1));", stop.toString()); } @Test diff --git a/chapter13/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter13/src/test/java/com/seaofnodes/simple/Chapter10Test.java index dddddf13..6649c0b0 100644 --- a/chapter13/src/test/java/com/seaofnodes/simple/Chapter10Test.java +++ b/chapter13/src/test/java/com/seaofnodes/simple/Chapter10Test.java @@ -1,6 +1,11 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.node.Node; import com.seaofnodes.simple.node.StopNode; +import com.seaofnodes.simple.node.LoadNode; +import com.seaofnodes.simple.node.StoreNode; +import com.seaofnodes.simple.evaluator.Evaluator; +import java.util.BitSet; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -160,7 +165,7 @@ while (arg) { return bar.a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop12,0,(Phi_a+2));", stop.toString()); + assertEquals("return Phi(Loop13,0,(Phi_a+2));", stop.toString()); } @Test @@ -212,7 +217,7 @@ if( bar ) bar.a = 1; return bar; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region31,(*void)Phi(Region19,null,new Bar),null);", stop.toString()); + assertEquals("return Phi(Region33,(*void)Phi(Region20,null,new Bar),null);", stop.toString()); } @Test @@ -228,7 +233,7 @@ else bar.a = 1; return rez; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region37,4,3);", stop.toString()); + assertEquals("return Phi(Region39,4,3);", stop.toString()); } @Test @@ -282,7 +287,7 @@ while( i.x < i.len ) { return sum; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop17,0,(Phi(Loop,0,(Phi_x+1))+Phi_sum));", stop.toString()); + assertEquals("return Phi(Loop19,0,(Phi(Loop,0,(Phi_x+1))+Phi_sum));", stop.toString()); } @@ -303,7 +308,7 @@ while(arg) { return ret; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop12,new s0,Phi(Region34,new s0,Phi_ret));", stop.toString()); + assertEquals("return Phi(Loop13,new s0,Phi(Region37,new s0,Phi_ret));", stop.toString()); } @Test @@ -321,7 +326,7 @@ while(arg) { return ret; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop15,new s0,Phi(Region35,new s0,Phi_ret));", stop.toString()); + assertEquals("return Phi(Loop17,new s0,Phi(Region38,new s0,Phi_ret));", stop.toString()); } @@ -338,7 +343,7 @@ while(arg < 10) { return ret; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop12,new s0,Phi(Region33,new s0,Phi_ret));", stop.toString()); + assertEquals("return Phi(Loop13,new s0,Phi(Region35,new s0,Phi_ret));", stop.toString()); } @Test @@ -456,4 +461,121 @@ 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)); + } + + 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()); + } + } + + @Test public void testKeepLoadsAtMerge() { + StopNode stop = new Parser(""" + struct S { int x; } + S a = new S; S b = new S; + a.x = arg; b.x = arg+1; + S p = a; S q = b; + if (arg<0) { p=b; q=a; } + int v; + if (arg>1) v=p.x; else v=q.x; + return v; + """).parse().iterate(); + // The one-step safety check stops at the memory aggregate. + assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet())); + assertEquals(-1L,Evaluator.evaluate(stop,-1)); + assertEquals(1L,Evaluator.evaluate(stop,0)); + assertEquals(3L,Evaluator.evaluate(stop,3)); + } + + @Test public void testDropStores() { + StopNode stop = new Parser(""" + struct S { int x; } + S s = new S; + if (arg) s.x=arg+1; else s.x=arg+2; + return s; + """).parse().iterate(); + // These chapters still bind struct Stores to branch control. + assertEquals(3,countMemoryNodes(stop,StoreNode.class,new BitSet())); + assertEquals(2L,((Evaluator.Obj)Evaluator.evaluate(stop,0)).fields()[0]); + assertEquals(4L,((Evaluator.Obj)Evaluator.evaluate(stop,3)).fields()[0]); + } + + @Test public void testKeepReadsBeforeWrites() { + StopNode stop = new Parser(""" + struct S { int x; int y; } + S a = new S; S b = new S; + a.x=arg; b.x=arg+1; + S p=a; S q=b; + if (arg<0) { p=b; q=a; } + int v; + if (arg>1) { v=p.x; p.x=41; p.y=5; } + else { v=q.x; q.x=42; } + return v; + """).parse().iterate(); + assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet())); + assertEquals(-1L,Evaluator.evaluate(stop,-1)); + assertEquals(1L,Evaluator.evaluate(stop,0)); + assertEquals(3L,Evaluator.evaluate(stop,3)); + } + + private static int countMemoryNodes(Node n, Class<?> kind, BitSet seen) { + if (n==null || seen.get(n._nid)) return 0; + seen.set(n._nid); + int cnt=kind.isInstance(n) ? 1 : 0; + for (Node def : n._inputs) cnt+=countMemoryNodes(def,kind,seen); + return cnt; + } } diff --git a/chapter13/src/test/java/com/seaofnodes/simple/Chapter10bTest.java b/chapter13/src/test/java/com/seaofnodes/simple/Chapter10bTest.java new file mode 100644 index 00000000..6f094cfb --- /dev/null +++ b/chapter13/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/chapter13/src/test/java/com/seaofnodes/simple/Chapter11Test.java b/chapter13/src/test/java/com/seaofnodes/simple/Chapter11Test.java index 41c4d063..7aab9752 100644 --- a/chapter13/src/test/java/com/seaofnodes/simple/Chapter11Test.java +++ b/chapter13/src/test/java/com/seaofnodes/simple/Chapter11Test.java @@ -325,7 +325,7 @@ while( prime <= arg ) { return primeCount; """); StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ return 0; return Phi(Loop20,1,Phi(Region87,Phi_primeCount,Phi(Region81,(Phi_primeCount+1),Phi_primeCount))); ]", stop.toString()); + assertEquals("Stop[ return 0; return Phi(Loop21,1,Phi(Region88,Phi_primeCount,Phi(Region82,(Phi_primeCount+1),Phi_primeCount))); ]", stop.toString()); assertEquals(0L, Evaluator.evaluate(stop, 1)); // No primes 1 or below assertEquals(1L, Evaluator.evaluate(stop, 2)); // 2 assertEquals(2L, Evaluator.evaluate(stop, 3)); // 2, 3 @@ -367,7 +367,7 @@ if (arg) { return i; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region34,.f,0);", stop.toString()); + assertEquals("return Phi(Region37,.f,0);", stop.toString()); } @Test @@ -473,7 +473,7 @@ while(arg) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop15,arg,Phi(Region37,.f,0));", stop.toString()); + assertEquals("return Phi(Loop17,arg,Phi(Region41,.f,0));", stop.toString()); } @Test @@ -524,7 +524,7 @@ else if (arg == 1) s.f = 1; return s.f; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region37,1,Phi(Region35,1,0));", stop.toString()); + assertEquals("return Phi(Region43,1,Phi(Region38,1,0));", stop.toString()); assertEquals(1L, Evaluator.evaluate(stop, 0)); assertEquals(1L, Evaluator.evaluate(stop, 1)); assertEquals(0L, Evaluator.evaluate(stop, 2)); diff --git a/chapter13/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter13/src/test/java/com/seaofnodes/simple/Chapter12Test.java index a1c7a92a..ced5606c 100644 --- a/chapter13/src/test/java/com/seaofnodes/simple/Chapter12Test.java +++ b/chapter13/src/test/java/com/seaofnodes/simple/Chapter12Test.java @@ -64,7 +64,7 @@ while( 1 ) { return guess; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,(flt)arg,(((ToFloat/Phi_guess)+Phi_guess)/2.0));", stop.toString()); + assertEquals("return Phi(Loop10,(flt)arg,(((ToFloat/Phi_guess)+Phi_guess)/2.0));", stop.toString()); assertEquals(3.0, Evaluator.evaluate(stop, 9)); assertEquals(1.414213562373095, Evaluator.evaluate(stop, 2)); } diff --git a/chapter13/src/test/java/com/seaofnodes/simple/Chapter13Test.java b/chapter13/src/test/java/com/seaofnodes/simple/Chapter13Test.java index d695c9a6..13ac58f4 100644 --- a/chapter13/src/test/java/com/seaofnodes/simple/Chapter13Test.java +++ b/chapter13/src/test/java/com/seaofnodes/simple/Chapter13Test.java @@ -35,7 +35,7 @@ while( arg ) { return head.next.i; """); try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Might be null accessing 'next'",e.getMessage()); } + catch( Exception e ) { assertEquals("Might be null accessing 'i'",e.getMessage()); } } @Test diff --git a/chapter13/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/chapter13/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java index 3512d2e9..40a2f78d 100644 --- a/chapter13/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java +++ b/chapter13/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java @@ -154,6 +154,7 @@ public class Evaluator { case ToFloatNode cast -> (double)vall(cast.in(1)); case LoadNode load -> load(load); case StoreNode store -> store(store); + case MemMergeNode merge -> MEMORY; case NewNode alloc -> alloc(alloc); case CProjNode cproj -> ((Object[])val(cproj.ctrl()))[cproj._idx]; case ProjNode proj -> ((Object[])val( proj.in(0) ))[ proj._idx]; @@ -167,8 +168,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; @@ -179,7 +180,7 @@ public class Evaluator { case null: return Status.FALLTHROUGH; case ReturnNode ret: - return val(ret.in(1)); + return val(ret.expr()); case IfNode ifn: block = block.next()[isTrue(val(ifn.in(1))) ? 0 : 1]; if (block == null) return Status.FALLTHROUGH; diff --git a/chapter13/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java b/chapter13/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java index 8b86d67a..d16f4300 100644 --- a/chapter13/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java +++ b/chapter13/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java @@ -250,6 +250,12 @@ public class Scheduler { // 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++) { @@ -271,7 +277,7 @@ public class Scheduler { // 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) od(out).ifPresent(this::decUsers); + if (out instanceof LoadNode l && l._alias==s._alias) od(out).ifPresent(this::decUsers); } } } @@ -469,7 +475,8 @@ public class Scheduler { var data = mem.pop(); node = data.node; for(var out:node.in(1)._outputs) { - if (out instanceof LoadNode) od(out).ifPresent(d->d.users++); + if (out instanceof LoadNode l && l._alias==((StoreNode)node)._alias) + od(out).ifPresent(d->d.users++); } } } diff --git a/chapter14/README.md b/chapter14/README.md index 496cbb8c..acd8a492 100644 --- a/chapter14/README.md +++ b/chapter14/README.md @@ -1,5 +1,9 @@ # Chapter 14: Narrow types +Memory retains the single `$mem` binding and lazy partitions from Chapter 10b. +Narrow-field stores perform this chapter's truncation before updating their +alias in MemMerge. Start and Return keep control in slot 0 and memory in slot 1. + # Table of Contents 1. [Narrow Word Types](#narrow-or-sub-word-types) @@ -347,4 +351,3 @@ slightly different `idealize()` calls. [^1]: Hacker's delight. 4-2 Propagating Bounds through Add's and Subtract's - diff --git a/chapter14/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java b/chapter14/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java index a270f026..78a0dcde 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java @@ -149,7 +149,7 @@ public abstract class GlobalCodeMotion { // Loads need their memory inputs' uses also done if( n instanceof LoadNode ld ) for( Node memuse : ld.mem()._outputs ) - if( memuse._type instanceof TypeMem && late[memuse._nid]==null ) + if( antiUse(ld,memuse) && late[memuse._nid]==null ) continue outer; // All uses done, schedule @@ -219,6 +219,17 @@ public abstract class GlobalCodeMotion { best instanceof IfNode; } + // Only a store or memory Phi covering this alias can constrain a load. + // MemMerge packages slices without overwriting them. + private static boolean antiUse(LoadNode load, Node use) { + return switch( use ) { + case StoreNode st -> st._alias==load._alias; + case MemPhiNode phi -> phi._alias==load._alias; + case BulkMemPhiNode phi -> !phi.isSplit(load._alias); + default -> false; + }; + } + private static CFGNode find_anti_dep(CFGNode lca, LoadNode load, CFGNode early, CFGNode[] late, int[] anti) { // We could skip final-field loads here. // Walk LCA->early, flagging Load's block location choices @@ -226,6 +237,7 @@ public abstract class GlobalCodeMotion { anti[cfg._nid] = load._nid; // Walk load->mem uses, looking for Stores causing an anti-dep for( Node mem : load.mem()._outputs ) { + if( !antiUse(load,mem) ) continue; switch( mem ) { case StoreNode st: assert late[st._nid]!=null; @@ -238,9 +250,6 @@ public abstract class GlobalCodeMotion { if( phi.in(i)==load.mem() ) lca = anti_dep(load,phi.region().cfg(i),load.mem().cfg0(),lca,null,anti); break; - case LoadNode ld: break; // Loads do not cause anti-deps on other loads - case ReturnNode ret: break; // Load must already be ahead of Return - case NeverNode never: break; default: throw Utils.TODO(); } } diff --git a/chapter14/src/main/java/com/seaofnodes/simple/Parser.java b/chapter14/src/main/java/com/seaofnodes/simple/Parser.java index 34fac183..bce192c2 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/Parser.java @@ -36,6 +36,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; + // Debugger Printing. public static boolean SCHEDULED; // True if debug printer can use schedule info @@ -123,7 +127,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); ZERO = new ConstantNode(TypeInteger.constant(0)).peephole().keep(); XCTRL= new XCtrlNode().peephole().keep(); @@ -150,7 +154,8 @@ public class Parser { // Enter a new scope for the initial control and arguments _scope.push(); _scope.define(ScopeNode.CTRL, Type.CONTROL , new CProjNode(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(); if( ctrl()._type==Type.CONTROL ) STOP.addReturn(new ReturnNode(ctrl(), new ConstantNode(TypeInteger.constant(0)).peephole(), _scope).peephole()); @@ -238,8 +243,9 @@ public class Parser { require("}"); // Build and install the TypeStruct TypeStruct ts = TypeStruct.make(typeName, fields); - TYPES.put(typeName, ts); - START.addMemProj(ts, _scope); // Insert memory edges + TYPES.put(typeName, ts); // Insert the struct name in the collection of all struct names + _aliases.put(typeName,_alias); + _alias += ts._fields.length; return parseStatement(); } @@ -666,21 +672,24 @@ public class Parser { */ private Node newStruct(TypeStruct obj) { Node n = new NewNode(TypeMemPtr.make(obj), ctrl()).peephole().keep(); - 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, field._type, ctrl(), memAlias(alias), n, new ConstantNode(field._type.makeInit()).peephole(),true).peephole()); + store(field._fname,alias,field._type,n,new ConstantNode(field._type.makeInit()).peephole(),true); 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, Type glb, Node ptr, Node val, boolean init) { + Node prior = mem().keep(); + Node st = new StoreNode(name,alias,glb,ctrl(),prior,ptr,val,init).peephole(); + mem(new MemMergeNode(prior,alias,st).peephole()); + prior.unkeep(); + } /** * Parse postfix expression. For now this is just a field @@ -702,7 +711,7 @@ public class Parser { TypeStruct base = (TypeStruct)TYPES.get(ptr._obj._name); int idx = base==null ? -1 : base.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 @@ -712,13 +721,13 @@ public class Parser { Type glb = base._fields[idx]._type; // Auto-truncate when storing to narrow fields val = zsMask(val,glb); - memAlias(alias, new StoreNode(name, alias,glb, ctrl(), memAlias(alias), expr, val, false).peephole()); + store(name,alias,glb,expr,val,false); return expr; // "obj.a = expr" returns the expression while updating memory } } Type declaredType = base._fields[idx]._type; - return parsePostfix(new LoadNode(name, alias, declaredType.glb(), memAlias(alias), expr).peephole()); + return parsePostfix(new LoadNode(name, alias, declaredType.glb(), mem(), expr).peephole()); } // zero/sign extend. "i" is limited to either classic unsigned (min==0) or diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java new file mode 100644 index 00000000..8d512f21 --- /dev/null +++ b/chapter14/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/chapter14/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/LoadNode.java index 2db5e469..5cd39a33 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter14/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 java.util.BitSet; @@ -22,6 +23,24 @@ public class LoadNode extends MemOpNode { super(name, alias, glb, null, memSlice, memPtr); } + @Override boolean canDrop(MemOpNode other, Node dep) { + return super.canDrop(other,dep) && !clobbered(dep); + } + + // Check only immediate memory users. Stores clobber memory; Phis and + // aggregates might lead to a clobber, so stop rather than search further. + private boolean clobbered(Node dep) { + Node mem = mem(); + mem.addDepForwards(dep); + for( Node use : mem._outputs ) { + if( use==null ) continue; + use.addDepForwards(dep); + if( use instanceof StoreNode || use instanceof PhiNode || + use instanceof MemMergeNode ) return true; + } + return false; + } + @Override public String label() { return _name; } @Override StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append(".").append(_name); } @@ -33,10 +52,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 _name.equals(st._name); // Equiv class aliasing is perfect return st.val(); } @@ -48,7 +72,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. @@ -69,7 +93,7 @@ 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; } Node copy(Node mem, Node ptr) { diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java new file mode 100644 index 00000000..d7fed402 --- /dev/null +++ b/chapter14/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/chapter14/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/MemOpNode.java index 76564db0..29d302cb 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/node/MemOpNode.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/node/MemOpNode.java @@ -24,6 +24,13 @@ public abstract class MemOpNode extends Node { public Node mem() { return in(1); } public Node ptr() { return in(2); } + // Extra conditions for factoring matching memory operations through a Phi. + // The caller has already checked the opcode, input shape and control. + boolean canDrop(MemOpNode other, Node dep) { + ptr().addDep(dep); + return _alias==other._alias && _declaredType==other._declaredType && err()==null; + } + @Override boolean eq(Node n) { MemOpNode mem = (MemOpNode)n; // Invariant diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java new file mode 100644 index 00000000..c806bede --- /dev/null +++ b/chapter14/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/chapter14/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter14/src/main/java/com/seaofnodes/simple/node/Node.java index af59cb0d..61679fa4 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/node/Node.java @@ -16,14 +16,14 @@ import java.util.function.Function; * The Node class provides common functionality used by all subtypes. * Subtypes of Node specialize by overriding methods. */ -public abstract class Node implements OutNode { +public abstract class Node implements OutNode, Cloneable { /** * 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; + public int _nid; /** * Inputs to the node. These are use-def references to Nodes. @@ -32,7 +32,7 @@ public abstract class Node implements OutNode { * 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; + public ArrayList<Node> _inputs; /** * Outputs reference Nodes that are not null and have this Node as an @@ -43,7 +43,7 @@ public abstract class Node implements OutNode { * walked in either direction. These outputs are typically used for * efficient optimizations but otherwise have no semantics meaning. */ - public final ArrayList<Node> _outputs; + public ArrayList<Node> _outputs; /** @@ -208,6 +208,7 @@ public abstract class Node implements OutNode { // Error is 'use' does not exist; ok for 'use' to be null. protected boolean delUse( Node use ) { Utils.del(_outputs, Utils.find(_outputs, use)); + moveDepsToWorklist(); // User-count and anti-dependence queries can now change. return _outputs.isEmpty(); } @@ -264,6 +265,7 @@ public abstract class Node implements OutNode { n.unlock(); int idx = Utils.find(n._inputs, this); n._inputs.set(idx,nnn); + n.moveDepsToWorklist(); // Rewiring can change a dependent query without changing type. nnn.addUse(n); } kill(); @@ -449,6 +451,14 @@ public abstract class Node implements OutNode { * 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; @@ -456,8 +466,8 @@ public abstract class Node implements OutNode { 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( Utils.find(_inputs,dep) != -1 ) return this; // No need for deps on immediate neighbors - if( Utils.find(_outputs,dep)!= -1 ) return this; + if( !forwards && Utils.find(_inputs,dep) != -1 ) return this; + if( !forwards && Utils.find(_outputs,dep)!= -1 ) return this; _deps.add(dep); return this; } @@ -589,6 +599,20 @@ public abstract class Node implements OutNode { // Does not need to be implemented in isCFG() nodes. Node copy(Node lhs, Node rhs) { throw Utils.TODO("Binary ops need to implement copy"); } + // Exact-class copy preserving operation attributes, with fresh identity + // and no edges, dependencies, or GVN membership. + public final Node copyEmpty() { + Node n; + try { n = (Node)clone(); } + catch( CloneNotSupportedException e ) { throw new AssertionError(e); } + n._nid = UNIQUE_ID++; + n._inputs = new ArrayList<>(); + n._outputs = new ArrayList<>(); + n._deps = null; + n._hash = 0; + return n; + } + // Report any post-optimize errors String err() { return null; } diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/PhiNode.java index 1d4b3fba..0f8b06f5 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -16,6 +16,10 @@ public class PhiNode extends Node { 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 @@ -65,30 +69,9 @@ public class PhiNode extends Node { if (live != null) return live; - // Pull "down" a common data op. One less op in the world. One more - // Phi, but Phis do not make code. - // Phi(op(A,B),op(Q,R),op(X,Y)) becomes - // op(Phi(A,Q,X), Phi(B,R,Y)). - Node op = in(1); - if( op.nIns()==3 && op.in(0)==null && !op.isCFG() && same_op() ) { - Node[] lhss = new Node[nIns()]; - Node[] rhss = new Node[nIns()]; - lhss[0] = rhss[0] = in(0); // Set Region - for( int i=1; i<nIns(); i++ ) { - lhss[i] = in(i).in(1); - rhss[i] = in(i).in(2); - } - Node phi_lhs = new PhiNode(_label, in(1).in(1)._type.glb(),lhss).peephole(); - Node phi_rhs = new PhiNode(_label, in(1).in(2)._type.glb(),rhss).peephole(); - Node phi_op = op.copy(phi_lhs,phi_rhs); - phi_op.setType(phi_op.compute()); - // If losing precision, don't do it - if( !phi_op._type.isa(_type) ) { - phi_op.kill(); - return null; - } - return phi_op; - } + // Phi(op(a,b,...),op(c,d,...)) -> op(Phi(a,c),Phi(b,d),...). + Node down; + if( same_op() && (down=drop_same_op())!=null ) return down; // If merging Phi(N, cast(N)) - we are losing the cast JOIN effects, so just remove. if( nIns()==3 ) { @@ -118,16 +101,72 @@ public class PhiNode extends Node { } private boolean same_op() { - for( int i=2; i<nIns(); i++ ) { - if( in(1).getClass() != in(i).getClass() ) return false; - if( in(1).in(1)._type.glb() != in(i).in(1)._type.glb() ) return false; - if( in(1).in(2)._type.glb() != in(i).in(2)._type.glb() ) return false; + Node op = in(1); + if( op.isCFG() || op instanceof ConstantNode || op instanceof PhiNode || + op instanceof ProjNode || op instanceof NewNode || op instanceof ScopeNode || op instanceof MemMergeNode ) return false; + // A bulk Phi must split its aliases before factoring precise Stores. + if( this instanceof BulkMemPhiNode ) return false; + // Bulk splitting currently identifies parallel slices by Region/alias. + // Factoring can introduce another slice at a different memory point; + // wait until that Region's bulk partitioning has finished. + if( op instanceof MemOpNode ) + for( Node use : region()._outputs ) + if( use instanceof BulkMemPhiNode ) { + use.addDepForwards(this); + return false; + } + Node busy = null; + for( int i=1; i<nIns(); i++ ) { + Node n = in(i); + if( region().in(i).addDep(this)._type==Type.XCONTROL || + op.getClass()!=n.getClass() || n.nIns()!=op.nIns() || n.in(0)!=null || !op.eq(n) ) return false; + if( n instanceof MemOpNode mem && !mem.canDrop((MemOpNode)op,this) ) return false; + // Moving a Store must remove the old effect, not duplicate it. + // Allow one shared arm: the other arms disappear into the single + // factored operation, usually reducing the total operation count. + n.addDepForwards(this); + if( n.nOuts()>1 ) { + for( Node use : n._outputs ) + if( use!=null && use!=this ) use.addDepForwards(this); + if( n instanceof StoreNode || busy!=null ) return false; + busy=n; + } + for( int j=1; j<n.nIns(); j++ ) + if( n.in(j) instanceof ScopeNode || ((n.in(j)==null) != (op.in(j)==null)) ) return false; } - // Load merging needs an anti-dep check. - if( in(1) instanceof MemOpNode ) return false; return true; } + private Node drop_same_op() { + Node op = in(1), cp = op.copyEmpty(); + cp._type = null; + cp.addDef(null); + for( int j=1; j<op.nIns(); j++ ) { + Node x = op.in(j); + boolean different = false; + for( int i=2; i<nIns(); i++ ) + if( in(i).in(j)!=x ) different=true; + if( different ) { + Node[] ins = new Node[nIns()]; + ins[0] = region(); + Type t = Type.TOP; + for( int i=1; i<nIns(); i++ ) { + ins[i] = in(i).in(j); + t = t.meet(ins[i]._type); + } + PhiNode phi = j==1 && op instanceof MemOpNode mem + ? new MemPhiNode(_label,mem._alias,ins) + : PhiNode.make(_label,t.glb(),ins); + x = phi.peephole(); + } + cp.addDef(x); + } + // Factoring must not widen the result (e.g. correlated And operands). + if( cp.compute().isa(compute()) ) return cp; + cp.kill(); + return null; + } + /** * If only single unique input, return it */ diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/RegionNode.java index fb934511..d7e934d7 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/node/RegionNode.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/node/RegionNode.java @@ -68,7 +68,7 @@ public class RegionNode extends CFGNode { 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.addDepForwards(this).in(0)==p2.addDepForwards(this).in(0) && p1.in(0) instanceof IfNode iff ) return iff.ctrl(); diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/ReturnNode.java index 444b76a8..725c704f 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -4,32 +4,17 @@ 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 all preceding memory effects and the return value alive: {ctrl, $mem, value}. */ public class ReturnNode extends CFGNode { 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 - if( scope != null ) { - String[] names = scope.reverseNames(); - for (String name: names) { - if (!name.equals("$ctrl") && name.startsWith("$")) - addDef(scope.lookup(name)); - } - } + // A synthetic never-taken loop exit has no source scope. + super(ctrl, scope == null ? null : scope.lookup("$mem"), data); } public Node ctrl() { return in(0); } - public Node expr() { return in(1); } + public Node mem () { return in(1); } + public Node expr() { return in(2); } @Override public String label() { return "Return"; } @@ -43,7 +28,8 @@ public class ReturnNode extends CFGNode { @Override public Type compute() { - return TypeTuple.make(ctrl()._type,expr()._type); + // Return exposes the complete memory state. + return TypeTuple.make(ctrl()._type,TypeMem.BOT,expr()._type); } @Override diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/ScopeNode.java index 455f4b3a..b931e862 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -129,7 +129,7 @@ public class ScopeNode extends Node { // Set real Phi in the loop head // The phi takes its one input (no backedge yet) from a recursive // lookup, which might have insert a Phi in every loop nest. - : loop.setDef(idx,new PhiNode(name, lookupDeclaredType(name),loop.ctrl(),loop.update(name,null,nestingLevel),null).peephole()); + : 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 @@ -207,7 +207,7 @@ public class ScopeNode extends Node { if( in(i) != that.in(i) ) // No need for redundant Phis // If we are in lazy phi mode we need to a lookup // by name as it will trigger a phi creation - setDef(i, new PhiNode(ns[i], this.lookupDeclaredType(ns[i]), r, this.lookup(ns[i]), that.lookup(ns[i])).peephole()); + 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(); diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/StartNode.java index 5f205ffd..2cedf0f6 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -1,59 +1,17 @@ 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; import java.util.HashSet; -/** - * 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. - */ +/** Start supplies control, whole memory, and the argument, in that order. */ public class StartNode extends CFGNode implements 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; - + private final TypeTuple _args; 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 diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/StoreNode.java index 142aebc3..031240ce 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter14/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; @@ -24,6 +25,10 @@ public class StoreNode extends MemOpNode { _init = init; } + @Override boolean canDrop(MemOpNode other, Node dep) { + return super.canDrop(other,dep) && _init==((StoreNode)other)._init; + } + @Override public String label() { return "." +_name+"="; } @Override public boolean isMem() { return true; } @@ -39,11 +44,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/chapter14/src/test/java/com/seaofnodes/simple/Chapter04Test.java b/chapter14/src/test/java/com/seaofnodes/simple/Chapter04Test.java index a3109ff7..14d31245 100644 --- a/chapter14/src/test/java/com/seaofnodes/simple/Chapter04Test.java +++ b/chapter14/src/test/java/com/seaofnodes/simple/Chapter04Test.java @@ -84,7 +84,10 @@ public class Chapter04Test { StopNode stop = parser.parse(); ReturnNode ret = stop.ret(); assertTrue(ret.in(0) instanceof CProjNode); - assertTrue(ret.in(1) instanceof ProjNode); + assertTrue(ret.in(1) instanceof ProjNode); + assertEquals(1, ((ProjNode)ret.in(1))._idx); + assertTrue(ret.in(2) instanceof ProjNode); + assertEquals(2, ((ProjNode)ret.in(2))._idx); } @Test diff --git a/chapter14/src/test/java/com/seaofnodes/simple/Chapter05Test.java b/chapter14/src/test/java/com/seaofnodes/simple/Chapter05Test.java index 70f4ccea..cd646126 100644 --- a/chapter14/src/test/java/com/seaofnodes/simple/Chapter05Test.java +++ b/chapter14/src/test/java/com/seaofnodes/simple/Chapter05Test.java @@ -22,7 +22,7 @@ else { return a;"""); StopNode ret = parser.parse().iterate(); - assertEquals("return Phi(Region20,(arg+2),(arg-3));", ret.toString()); + assertEquals("return Phi(Region21,(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(Region19,4,3);", ret.toString()); + assertEquals("return Phi(Region20,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(Region23,2,3));", ret.toString()); + assertEquals("return ((arg*2)+Phi(Region24,2,3));", ret.toString()); } @Test @@ -80,7 +80,7 @@ else a=b+1; return a+b;"""); StopNode ret = parser.parse().iterate(); - assertEquals("return ((Phi(Region34,(arg*2),arg)+arg)+Phi(Region,4,5));", ret.toString()); + assertEquals("return ((Phi(Region35,(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(Region38,Phi(Region24,2,3),Phi(Region36,4,5));", stop.toString()); + assertEquals("return Phi(Region39,Phi(Region25,2,3),Phi(Region37,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(Region17,1,0))+Phi(Region33,2,0));", stop.toString()); + assertEquals("return ((arg+Phi(Region18,1,0))+Phi(Region34,2,0));", stop.toString()); } @Test @@ -130,7 +130,7 @@ if( arg==1 ) } return a;"""); StopNode ret = parser.parse().iterate(); - assertEquals("return (arg==Phi(Region19,3,2));", ret.toString()); + assertEquals("return (arg==Phi(Region20,3,2));", ret.toString()); } @Test diff --git a/chapter14/src/test/java/com/seaofnodes/simple/Chapter06Test.java b/chapter14/src/test/java/com/seaofnodes/simple/Chapter06Test.java index 99ce8fa5..c5664c9d 100644 --- a/chapter14/src/test/java/com/seaofnodes/simple/Chapter06Test.java +++ b/chapter14/src/test/java/com/seaofnodes/simple/Chapter06Test.java @@ -65,7 +65,7 @@ else b=5; return b;""", TypeInteger.BOT); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region34,42,5);", stop.toString()); + assertEquals("return Phi(Region35,42,5);", stop.toString()); } @Test @@ -84,7 +84,7 @@ else b=5; return b;""", TypeInteger.BOT); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region32,2,5);", stop.toString()); + assertEquals("return Phi(Region33,2,5);", stop.toString()); } @Test @@ -144,7 +144,7 @@ else return a; """, TypeInteger.BOT); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region43,3,Phi(Region41,4,5));", stop.toString()); + assertEquals("return Phi(Region44,3,Phi(Region42,4,5));", stop.toString()); } @Test @@ -200,7 +200,7 @@ if( arg ) { return a+b; """); StopNode ret = parser.parse().iterate(); - assertEquals("return Phi(Region28,4,1);", ret.toString()); + assertEquals("return Phi(Region29,4,1);", ret.toString()); } @@ -251,7 +251,7 @@ if( arg ) { return a+b+c; """); StopNode ret = parser.parse().iterate(); - assertEquals("return (Phi(Region40,Phi(Region25,2,3),0)+Phi(Region,3,1));", ret.toString()); + assertEquals("return (Phi(Region41,Phi(Region26,2,3),0)+Phi(Region,3,1));", ret.toString()); } diff --git a/chapter14/src/test/java/com/seaofnodes/simple/Chapter07Test.java b/chapter14/src/test/java/com/seaofnodes/simple/Chapter07Test.java index 29aa7eaf..443b0d17 100644 --- a/chapter14/src/test/java/com/seaofnodes/simple/Chapter07Test.java +++ b/chapter14/src/test/java/com/seaofnodes/simple/Chapter07Test.java @@ -17,7 +17,7 @@ public class Chapter07Test { return arg; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop8,arg,(Phi_arg+1));", stop.toString()); + assertEquals("return Phi(Loop9,arg,(Phi_arg+1));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -34,7 +34,7 @@ if(arg){}else{ return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region26,1,Phi(Loop15,1,(Phi_a+1)));", stop.toString()); + assertEquals("return Phi(Region27,1,Phi(Loop16,1,(Phi_a+1)));", stop.toString()); } @Test @@ -54,7 +54,7 @@ while(i < arg) { return sum; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,0,Phi(Loop22,Phi_sum,(Phi_sum+Phi(Loop,0,(Phi_j+1)))));", stop.toString()); + assertEquals("return Phi(Loop11,0,Phi(Loop23,Phi_sum,(Phi_sum+Phi(Loop,0,(Phi_j+1)))));", stop.toString()); } @Test @@ -70,7 +70,7 @@ while(a < 10) { return b; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop10,2,Phi(Region29,Phi_b,4));", stop.toString()); + assertEquals("return Phi(Loop11,2,Phi(Region30,Phi_b,4));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -89,7 +89,7 @@ while(a < 10) { return b; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,2,(Phi(Region29,Phi_b,4)+1));", stop.toString()); + assertEquals("return Phi(Loop11,2,(Phi(Region30,Phi_b,4)+1));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -106,7 +106,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop9,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -122,7 +122,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -135,7 +135,7 @@ while(arg) a = 2; return a; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop9,1,2);", stop.toString()); + assertEquals("return Phi(Loop10,1,2);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -148,7 +148,7 @@ while(arg) a = 2; return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,1,2);", stop.toString()); + assertEquals("return Phi(Loop10,1,2);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -164,7 +164,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop9,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -180,7 +180,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -197,7 +197,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop11,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -214,7 +214,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop11,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } diff --git a/chapter14/src/test/java/com/seaofnodes/simple/Chapter08Test.java b/chapter14/src/test/java/com/seaofnodes/simple/Chapter08Test.java index 7760ae09..89ce8313 100644 --- a/chapter14/src/test/java/com/seaofnodes/simple/Chapter08Test.java +++ b/chapter14/src/test/java/com/seaofnodes/simple/Chapter08Test.java @@ -23,7 +23,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region37,Phi(Region26,Phi(Loop8,arg,(Phi_arg+1)),Add),Add);", stop.toString()); + assertEquals("return Phi(Region38,Phi(Region27,Phi(Loop9,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)); @@ -46,7 +46,7 @@ while(arg < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,1,Phi(Region43,Phi_a,(Phi_a+1)));", stop.toString()); + assertEquals("return Phi(Loop10,1,Phi(Region44,Phi_a,(Phi_a+1)));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -65,7 +65,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region35,Phi(Loop8,arg,(Phi_arg+1)),Add);", stop.toString()); + assertEquals("return Phi(Region36,Phi(Loop9,arg,(Phi_arg+1)),Add);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof RegionNode); } @@ -81,7 +81,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region26,Phi(Loop8,arg,(Phi_arg+1)),Add);", stop.toString()); + assertEquals("return Phi(Region27,Phi(Loop9,arg,(Phi_arg+1)),Add);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof RegionNode); } @@ -100,7 +100,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop8,arg,(Phi_arg+1));", stop.toString()); + assertEquals("return Phi(Loop9,arg,(Phi_arg+1));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -117,7 +117,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop8,arg,(Phi_arg+1));", stop.toString()); + assertEquals("return Phi(Loop9,arg,(Phi_arg+1));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -185,7 +185,7 @@ while(arg < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region29,Phi(Loop9,1,(Phi_a+1)),Add);", stop.toString()); + assertEquals("return Phi(Region30,Phi(Loop10,1,(Phi_a+1)),Add);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof RegionNode); } @@ -201,7 +201,7 @@ while(1) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return (Phi(Loop9,1,Add)+1);", stop.toString()); + assertEquals("return (Phi(Loop10,1,Add)+1);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode prj && prj._idx==1 ); } diff --git a/chapter14/src/test/java/com/seaofnodes/simple/Chapter09Test.java b/chapter14/src/test/java/com/seaofnodes/simple/Chapter09Test.java index 64bbf245..49ee75a2 100644 --- a/chapter14/src/test/java/com/seaofnodes/simple/Chapter09Test.java +++ b/chapter14/src/test/java/com/seaofnodes/simple/Chapter09Test.java @@ -69,7 +69,7 @@ while (arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,arg,(Phi_arg+2));", stop.toString()); + assertEquals("return Phi(Loop10,arg,(Phi_arg+2));", stop.toString()); Assert.assertEquals(11L, Evaluator.evaluate(stop, 1)); } @@ -86,7 +86,7 @@ while (arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,arg,(Phi_arg+4));", stop.toString()); + assertEquals("return Phi(Loop11,arg,(Phi_arg+4));", stop.toString()); Assert.assertEquals(13L, Evaluator.evaluate(stop, 1)); } @@ -115,7 +115,7 @@ while (arg) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop16,arg,(Phi_arg+1));", stop.toString()); + assertEquals("return Phi(Loop17,arg,(Phi_arg+1));", stop.toString()); } @Test @@ -185,7 +185,7 @@ while(arg) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop11,0,(-(Phi_a+3)));", stop.toString()); + assertEquals("return Phi(Loop12,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(Loop8,arg,(Phi_arg-1));", stop.toString()); + assertEquals("return Phi(Loop9,arg,(Phi_arg-1));", stop.toString()); } @Test diff --git a/chapter14/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter14/src/test/java/com/seaofnodes/simple/Chapter10Test.java index dddddf13..6649c0b0 100644 --- a/chapter14/src/test/java/com/seaofnodes/simple/Chapter10Test.java +++ b/chapter14/src/test/java/com/seaofnodes/simple/Chapter10Test.java @@ -1,6 +1,11 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.node.Node; import com.seaofnodes.simple.node.StopNode; +import com.seaofnodes.simple.node.LoadNode; +import com.seaofnodes.simple.node.StoreNode; +import com.seaofnodes.simple.evaluator.Evaluator; +import java.util.BitSet; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -160,7 +165,7 @@ while (arg) { return bar.a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop12,0,(Phi_a+2));", stop.toString()); + assertEquals("return Phi(Loop13,0,(Phi_a+2));", stop.toString()); } @Test @@ -212,7 +217,7 @@ if( bar ) bar.a = 1; return bar; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region31,(*void)Phi(Region19,null,new Bar),null);", stop.toString()); + assertEquals("return Phi(Region33,(*void)Phi(Region20,null,new Bar),null);", stop.toString()); } @Test @@ -228,7 +233,7 @@ else bar.a = 1; return rez; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region37,4,3);", stop.toString()); + assertEquals("return Phi(Region39,4,3);", stop.toString()); } @Test @@ -282,7 +287,7 @@ while( i.x < i.len ) { return sum; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop17,0,(Phi(Loop,0,(Phi_x+1))+Phi_sum));", stop.toString()); + assertEquals("return Phi(Loop19,0,(Phi(Loop,0,(Phi_x+1))+Phi_sum));", stop.toString()); } @@ -303,7 +308,7 @@ while(arg) { return ret; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop12,new s0,Phi(Region34,new s0,Phi_ret));", stop.toString()); + assertEquals("return Phi(Loop13,new s0,Phi(Region37,new s0,Phi_ret));", stop.toString()); } @Test @@ -321,7 +326,7 @@ while(arg) { return ret; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop15,new s0,Phi(Region35,new s0,Phi_ret));", stop.toString()); + assertEquals("return Phi(Loop17,new s0,Phi(Region38,new s0,Phi_ret));", stop.toString()); } @@ -338,7 +343,7 @@ while(arg < 10) { return ret; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop12,new s0,Phi(Region33,new s0,Phi_ret));", stop.toString()); + assertEquals("return Phi(Loop13,new s0,Phi(Region35,new s0,Phi_ret));", stop.toString()); } @Test @@ -456,4 +461,121 @@ 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)); + } + + 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()); + } + } + + @Test public void testKeepLoadsAtMerge() { + StopNode stop = new Parser(""" + struct S { int x; } + S a = new S; S b = new S; + a.x = arg; b.x = arg+1; + S p = a; S q = b; + if (arg<0) { p=b; q=a; } + int v; + if (arg>1) v=p.x; else v=q.x; + return v; + """).parse().iterate(); + // The one-step safety check stops at the memory aggregate. + assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet())); + assertEquals(-1L,Evaluator.evaluate(stop,-1)); + assertEquals(1L,Evaluator.evaluate(stop,0)); + assertEquals(3L,Evaluator.evaluate(stop,3)); + } + + @Test public void testDropStores() { + StopNode stop = new Parser(""" + struct S { int x; } + S s = new S; + if (arg) s.x=arg+1; else s.x=arg+2; + return s; + """).parse().iterate(); + // These chapters still bind struct Stores to branch control. + assertEquals(3,countMemoryNodes(stop,StoreNode.class,new BitSet())); + assertEquals(2L,((Evaluator.Obj)Evaluator.evaluate(stop,0)).fields()[0]); + assertEquals(4L,((Evaluator.Obj)Evaluator.evaluate(stop,3)).fields()[0]); + } + + @Test public void testKeepReadsBeforeWrites() { + StopNode stop = new Parser(""" + struct S { int x; int y; } + S a = new S; S b = new S; + a.x=arg; b.x=arg+1; + S p=a; S q=b; + if (arg<0) { p=b; q=a; } + int v; + if (arg>1) { v=p.x; p.x=41; p.y=5; } + else { v=q.x; q.x=42; } + return v; + """).parse().iterate(); + assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet())); + assertEquals(-1L,Evaluator.evaluate(stop,-1)); + assertEquals(1L,Evaluator.evaluate(stop,0)); + assertEquals(3L,Evaluator.evaluate(stop,3)); + } + + private static int countMemoryNodes(Node n, Class<?> kind, BitSet seen) { + if (n==null || seen.get(n._nid)) return 0; + seen.set(n._nid); + int cnt=kind.isInstance(n) ? 1 : 0; + for (Node def : n._inputs) cnt+=countMemoryNodes(def,kind,seen); + return cnt; + } } diff --git a/chapter14/src/test/java/com/seaofnodes/simple/Chapter10bTest.java b/chapter14/src/test/java/com/seaofnodes/simple/Chapter10bTest.java new file mode 100644 index 00000000..6f094cfb --- /dev/null +++ b/chapter14/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/chapter14/src/test/java/com/seaofnodes/simple/Chapter11Test.java b/chapter14/src/test/java/com/seaofnodes/simple/Chapter11Test.java index 6df1929f..d2e300b1 100644 --- a/chapter14/src/test/java/com/seaofnodes/simple/Chapter11Test.java +++ b/chapter14/src/test/java/com/seaofnodes/simple/Chapter11Test.java @@ -325,7 +325,7 @@ while( prime <= arg ) { return primeCount; """); StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ return 0; return Phi(Loop20,1,Phi(Region87,Phi_primeCount,Phi(Region81,(Phi_primeCount+1),Phi_primeCount))); ]", stop.toString()); + assertEquals("Stop[ return 0; return Phi(Loop21,1,Phi(Region88,Phi_primeCount,Phi(Region82,(Phi_primeCount+1),Phi_primeCount))); ]", stop.toString()); assertEquals(0L, Evaluator.evaluate(stop, 1)); // No primes 1 or below assertEquals(1L, Evaluator.evaluate(stop, 2)); // 2 assertEquals(2L, Evaluator.evaluate(stop, 3)); // 2, 3 @@ -367,7 +367,7 @@ if (arg) { return i; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region34,.f,0);", stop.toString()); + assertEquals("return Phi(Region37,.f,0);", stop.toString()); } @Test @@ -473,7 +473,7 @@ while(arg) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop15,arg,Phi(Region37,.f,0));", stop.toString()); + assertEquals("return Phi(Loop17,arg,Phi(Region41,.f,0));", stop.toString()); } @Test @@ -524,7 +524,7 @@ else if (arg == 1) s.f = 1; return s.f; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region37,1,Phi(Region35,1,0));", stop.toString()); + assertEquals("return Phi(Region43,1,Phi(Region38,1,0));", stop.toString()); assertEquals(1L, Evaluator.evaluate(stop, 0)); assertEquals(1L, Evaluator.evaluate(stop, 1)); assertEquals(0L, Evaluator.evaluate(stop, 2)); diff --git a/chapter14/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter14/src/test/java/com/seaofnodes/simple/Chapter12Test.java index 4eaf81df..998053ec 100644 --- a/chapter14/src/test/java/com/seaofnodes/simple/Chapter12Test.java +++ b/chapter14/src/test/java/com/seaofnodes/simple/Chapter12Test.java @@ -64,7 +64,7 @@ while( 1 ) { return guess; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,(flt)arg,(((ToFloat/Phi_guess)+Phi_guess)/2.0));", stop.toString()); + assertEquals("return Phi(Loop10,(flt)arg,(((ToFloat/Phi_guess)+Phi_guess)/2.0));", stop.toString()); assertEquals(3.0, Evaluator.evaluate(stop, 9)); assertEquals(1.414213562373095, Evaluator.evaluate(stop, 2)); } diff --git a/chapter14/src/test/java/com/seaofnodes/simple/Chapter13Test.java b/chapter14/src/test/java/com/seaofnodes/simple/Chapter13Test.java index 66ed1073..15f97b79 100644 --- a/chapter14/src/test/java/com/seaofnodes/simple/Chapter13Test.java +++ b/chapter14/src/test/java/com/seaofnodes/simple/Chapter13Test.java @@ -35,7 +35,7 @@ while( arg ) { return head.next.i; """); try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Might be null accessing 'next'",e.getMessage()); } + catch( Exception e ) { assertEquals("Might be null accessing 'i'",e.getMessage()); } } @Test diff --git a/chapter14/src/test/java/com/seaofnodes/simple/Chapter14Test.java b/chapter14/src/test/java/com/seaofnodes/simple/Chapter14Test.java index c4768fb3..23c8fed7 100644 --- a/chapter14/src/test/java/com/seaofnodes/simple/Chapter14Test.java +++ b/chapter14/src/test/java/com/seaofnodes/simple/Chapter14Test.java @@ -59,7 +59,7 @@ while( b ) b = b + 456;// Truncate return b; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,123,((Phi_b+456)&255));", stop.toString()); + assertEquals("return Phi(Loop10,123,((Phi_b+456)&255));", stop.toString()); } @Test @@ -152,7 +152,7 @@ if (arg) j = 1; return j; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region28,1,(-((arg&9223372036854775807)+-9223372036854775808)));", stop.toString()); + assertEquals("return Phi(Region29,1,(-((arg&9223372036854775807)+-9223372036854775808)));", stop.toString()); assertEquals(-9223372036854775808L, Evaluator.evaluate(stop, 0)); assertEquals(1L, Evaluator.evaluate(stop, 1)); } @@ -209,7 +209,7 @@ else i = b.i; return i; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region31,.i,.i);", stop.toString()); + assertEquals("return Phi(Region34,.i,.i);", stop.toString()); assertEquals(0L, Evaluator.evaluate(stop, 0)); } @@ -229,7 +229,7 @@ while (1) { } """); StopNode stop = parser.parse().iterate(); - assertEquals("return ((arg>>Phi(Region32,2,3))+1);", stop.toString()); + assertEquals("return ((arg>>Phi(Region33,2,3))+1);", stop.toString()); assertEquals(1L, Evaluator.evaluate(stop, 0)); assertEquals(1L, Evaluator.evaluate(stop, 1)); assertEquals(2L, Evaluator.evaluate(stop, 7)); @@ -248,7 +248,7 @@ if (arg&5) b = arg == 1; return b; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region37,(arg==1),((flt)arg==Phi(Region23,1.0,2.0)));", stop.toString()); + assertEquals("return Phi(Region35,(arg==1),((flt)arg==Phi(Region24,1.0,2.0)));", stop.toString()); assertEquals(1L, Evaluator.evaluate(stop, 1)); assertEquals(0L, Evaluator.evaluate(stop, 2)); assertEquals(0L, Evaluator.evaluate(stop, 3)); @@ -264,7 +264,7 @@ else i=2; return (arg == i) == 1; """); StopNode stop = parser.parse().iterate(); - assertEquals("return (arg==Phi(Region18,3,2));", stop.toString()); + assertEquals("return (arg==Phi(Region19,3,2));", stop.toString()); assertEquals(0L, Evaluator.evaluate(stop, 0)); assertEquals(0L, Evaluator.evaluate(stop, 1)); assertEquals(0L, Evaluator.evaluate(stop, 2)); @@ -283,7 +283,7 @@ while (1) { } """); StopNode stop = parser.parse().iterate(); - assertEquals("return (Phi(Region43,((arg&3)+9223372036854775804),((arg&7)+9223372036854775800))+-1);", stop.toString()); + assertEquals("return (Phi(Region44,((arg&3)+9223372036854775804),((arg&7)+9223372036854775800))+-1);", stop.toString()); assertEquals(0x7FFFFFFFFFFFFFF7L, Evaluator.evaluate(stop, 0)); assertEquals(0x7FFFFFFFFFFFFFFCL, Evaluator.evaluate(stop, 1)); } diff --git a/chapter14/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/chapter14/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java index 31a14c05..afe4cfe3 100644 --- a/chapter14/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java +++ b/chapter14/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java @@ -160,6 +160,7 @@ public class Evaluator { case ToFloatNode cast -> (double)vall(cast.in(1)); case LoadNode load -> load(load); case StoreNode store -> store(store); + case MemMergeNode merge -> MEMORY; case NewNode alloc -> alloc(alloc); case CProjNode cproj -> ((Object[])val(cproj.ctrl()))[cproj._idx]; case ProjNode proj -> ((Object[])val( proj.in(0) ))[ proj._idx]; @@ -173,8 +174,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; @@ -185,7 +186,7 @@ public class Evaluator { case null: return Status.FALLTHROUGH; case ReturnNode ret: - return val(ret.in(1)); + return val(ret.expr()); case IfNode ifn: block = block.next()[isTrue(val(ifn.in(1))) ? 0 : 1]; if (block == null) return Status.FALLTHROUGH; diff --git a/chapter14/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java b/chapter14/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java index 8b86d67a..d16f4300 100644 --- a/chapter14/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java +++ b/chapter14/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java @@ -250,6 +250,12 @@ public class Scheduler { // 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++) { @@ -271,7 +277,7 @@ public class Scheduler { // 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) od(out).ifPresent(this::decUsers); + if (out instanceof LoadNode l && l._alias==s._alias) od(out).ifPresent(this::decUsers); } } } @@ -469,7 +475,8 @@ public class Scheduler { var data = mem.pop(); node = data.node; for(var out:node.in(1)._outputs) { - if (out instanceof LoadNode) od(out).ifPresent(d->d.users++); + if (out instanceof LoadNode l && l._alias==((StoreNode)node)._alias) + od(out).ifPresent(d->d.users++); } } } diff --git a/chapter15/README.md b/chapter15/README.md index d05fca54..8ab3378b 100644 --- a/chapter15/README.md +++ b/chapter15/README.md @@ -4,6 +4,7 @@ 1. [Parser changes](#parser) 2. [TypeArray is TypeStruct](#type-array-is-typestruct) +3. [Allocation and partial memory](#allocation-and-partial-memory) 4. [New Address Math](#new-address-math) 5. [Struct Layout](#struct-layout) 6. [Some Simple Address Math Peeps](#some-simple-address-math-peeps) @@ -81,6 +82,49 @@ the Parser, users cannot write their own structs which get confused as arrays. These `TypeStruct` arrays otherwise behave exactly as a `TypeStruct`. +## Allocation and partial memory + +This chapter keeps Chapter 10b's single hidden `$mem` variable and lazy +`BulkMemPhi`/`MemPhi` partitioning. Start produces `{ctrl, $mem, arg}` and +Return consumes `{ctrl, $mem, value}`. Array initialization changes New: +instead of separate initializing Stores, New allocates and zeros the object. + +New consumes `{ctrl, $mem, size}` and produces just `{ptr, $mem}`. The pointer +is result 0 because New has no control result. Its memory input and result +cover only the field aliases in New's internal `TypeStruct`, also available +through its `TypeMemPtr`. Alias numbers are identities, not projection indices. + +For an allocation affecting aliases `A`, the parser builds: + +```text +input = MemMerge(default: absent, each a in A: before[a]) +new = New(ctrl, input, size) +ptr = Proj(new, 0) +after = MemMerge(default: before, each a in A: Proj(new, 1)) +``` + +All affected entries share the same memory projection. The input MemMerge is +partial: a null default means an unlisted alias is **not covered**. The output +aggregate retains the previous memory for unrelated aliases. An empty struct +has an empty partial input and leaves whole memory unchanged. + +Memory types now pair an alias with the type of values stored in that slice. +An alias query through New combines the incoming contents with the field's +initializer: integer zero, floating zero, or null. The query follows aggregates +and allocations, stops at cached Phi types, and records optimizer dependencies. +MemPhi merges these value types for its one alias. No per-alias New projections +or new lattice domain are needed. + +A Load using New's own pointer can fold to the initialized value. A pointer +from a distinct New can bypass the allocation to its memory input. Mere node +inequality is insufficient: a Phi or cast can still denote the allocated object. +Array length is set by a subsequent Store; the array body is zeroed by New. +Array accesses retain their existing control input as the bounds-check proxy. + +Both schedulers follow an alias through MemMerge to find New's memory effect. +MemMerge itself does not overwrite memory, but must not hide the ordering of +a read before allocation and a later write to an existing object. + ## New Address Math Since arrays need adressing math to index into them, Loads and Stores need a @@ -208,4 +252,3 @@ for( int i=0; i < ary#; i++ ) that would be entirely syntatic-sugar over the existing parser, but would really help make array looping syntax clearer. There are lots of options here, which should really be the focus of another chapter! - diff --git a/chapter15/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java b/chapter15/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java index 27307d32..d17ec3b6 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java @@ -148,12 +148,8 @@ public abstract class GlobalCodeMotion { // Loads need their memory inputs' uses also done if( n instanceof LoadNode ld ) - for( Node memuse : ld.mem()._outputs ) - if( late[memuse._nid]==null && - // Load-use directly defines memory - (memuse._type instanceof TypeMem || - // Load-use indirectly defines memory - (memuse._type instanceof TypeTuple tt && tt._types[ld._alias] instanceof TypeMem)) ) + for( Node memuse : ld.antiDeps() ) + if( late[memuse._nid]==null ) continue outer; // All uses done, schedule @@ -165,9 +161,7 @@ public abstract class GlobalCodeMotion { for( Node def : n._inputs ) { if( def==null ) continue; if( late[def._nid]==null ) work.push(def); - for( Node out : def._outputs ) - if( out instanceof LoadNode ld && late[ld._nid]==null ) - work.push(ld); + wakeLoads(def,late,work,new BitSet()); } if( n instanceof LoopNode loop ) for( Node phi : loop._outputs ) @@ -224,13 +218,24 @@ public abstract class GlobalCodeMotion { best instanceof IfNode; } + // An allocation may consume a load's memory through a partial aggregate. + private static void wakeLoads(Node def, CFGNode[] late, WorkList<Node> work, BitSet visit) { + if( visit.get(def._nid) ) return; + visit.set(def._nid); + for( Node out : def._outputs ) + if( out instanceof LoadNode ld && late[ld._nid]==null ) work.push(ld); + if( def instanceof MemMergeNode ) + for( int i=1; i<def.nIns(); i++ ) + if( def.in(i)!=null ) wakeLoads(def.in(i),late,work,visit); + } + private static CFGNode find_anti_dep(CFGNode lca, LoadNode load, CFGNode early, CFGNode[] late, int[] anti) { // We could skip final-field loads here. // Walk LCA->early, flagging Load's block location choices for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() ) anti[cfg._nid] = load._nid; // Walk load->mem uses, looking for Stores causing an anti-dep - for( Node mem : load.mem()._outputs ) { + for( Node mem : load.antiDeps() ) { switch( mem ) { case StoreNode st: assert late[st._nid]!=null; @@ -247,9 +252,6 @@ public abstract class GlobalCodeMotion { if( phi.in(i)==load.mem() ) lca = anti_dep(load,phi.region().cfg(i),load.mem().cfg0(),lca,null,anti); break; - case LoadNode ld: break; // Loads do not cause anti-deps on other loads - case ReturnNode ret: break; // Load must already be ahead of Return - case NeverNode never: break; default: throw Utils.TODO(); } } diff --git a/chapter15/src/main/java/com/seaofnodes/simple/Parser.java b/chapter15/src/main/java/com/seaofnodes/simple/Parser.java index 965bd146..1fc6f312 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/Parser.java @@ -128,7 +128,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); ZERO = con(0).keep(); XCTRL= new XCtrlNode().peephole().keep(); @@ -156,9 +156,8 @@ public class Parser { // Enter a new scope for the initial control and arguments _scope.push(); _scope.define(ScopeNode.CTRL, Type.CONTROL , new CProjNode(START, 0, ScopeNode.CTRL).peephole()); - _scope.define(ScopeNode.ARG0, TypeInteger.BOT, new ProjNode(START, 1, ScopeNode.ARG0).peephole()); - - // Parse whole program + _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(); if( ctrl()._type==Type.CONTROL ) @@ -250,7 +249,6 @@ public class Parser { // Build and install the TypeStruct TypeStruct ts = TypeStruct.make(typeName, fields.toArray(new Field[fields.size()])); TYPES.put(typeName, TypeMemPtr.make(ts)); - START.addMemProj(ts, _scope); // Insert memory edges return parseStatement(); } @@ -536,7 +534,6 @@ public class Parser { TypeStruct ts = TypeStruct.makeAry(TypeInteger.BOT,ALIAS++,t,ALIAS++); TypeMemPtr tary = TypeMemPtr.make(ts); TYPES.put(tname,tary); - START.addMemProj(ts, _scope); // Insert memory alias edges return tary; } @@ -727,16 +724,22 @@ public class Parser { * Return a NewNode with pre-zeroed memory */ private Node newStruct(TypeStruct obj, Node size) { - Field[] fs = obj._fields; - Node[] ns = new Node[2+fs.length]; - ns[0] = ctrl(); - ns[1] = size; - for( int i = 0; i < fs.length; i++ ) - ns[i+2] = memAlias(fs[i]._alias); - Node nnn = new NewNode(TypeMemPtr.make(obj), ns).peephole(); - for( int i = 0; i < fs.length; i++ ) - memAlias(fs[i]._alias, new ProjNode(nnn,i+2,memName(fs[i]._alias)).peephole()); - return new ProjNode(nnn,1,obj._name).peephole(); + Node prior = mem().keep(); + // No default: only the aliases initialized by this allocation. + MemMergeNode input = new MemMergeNode(null); + for( Field f : obj._fields ) + input.alias(f._alias,prior); + Node nnn = new NewNode(TypeMemPtr.make(obj),ctrl(),input.peephole(),size).peephole().keep(); + Node ptr = new ProjNode(nnn,0,obj._name).peephole().keep(); + Node out = new ProjNode(nnn,1,"$mem").peephole().keep(); + MemMergeNode after = new MemMergeNode(prior); + for( Field f : obj._fields ) + after.alias(f._alias,out); + mem(after.peephole()); + out.unkeep(); + nnn.unkeep(); + prior.unkeep(); + return ptr.unkeep(); } private Node newArray(TypeStruct ary, Node len) { @@ -745,17 +748,22 @@ public class Parser { Node size = new AddNode(con(base),new ShlNode(len,con(scale)).peephole()).peephole(); Node ptr = newStruct(ary,size); int alias = ary._fields[0]._alias; // Length alias - memAlias(alias,new StoreNode("#",alias,TypeInteger.BOT,memAlias(alias),ptr,con( ary.offset(0) ), len.unkeep(), true ).peephole()); + store("#",alias,TypeInteger.BOT,ptr,con(ary.offset(0)),len.unkeep(),true,null); return ptr; } - // 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, Type glb, Node ptr, Node off, Node val, boolean init, Node ctrl) { + Node prior = mem().keep(); + Node st = new StoreNode(name,alias,glb,prior,ptr,off,val,init); + st.setDef(0,ctrl); + st = st.peephole(); + mem(new MemMergeNode(prior,alias,st).peephole()); + prior.unkeep(); + } /** * Parse postfix expression. For now this is just a field @@ -808,17 +816,13 @@ public class Parser { Node val = parseExpression(); // Auto-truncate when storing to narrow fields val = zsMask(val,f._type).keep(); - Node st = new StoreNode(name, f._alias, f._type, memAlias(f._alias), expr.unkeep(), off.unkeep(), val, false); - // Arrays include control, as a proxy for a safety range check. - // Structs don't need this; they only need a NPE check which is - // done via the type system. - if( base.isAry() ) st.setDef(0,ctrl()); - memAlias(f._alias, st.peephole()); + // Array control stands in for the future bounds check. + store(name,f._alias,f._type,expr.unkeep(),off.unkeep(),val,false,base.isAry() ? ctrl() : null); return val.unkeep(); // "obj.a = expr" returns the expression while updating memory } } - Node load = new LoadNode(name, f._alias, f._type.glb(), memAlias(f._alias), expr.unkeep(), off); + Node load = new LoadNode(name, f._alias, f._type.glb(), mem(), expr.unkeep(), off); // Arrays include control, as a proxy for a safety range check // Structs don't need this; they only need a NPE check which is // done via the type system. diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java new file mode 100644 index 00000000..84f97189 --- /dev/null +++ b/chapter15/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 mmm.in(1)!=null && missingAlias(mmm)==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); + 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); + 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; + } + + // Every explicit slot requests that alias, including a partial allocation + // input whose slot still points to this bulk Phi. + private int missingAlias(MemMergeNode mmm) { + for( int alias=2; alias<mmm.nIns(); alias++ ) + if( mmm.in(alias)!=null && + mmm.alias(alias)!=mmm.in(1) && + !_aliases.get(alias) ) + 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,Type.BOTTOM)); + 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/chapter15/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/LoadNode.java index 99a6d72c..4c2013b9 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -1,11 +1,10 @@ package com.seaofnodes.simple.node; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeMem; -import com.seaofnodes.simple.type.TypeStruct; import java.util.BitSet; +import java.util.ArrayList; /** * Load represents extracting a value from inside a memory object, @@ -25,6 +24,25 @@ public class LoadNode extends MemOpNode { super(name, alias, glb, mem, ptr, off); } + @Override boolean canDrop(MemOpNode other, Node dep) { + return super.canDrop(other,dep) && !clobbered(dep); + } + + // Check only immediate memory users. Stores clobber memory; Phis and + // aggregates might lead to a clobber, so stop rather than search further. + private boolean clobbered(Node dep) { + Node mem = mem(); + mem.addDepForwards(dep); + for( Node use : mem._outputs ) { + if( use==null ) continue; + use.addDepForwards(dep); + if( use instanceof StoreNode || use instanceof PhiNode || + use instanceof MemMergeNode || + use instanceof NewNode ) return true; + } + return false; + } + // Debugger label @Override public String label() { return "ld_"+mlabel(); } @Override @@ -32,29 +50,29 @@ public class LoadNode extends MemOpNode { @Override public Type compute() { - if( mem()._type instanceof TypeMem mem && - // No constant folding if ptr might null-check - _declaredType != mem._t && err()==null ) { - assert mem._alias == _alias; - return _declaredType.join(mem._t); - } - return _declaredType; + Type t = MemMergeNode.contents(mem(),_alias,this); + // No constant folding if the pointer still needs a null check. + return err()==null ? _declaredType.join(t) : _declaredType; } @Override public Node idealize() { + if( mem() instanceof MemMergeNode merge ) { + setDef(1,IterPeeps.add(merge.alias(_alias))); + return this; + } Node ptr = ptr(); // Simple Load-after-Store on same address. if( mem() instanceof StoreNode st && - ptr == st.ptr() && off() == st.off() ) { // Must check same object + _alias==st._alias && ptr == st.ptr() && off() == st.off() ) { // Must check same object assert _name.equals(st._name); // Equiv class aliasing is perfect return st.val(); } // Simple Load-after-New on same address. if( mem() instanceof ProjNode p && p.in(0) instanceof NewNode nnn && - ptr == nnn.proj(1) ) // Must check same object + ptr == nnn.proj(0) ) // Must check same object return new ConstantNode(_declaredType.makeInit()); // Load-after-Store on same address, but bypassing provably unrelated @@ -63,12 +81,16 @@ public class LoadNode extends MemOpNode { Node mem = mem(); outer: while( true ) { + mem.addDep(this); switch( mem ) { + case MemMergeNode merge: + mem = merge.alias(_alias); + break; case StoreNode st: - if( ptr == st.ptr() && off() == st.off() ) + if( _alias==st._alias && ptr == st.ptr() && off() == st.off() ) return st.val(); // Proved equal // Can we prove unequal? Offsets do not overlap? - if( !off()._type.join(st.off()._type).isHigh() && // Offsets overlap + if( _alias==st._alias && !off()._type.join(st.off()._type).isHigh() && // Offsets overlap !neverAlias(ptr,st.ptr()) ) // And might alias break outer; // Cannot tell, stop trying // Pointers cannot overlap @@ -82,11 +104,11 @@ public class LoadNode extends MemOpNode { return new ConstantNode(_declaredType.makeInit()); // Load from a New if( !(ptr instanceof ProjNode pproj && pproj.in(0) instanceof NewNode nnn2) ) break outer; // Cannot tell, ptr not related to New - mem = nnn1.in(_alias);// Bypass unrelated New + mem = nnn1.mem();// Bypass unrelated New break; - } else throw Utils.TODO(); + } else break outer; default: - throw Utils.TODO(); + break outer; } } @@ -97,7 +119,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 && off() instanceof ConstantNode ) { // Profit on RHS/Loop backedge if( profit(memphi,2) || @@ -113,7 +135,9 @@ public class LoadNode extends MemOpNode { } private Node ld( int idx ) { Node mem = mem(), ptr = ptr(); - return new LoadNode(_name,_alias,_declaredType,mem.in(idx),ptr instanceof PhiNode && ptr.in(0)==mem.in(0) ? ptr.in(idx) : ptr, off()).peephole(); + LoadNode ld = new LoadNode(_name,_alias,_declaredType,mem.in(idx),ptr instanceof PhiNode && ptr.in(0)==mem.in(0) ? ptr.in(idx) : ptr,off()); + ld.setDef(0,in(0)); + return ld.peephole(); } private static boolean neverAlias( Node ptr1, Node ptr2 ) { return ptr1.in(0) != ptr2.in(0) && @@ -127,8 +151,34 @@ public class LoadNode extends MemOpNode { private boolean profit(PhiNode phi, int idx) { Node px = phi.in(idx); if( px==null ) return false; - if( px._type instanceof TypeMem mem && mem._t.isHighOrConst() ) { px.addDep(this); return true; } - if( px instanceof StoreNode st1 && ptr()==st1.ptr() && off()==st1.off() ) { px.addDep(this); return true; } + px.addDep(this); + if( px._type instanceof TypeMem mem && mem._t.isHighOrConst() ) return true; + if( px instanceof StoreNode st1 && _alias==st1._alias && ptr()==st1.ptr() && off()==st1.off() ) return true; return false; } + + // Memory writers which must follow this read. A packaging node is not a + // write, but can hide New's input, so follow this alias through aggregates. + public ArrayList<Node> antiDeps() { + ArrayList<Node> deps = new ArrayList<>(); + antiDeps(mem(),deps,new BitSet()); + return deps; + } + + private void antiDeps(Node mem, ArrayList<Node> deps, BitSet visit) { + if( visit.get(mem._nid) ) return; + visit.set(mem._nid); + for( Node use : mem._outputs ) { + if( use instanceof MemMergeNode merge ) { + Node slice = _alias<merge.nIns() ? merge.in(_alias) : null; + if( (slice==mem) || (slice==null && merge.in(1)==mem) ) + antiDeps(merge,deps,visit); + } else if( (use instanceof StoreNode st && st._alias==_alias) || + (use instanceof NewNode nnn && nnn.field(_alias)!=null) || + (use instanceof MemPhiNode phi && phi._alias==_alias) || + (use instanceof BulkMemPhiNode phi && !phi.isSplit(_alias)) ) { + if( !deps.contains(use) ) deps.add(use); + } + } + } } diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java new file mode 100644 index 00000000..22d97dae --- /dev/null +++ b/chapter15/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java @@ -0,0 +1,88 @@ +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 default slice and explicit alias overrides. A null default means partial + * memory: absent aliases are not covered, rather than having unknown contents. + */ +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); + n = n==null ? in(1) : n; + assert n!=null : "Alias not covered: "+alias; + return n; + } + + public void alias(int alias, Node mem) { + assert alias > 1; + while( nIns()<=alias ) addDef(null); + setDef(alias,mem); + } + + // Scalar contents of one alias. New's struct supplies the initialized type; + // join it with the incoming contents for all previously allocated objects. + // Phis use their cached types, so this query does not recurse around loops. + static Type contents(Node mem, int alias, Node dep) { + mem.addDep(dep); + if( mem instanceof MemMergeNode merge ) + return contents(merge.alias(alias),alias,dep); + if( mem instanceof ProjNode proj && proj.in(0) instanceof NewNode nnn ) { + assert proj._idx==1 && nnn.field(alias)!=null; + nnn.addDep(dep); + return contents(nnn.mem(),alias,dep).meet(nnn.field(alias)._type.makeInit()); + } + // There are no heap arguments in this chapter. Before the first New, + // the set of allocated objects (and hence stored values) is empty. + if( mem instanceof ProjNode proj && proj.in(0) instanceof StartNode ) return Type.TOP; + if( mem._type==Type.TOP || mem._type==TypeMem.TOP ) return Type.TOP; + if( mem._type instanceof TypeMem mt && mt._alias==alias ) return mt._t; + return Type.BOTTOM; + } + + @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)!=null && in(i)==in(1) ) { setDef(i,null); progress=true; } + if( in(i) instanceof MemMergeNode mem ) { + setDef(i,IterPeeps.add(mem.alias(i))); + progress=true; + } + if( in(i)!=null ) allDefault=false; + } + if( allDefault && in(1)!=null ) 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/chapter15/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/MemOpNode.java index c7a1efec..9c222ad2 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/node/MemOpNode.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/node/MemOpNode.java @@ -32,6 +32,13 @@ public abstract class MemOpNode extends Node { public Node ptr() { return in(2); } public Node off() { return in(3); } + // Extra conditions for factoring matching memory operations through a Phi. + // The caller has already checked the opcode, input shape and control. + boolean canDrop(MemOpNode other, Node dep) { + ptr().addDep(dep); + return _alias==other._alias && _declaredType==other._declaredType && err()==null; + } + @Override boolean eq(Node n) { MemOpNode mem = (MemOpNode)n; // Invariant diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java new file mode 100644 index 00000000..6aae00f9 --- /dev/null +++ b/chapter15/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java @@ -0,0 +1,42 @@ +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,Type.BOTTOM),inputs); + assert alias > 1; + _alias = alias; + } + + @Override public String label() { return "MemPhi_"+_alias; } + + @Override public Type compute() { + assert BulkMemPhiNode.checkMem(this,_alias); + if( !(region() instanceof RegionNode r) ) + return region()._type==Type.XCONTROL ? TypeMem.TOP : _type; + if( r.inProgress() ) return _declaredType; + Type t = Type.TOP; + for( int i=1; i<nIns(); i++ ) + if( r.in(i).addDep(this)._type!=Type.XCONTROL ) + t = t.meet(MemMergeNode.contents(in(i),_alias,this)); + return TypeMem.make(_alias,t); + } + + @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/chapter15/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/NewNode.java index f06564cd..d5154d90 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/node/NewNode.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/node/NewNode.java @@ -3,29 +3,30 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.type.*; import java.util.BitSet; -/** - * Allocation! Allocate a chunk of memory, and pre-zero it. - * The inputs include control and size, and ALL aliases being set. - * The output is large tuple, one for every alias plus the created pointer. - * New is expected to be followed by projections for every alias. +/** Allocate and zero an object. Inputs {ctrl, $mem, size}; results {ptr, $mem}. + * The memory input and result cover only the aliases in the allocated struct. */ public class NewNode extends Node implements MultiNode { - public TypeMemPtr _ptr; - - public NewNode(TypeMemPtr ptr, Node... nodes) { - super(nodes); - // Control in slot 0 - assert nodes[0]._type==Type.CONTROL || nodes[0]._type == Type.XCONTROL; - // Malloc-length in slot 1 - assert nodes[1]._type instanceof TypeInteger; - // Memory slices in remaining slots - for( int i=2; i<nodes.length; i++ ) - assert nodes[i]._type instanceof TypeMem; + public final TypeMemPtr _ptr; + public NewNode(TypeMemPtr ptr, Node ctrl, Node mem, Node size) { + super(ctrl,mem,size); + assert ctrl._type==Type.CONTROL || ctrl._type==Type.XCONTROL; + assert mem._type instanceof TypeMem; + assert size._type instanceof TypeInteger; _ptr = ptr; } + public Node mem() { return in(1); } + public Node size() { return in(2); } + + public Field field(int alias) { + for( Field f : _ptr._obj._fields ) + if( f._alias==alias ) return f; + return null; + } + @Override public String label() { return "new_" + (_ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._type.str() : _ptr._obj.str()); } @@ -38,13 +39,7 @@ public class NewNode extends Node implements MultiNode { @Override public TypeTuple compute() { - Field[] fs = _ptr._obj._fields; - Type[] ts = new Type[fs.length+2]; - ts[0] = Type.CONTROL; - ts[1] = _ptr; - for( int i=0; i<fs.length; i++ ) - ts[i+2] = TypeMem.make(fs[i]._alias,fs[i]._type.makeInit()).meet( in(i+2)._type ); - return TypeTuple.make(ts); + return TypeTuple.make(_ptr,TypeMem.BOT); } @Override diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter15/src/main/java/com/seaofnodes/simple/node/Node.java index 688f34c9..54fbc68f 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/node/Node.java @@ -16,14 +16,14 @@ import java.util.function.Function; * The Node class provides common functionality used by all subtypes. * Subtypes of Node specialize by overriding methods. */ -public abstract class Node implements OutNode { +public abstract class Node implements OutNode, Cloneable { /** * 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; + public int _nid; /** * Inputs to the node. These are use-def references to Nodes. @@ -32,7 +32,7 @@ public abstract class Node implements OutNode { * 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; + public ArrayList<Node> _inputs; /** * Outputs reference Nodes that are not null and have this Node as an @@ -43,7 +43,7 @@ public abstract class Node implements OutNode { * walked in either direction. These outputs are typically used for * efficient optimizations but otherwise have no semantics meaning. */ - public final ArrayList<Node> _outputs; + public ArrayList<Node> _outputs; /** @@ -207,6 +207,7 @@ public abstract class Node implements OutNode { // Error is 'use' does not exist; ok for 'use' to be null. protected boolean delUse( Node use ) { Utils.del(_outputs, Utils.find(_outputs, use)); + moveDepsToWorklist(); // User-count and anti-dependence queries can now change. return _outputs.isEmpty(); } @@ -263,6 +264,7 @@ public abstract class Node implements OutNode { n.unlock(); int idx = Utils.find(n._inputs, this); n._inputs.set(idx,nnn); + n.moveDepsToWorklist(); // Rewiring can change a dependent query without changing type. nnn.addUse(n); } kill(); @@ -448,6 +450,14 @@ public abstract class Node implements OutNode { * 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; @@ -455,8 +465,8 @@ public abstract class Node implements OutNode { 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( Utils.find(_inputs,dep) != -1 ) return this; // No need for deps on immediate neighbors - if( Utils.find(_outputs,dep)!= -1 ) return this; + if( !forwards && Utils.find(_inputs,dep) != -1 ) return this; + if( !forwards && Utils.find(_outputs,dep)!= -1 ) return this; _deps.add(dep); return this; } @@ -585,6 +595,20 @@ public abstract class Node implements OutNode { // Does not need to be implemented in isCFG() nodes. Node copy(Node lhs, Node rhs) { throw Utils.TODO("Binary ops need to implement copy"); } + // Exact-class copy preserving operation attributes, with fresh identity + // and no edges, dependencies, or GVN membership. + public final Node copyEmpty() { + Node n; + try { n = (Node)clone(); } + catch( CloneNotSupportedException e ) { throw new AssertionError(e); } + n._nid = UNIQUE_ID++; + n._inputs = new ArrayList<>(); + n._outputs = new ArrayList<>(); + n._deps = null; + n._hash = 0; + return n; + } + // Report any post-optimize errors String err() { return null; } diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/PhiNode.java index 36c0fe56..f92458f1 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -16,6 +16,10 @@ public class PhiNode extends Node { 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_"+MemOpNode.mlabel(_label); } @Override @@ -65,24 +69,9 @@ public class PhiNode extends Node { if (live != null) return live; - // Pull "down" a common data op. One less op in the world. One more - // Phi, but Phis do not make code. - // Phi(op(A,B),op(Q,R),op(X,Y)) becomes - // op(Phi(A,Q,X), Phi(B,R,Y)). - Node op = in(1); - if( op.nIns()==3 && op.in(0)==null && same_op() ) { - assert !(op instanceof CFGNode); - 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); - } + // Phi(op(a,b,...),op(c,d,...)) -> op(Phi(a,c),Phi(b,d),...). + Node down; + if( same_op() && (down=drop_same_op())!=null ) return down; // If merging Phi(N, cast(N)) - we are losing the cast JOIN effects, so just remove. if( nIns()==3 ) { @@ -112,12 +101,72 @@ public class PhiNode extends Node { } private boolean same_op() { - for( int i=2; i<nIns(); i++ ) - if( in(1).getClass() != in(i).getClass() ) - return false; + Node op = in(1); + if( op instanceof CFGNode || op instanceof ConstantNode || op instanceof PhiNode || + op instanceof ProjNode || op instanceof NewNode || op instanceof ScopeNode || op instanceof MemMergeNode ) return false; + // A bulk Phi must split its aliases before factoring precise Stores. + if( this instanceof BulkMemPhiNode ) return false; + // Bulk splitting currently identifies parallel slices by Region/alias. + // Factoring can introduce another slice at a different memory point; + // wait until that Region's bulk partitioning has finished. + if( op instanceof MemOpNode ) + for( Node use : region()._outputs ) + if( use instanceof BulkMemPhiNode ) { + use.addDepForwards(this); + return false; + } + Node busy = null; + for( int i=1; i<nIns(); i++ ) { + Node n = in(i); + if( region().in(i).addDep(this)._type==Type.XCONTROL || + op.getClass()!=n.getClass() || n.nIns()!=op.nIns() || n.in(0)!=null || !op.eq(n) ) return false; + if( n instanceof MemOpNode mem && !mem.canDrop((MemOpNode)op,this) ) return false; + // Moving a Store must remove the old effect, not duplicate it. + // Allow one shared arm: the other arms disappear into the single + // factored operation, usually reducing the total operation count. + n.addDepForwards(this); + if( n.nOuts()>1 ) { + for( Node use : n._outputs ) + if( use!=null && use!=this ) use.addDepForwards(this); + if( n instanceof StoreNode || busy!=null ) return false; + busy=n; + } + for( int j=1; j<n.nIns(); j++ ) + if( n.in(j) instanceof ScopeNode || ((n.in(j)==null) != (op.in(j)==null)) ) return false; + } return true; } + private Node drop_same_op() { + Node op = in(1), cp = op.copyEmpty(); + cp._type = null; + cp.addDef(null); + for( int j=1; j<op.nIns(); j++ ) { + Node x = op.in(j); + boolean different = false; + for( int i=2; i<nIns(); i++ ) + if( in(i).in(j)!=x ) different=true; + if( different ) { + Node[] ins = new Node[nIns()]; + ins[0] = region(); + Type t = Type.TOP; + for( int i=1; i<nIns(); i++ ) { + ins[i] = in(i).in(j); + t = t.meet(ins[i]._type); + } + PhiNode phi = j==1 && op instanceof MemOpNode mem + ? new MemPhiNode(_label,mem._alias,ins) + : PhiNode.make(_label,t.glb(),ins); + x = phi.peephole(); + } + cp.addDef(x); + } + // Factoring must not widen the result (e.g. correlated And operands). + if( cp.compute().isa(compute()) ) return cp; + cp.kill(); + return null; + } + /** * If only single unique input, return it */ diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/RegionNode.java index 9e8b2611..e0bee5f4 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/node/RegionNode.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/node/RegionNode.java @@ -68,7 +68,7 @@ public class RegionNode extends CFGNode { if( !hasPhi() && // No Phi users, just a control user in(1) instanceof CProjNode p1 && in(2) instanceof CProjNode p2 && - p1.in(0)==p2.in(0) && + p1.addDepForwards(this).in(0)==p2.addDepForwards(this).in(0) && p1.in(0) instanceof IfNode iff ) return iff.ctrl(); diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/ReturnNode.java index e12f1fef..725c704f 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -4,33 +4,17 @@ 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 all preceding memory effects and the return value alive: {ctrl, $mem, value}. */ public class ReturnNode extends CFGNode { public ReturnNode(Node ctrl, Node data, ScopeNode scope) { - super(ctrl, data); - // Add memory slices to Return, so all memory updates are live-on-exit. - // We look up memory slices by the naming convention that they start with $. - // We could also use implicit knowledge that all memory projects are at offset >= 2 - if( scope != null ) { - String[] names = scope.reverseNames(); - for (String name: names) { - if (!name.equals("$ctrl") && name.startsWith("$")) - addDef(scope.lookup(name)); - } - } + // A synthetic never-taken loop exit has no source scope. + super(ctrl, scope == null ? null : scope.lookup("$mem"), data); } public Node ctrl() { return in(0); } - public Node expr() { return in(1); } + public Node mem () { return in(1); } + public Node expr() { return in(2); } @Override public String label() { return "Return"; } @@ -44,7 +28,8 @@ public class ReturnNode extends CFGNode { @Override public Type compute() { - return TypeTuple.make(ctrl()._type,expr()._type); + // Return exposes the complete memory state. + return TypeTuple.make(ctrl()._type,TypeMem.BOT,expr()._type); } @Override diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/ScopeNode.java index 9c4c4403..957202fd 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -141,7 +141,7 @@ public class ScopeNode extends Node { // Set real Phi in the loop head // The phi takes its one input (no backedge yet) from a recursive // lookup, which might have insert a Phi in every loop nest. - : loop.setDef(idx,new PhiNode(name, lookupDeclaredType(name),loop.ctrl(),loop.update(name,null,nestingLevel),null).peephole()); + : 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 @@ -205,7 +205,7 @@ public class ScopeNode extends Node { if( in(i) != that.in(i) ) // No need for redundant Phis // If we are in lazy phi mode we need to a lookup // by name as it will trigger a phi creation - setDef(i, new PhiNode(ns[i], this.lookupDeclaredType(ns[i]), r, this.lookup(ns[i]), that.lookup(ns[i])).peephole()); + 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(); diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/StartNode.java index 5964e19f..eaf00be8 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -1,53 +1,16 @@ 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.HashSet; -/** - * 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. - */ +/** Start supplies control, whole memory, and the argument, in that order. */ public class StartNode extends CFGNode implements MultiNode { - // This field is NOT final, because the tuple expands as we add memory aliases - TypeTuple _args; + private final TypeTuple _args; public StartNode(Type[] args) { super(); - _args = TypeTuple.make(args); - _type = _args; - } - - /** - * Creates a projection for each of the struct's fields, using the field alias - * as the key. - */ - public void addMemProj( TypeStruct ts, ScopeNode scope) { - if( ts._fields.length==0 ) return; - // Expand args type for more memory projections - Type[] args = _args._types; - int max = args.length; - for( Field f : ts._fields ) - max = Math.max(max,f._alias); - args = Arrays.copyOf(args, max+1); - // For each of the fields we now add a mem projection. - for( Field f : ts._fields ) { - TypeMem tm_decl = TypeMem.make(f._alias,f._type.glb()); - args[f._alias] = tm_decl.dual(); - String name = Parser.memName(f._alias); - Node n = new ProjNode(this, f._alias, name); // No 'compute' until ScopeNode gets typed - n._type = args[f._alias]; - scope.define(name, tm_decl, n); - } - for (int i = 0; i < args.length; i++) - if (args[i] == null) - args[i] = Type.TOP; // We parsed a nested struct/array. Avoid nulls until a caller replaces this alias. _type = _args = TypeTuple.make(args); } diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/StoreNode.java index ec7e0694..5c74fa61 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter15/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; @@ -26,6 +27,10 @@ public class StoreNode extends MemOpNode { } // Debugger label + @Override boolean canDrop(MemOpNode other, Node dep) { + return super.canDrop(other,dep) && _init==((StoreNode)other)._init; + } + @Override public String label() { return "st_"+mlabel(); } @Override public boolean isMem() { return true; } @@ -38,21 +43,22 @@ public class StoreNode extends MemOpNode { @Override public Type compute() { - Type val = val()._type; - TypeMem mem = (TypeMem)mem()._type; // Invariant - Type t = mem._alias==_alias - ? val.meet(mem._t) // Meet into existing memory - : Type.BOTTOM; + Type t = val()._type.meet(MemMergeNode.contents(mem(),_alias,this)); return TypeMem.make(_alias,t); } @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 off()==st.off() && // And same offset ptr()._type instanceof TypeMemPtr && // No bother if weird dead pointers // Must have exactly one use of "this" or you get weird diff --git a/chapter15/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter15/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index f5efd3e8..99920d24 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -54,7 +54,7 @@ public class SimpleGraphAdapter extends GraphAdapter<Node> { return n instanceof CFGNode ? 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 || n instanceof ReturnNode || n instanceof NewNode) ) return Role.MEM; Node def = n.in(i); if( def != null && isMem(def) ) return Role.MEM; return Role.DATA; diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter04Test.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter04Test.java index a3109ff7..14d31245 100644 --- a/chapter15/src/test/java/com/seaofnodes/simple/Chapter04Test.java +++ b/chapter15/src/test/java/com/seaofnodes/simple/Chapter04Test.java @@ -84,7 +84,10 @@ public class Chapter04Test { StopNode stop = parser.parse(); ReturnNode ret = stop.ret(); assertTrue(ret.in(0) instanceof CProjNode); - assertTrue(ret.in(1) instanceof ProjNode); + assertTrue(ret.in(1) instanceof ProjNode); + assertEquals(1, ((ProjNode)ret.in(1))._idx); + assertTrue(ret.in(2) instanceof ProjNode); + assertEquals(2, ((ProjNode)ret.in(2))._idx); } @Test diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter05Test.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter05Test.java index 70f4ccea..cd646126 100644 --- a/chapter15/src/test/java/com/seaofnodes/simple/Chapter05Test.java +++ b/chapter15/src/test/java/com/seaofnodes/simple/Chapter05Test.java @@ -22,7 +22,7 @@ else { return a;"""); StopNode ret = parser.parse().iterate(); - assertEquals("return Phi(Region20,(arg+2),(arg-3));", ret.toString()); + assertEquals("return Phi(Region21,(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(Region19,4,3);", ret.toString()); + assertEquals("return Phi(Region20,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(Region23,2,3));", ret.toString()); + assertEquals("return ((arg*2)+Phi(Region24,2,3));", ret.toString()); } @Test @@ -80,7 +80,7 @@ else a=b+1; return a+b;"""); StopNode ret = parser.parse().iterate(); - assertEquals("return ((Phi(Region34,(arg*2),arg)+arg)+Phi(Region,4,5));", ret.toString()); + assertEquals("return ((Phi(Region35,(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(Region38,Phi(Region24,2,3),Phi(Region36,4,5));", stop.toString()); + assertEquals("return Phi(Region39,Phi(Region25,2,3),Phi(Region37,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(Region17,1,0))+Phi(Region33,2,0));", stop.toString()); + assertEquals("return ((arg+Phi(Region18,1,0))+Phi(Region34,2,0));", stop.toString()); } @Test @@ -130,7 +130,7 @@ if( arg==1 ) } return a;"""); StopNode ret = parser.parse().iterate(); - assertEquals("return (arg==Phi(Region19,3,2));", ret.toString()); + assertEquals("return (arg==Phi(Region20,3,2));", ret.toString()); } @Test diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter06Test.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter06Test.java index ae37a867..d2c46df5 100644 --- a/chapter15/src/test/java/com/seaofnodes/simple/Chapter06Test.java +++ b/chapter15/src/test/java/com/seaofnodes/simple/Chapter06Test.java @@ -65,7 +65,7 @@ else b=5; return b;""", TypeInteger.BOT); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region39,42,5);", stop.toString()); + assertEquals("return Phi(Region40,42,5);", stop.toString()); } @Test @@ -84,7 +84,7 @@ else b=5; return b;""", TypeInteger.BOT); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region32,2,5);", stop.toString()); + assertEquals("return Phi(Region33,2,5);", stop.toString()); } @Test @@ -144,7 +144,7 @@ else return a; """, TypeInteger.BOT); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region43,3,Phi(Region41,4,5));", stop.toString()); + assertEquals("return Phi(Region44,3,Phi(Region42,4,5));", stop.toString()); } @Test @@ -200,7 +200,7 @@ if( arg ) { return a+b; """); StopNode ret = parser.parse().iterate(); - assertEquals("return Phi(Region28,4,1);", ret.toString()); + assertEquals("return Phi(Region29,4,1);", ret.toString()); } @@ -251,7 +251,7 @@ if( arg ) { return a+b+c; """); StopNode ret = parser.parse().iterate(); - assertEquals("return (Phi(Region40,Phi(Region25,2,3),0)+Phi(Region,3,1));", ret.toString()); + assertEquals("return (Phi(Region41,Phi(Region26,2,3),0)+Phi(Region,3,1));", ret.toString()); } diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter07Test.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter07Test.java index 29aa7eaf..443b0d17 100644 --- a/chapter15/src/test/java/com/seaofnodes/simple/Chapter07Test.java +++ b/chapter15/src/test/java/com/seaofnodes/simple/Chapter07Test.java @@ -17,7 +17,7 @@ public class Chapter07Test { return arg; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop8,arg,(Phi_arg+1));", stop.toString()); + assertEquals("return Phi(Loop9,arg,(Phi_arg+1));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -34,7 +34,7 @@ if(arg){}else{ return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region26,1,Phi(Loop15,1,(Phi_a+1)));", stop.toString()); + assertEquals("return Phi(Region27,1,Phi(Loop16,1,(Phi_a+1)));", stop.toString()); } @Test @@ -54,7 +54,7 @@ while(i < arg) { return sum; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,0,Phi(Loop22,Phi_sum,(Phi_sum+Phi(Loop,0,(Phi_j+1)))));", stop.toString()); + assertEquals("return Phi(Loop11,0,Phi(Loop23,Phi_sum,(Phi_sum+Phi(Loop,0,(Phi_j+1)))));", stop.toString()); } @Test @@ -70,7 +70,7 @@ while(a < 10) { return b; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop10,2,Phi(Region29,Phi_b,4));", stop.toString()); + assertEquals("return Phi(Loop11,2,Phi(Region30,Phi_b,4));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -89,7 +89,7 @@ while(a < 10) { return b; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,2,(Phi(Region29,Phi_b,4)+1));", stop.toString()); + assertEquals("return Phi(Loop11,2,(Phi(Region30,Phi_b,4)+1));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -106,7 +106,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop9,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -122,7 +122,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -135,7 +135,7 @@ while(arg) a = 2; return a; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop9,1,2);", stop.toString()); + assertEquals("return Phi(Loop10,1,2);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -148,7 +148,7 @@ while(arg) a = 2; return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,1,2);", stop.toString()); + assertEquals("return Phi(Loop10,1,2);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -164,7 +164,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop9,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -180,7 +180,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -197,7 +197,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop11,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -214,7 +214,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop11,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter08Test.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter08Test.java index 7760ae09..89ce8313 100644 --- a/chapter15/src/test/java/com/seaofnodes/simple/Chapter08Test.java +++ b/chapter15/src/test/java/com/seaofnodes/simple/Chapter08Test.java @@ -23,7 +23,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region37,Phi(Region26,Phi(Loop8,arg,(Phi_arg+1)),Add),Add);", stop.toString()); + assertEquals("return Phi(Region38,Phi(Region27,Phi(Loop9,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)); @@ -46,7 +46,7 @@ while(arg < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,1,Phi(Region43,Phi_a,(Phi_a+1)));", stop.toString()); + assertEquals("return Phi(Loop10,1,Phi(Region44,Phi_a,(Phi_a+1)));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -65,7 +65,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region35,Phi(Loop8,arg,(Phi_arg+1)),Add);", stop.toString()); + assertEquals("return Phi(Region36,Phi(Loop9,arg,(Phi_arg+1)),Add);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof RegionNode); } @@ -81,7 +81,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region26,Phi(Loop8,arg,(Phi_arg+1)),Add);", stop.toString()); + assertEquals("return Phi(Region27,Phi(Loop9,arg,(Phi_arg+1)),Add);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof RegionNode); } @@ -100,7 +100,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop8,arg,(Phi_arg+1));", stop.toString()); + assertEquals("return Phi(Loop9,arg,(Phi_arg+1));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -117,7 +117,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop8,arg,(Phi_arg+1));", stop.toString()); + assertEquals("return Phi(Loop9,arg,(Phi_arg+1));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -185,7 +185,7 @@ while(arg < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region29,Phi(Loop9,1,(Phi_a+1)),Add);", stop.toString()); + assertEquals("return Phi(Region30,Phi(Loop10,1,(Phi_a+1)),Add);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof RegionNode); } @@ -201,7 +201,7 @@ while(1) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return (Phi(Loop9,1,Add)+1);", stop.toString()); + assertEquals("return (Phi(Loop10,1,Add)+1);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode prj && prj._idx==1 ); } diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter09Test.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter09Test.java index 64bbf245..49ee75a2 100644 --- a/chapter15/src/test/java/com/seaofnodes/simple/Chapter09Test.java +++ b/chapter15/src/test/java/com/seaofnodes/simple/Chapter09Test.java @@ -69,7 +69,7 @@ while (arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,arg,(Phi_arg+2));", stop.toString()); + assertEquals("return Phi(Loop10,arg,(Phi_arg+2));", stop.toString()); Assert.assertEquals(11L, Evaluator.evaluate(stop, 1)); } @@ -86,7 +86,7 @@ while (arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,arg,(Phi_arg+4));", stop.toString()); + assertEquals("return Phi(Loop11,arg,(Phi_arg+4));", stop.toString()); Assert.assertEquals(13L, Evaluator.evaluate(stop, 1)); } @@ -115,7 +115,7 @@ while (arg) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop16,arg,(Phi_arg+1));", stop.toString()); + assertEquals("return Phi(Loop17,arg,(Phi_arg+1));", stop.toString()); } @Test @@ -185,7 +185,7 @@ while(arg) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop11,0,(-(Phi_a+3)));", stop.toString()); + assertEquals("return Phi(Loop12,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(Loop8,arg,(Phi_arg-1));", stop.toString()); + assertEquals("return Phi(Loop9,arg,(Phi_arg-1));", stop.toString()); } @Test diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter10Test.java index bd56a944..a5e3a9e0 100644 --- a/chapter15/src/test/java/com/seaofnodes/simple/Chapter10Test.java +++ b/chapter15/src/test/java/com/seaofnodes/simple/Chapter10Test.java @@ -1,6 +1,11 @@ package com.seaofnodes.simple; +import com.seaofnodes.simple.node.Node; import com.seaofnodes.simple.node.StopNode; +import com.seaofnodes.simple.node.LoadNode; +import com.seaofnodes.simple.node.StoreNode; +import com.seaofnodes.simple.evaluator.Evaluator; +import java.util.BitSet; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -161,7 +166,7 @@ while (arg) { return bar.a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop13,0,(Phi_a+2));", stop.toString()); + assertEquals("return Phi(Loop15,0,(Phi_a+2));", stop.toString()); } @Test @@ -213,7 +218,7 @@ if( bar ) bar.a = 1; return bar; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region33,(*void)Phi(Region20,null,Bar),null);", stop.toString()); + assertEquals("return Phi(Region36,(*void)Phi(Region22,null,Bar),null);", stop.toString()); } @Test @@ -229,7 +234,7 @@ else bar.a = 1; return rez; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region39,4,3);", stop.toString()); + assertEquals("return Phi(Region42,4,3);", stop.toString()); } @Test @@ -283,7 +288,7 @@ while( i.x < i.len ) { return sum; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop18,0,(Phi(Loop,0,(Phi_x+1))+Phi_sum));", stop.toString()); + assertEquals("return Phi(Loop19,0,(Phi(Loop,0,(Phi_x+1))+Phi_sum));", stop.toString()); } @@ -304,7 +309,7 @@ while(arg) { return ret; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop13,s0,Phi(Region37,s0,Phi_ret));", stop.toString()); + assertEquals("return Phi(Loop15,s0,Phi(Region42,s0,Phi_ret));", stop.toString()); } @Test @@ -322,7 +327,7 @@ while(arg) { return ret; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop17,s0,Phi(Region38,s0,Phi_ret));", stop.toString()); + assertEquals("return Phi(Loop21,s0,Phi(Region43,s0,Phi_ret));", stop.toString()); } @@ -339,7 +344,7 @@ while(arg < 10) { return ret; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop13,s0,Phi(Region35,s0,Phi_ret));", stop.toString()); + assertEquals("return Phi(Loop15,s0,Phi(Region39,s0,Phi_ret));", stop.toString()); } @Test @@ -457,4 +462,120 @@ 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)); + } + + 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()); + } + } + + @Test public void testKeepLoadsAtMerge() { + StopNode stop = new Parser(""" + struct S { int x; } + S a = new S; S b = new S; + a.x = arg; b.x = arg+1; + S p = a; S q = b; + if (arg<0) { p=b; q=a; } + int v; + if (arg>1) v=p.x; else v=q.x; + return v; + """).parse().iterate(); + // The one-step safety check stops at the memory aggregate. + assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet())); + assertEquals(-1L,Evaluator.evaluate(stop,-1)); + assertEquals(1L,Evaluator.evaluate(stop,0)); + assertEquals(3L,Evaluator.evaluate(stop,3)); + } + + @Test public void testDropStores() { + StopNode stop = new Parser(""" + struct S { int x; } + S s = new S; + if (arg) s.x=arg+1; else s.x=arg+2; + return s; + """).parse().iterate(); + assertEquals(1,countMemoryNodes(stop,StoreNode.class,new BitSet())); + assertEquals(2L,((Evaluator.Obj)Evaluator.evaluate(stop,0)).fields()[0]); + assertEquals(4L,((Evaluator.Obj)Evaluator.evaluate(stop,3)).fields()[0]); + } + + @Test public void testKeepReadsBeforeWrites() { + StopNode stop = new Parser(""" + struct S { int x; int y; } + S a = new S; S b = new S; + a.x=arg; b.x=arg+1; + S p=a; S q=b; + if (arg<0) { p=b; q=a; } + int v; + if (arg>1) { v=p.x; p.x=41; p.y=5; } + else { v=q.x; q.x=42; } + return v; + """).parse().iterate(); + assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet())); + assertEquals(-1L,Evaluator.evaluate(stop,-1)); + assertEquals(1L,Evaluator.evaluate(stop,0)); + assertEquals(3L,Evaluator.evaluate(stop,3)); + } + + private static int countMemoryNodes(Node n, Class<?> kind, BitSet seen) { + if (n==null || seen.get(n._nid)) return 0; + seen.set(n._nid); + int cnt=kind.isInstance(n) ? 1 : 0; + for (Node def : n._inputs) cnt+=countMemoryNodes(def,kind,seen); + return cnt; + } } diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter10bTest.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter10bTest.java new file mode 100644 index 00000000..6f094cfb --- /dev/null +++ b/chapter15/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/chapter15/src/test/java/com/seaofnodes/simple/Chapter11Test.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter11Test.java index 76581308..3421340f 100644 --- a/chapter15/src/test/java/com/seaofnodes/simple/Chapter11Test.java +++ b/chapter15/src/test/java/com/seaofnodes/simple/Chapter11Test.java @@ -325,7 +325,7 @@ while( prime <= arg ) { return primeCount; """); StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ return 0; return Phi(Loop20,1,Phi(Region87,Phi_primeCount,Phi(Region81,(Phi_primeCount+1),Phi_primeCount))); ]", stop.toString()); + assertEquals("Stop[ return 0; return Phi(Loop21,1,Phi(Region88,Phi_primeCount,Phi(Region82,(Phi_primeCount+1),Phi_primeCount))); ]", stop.toString()); assertEquals(0L, Evaluator.evaluate(stop, 1)); // No primes 1 or below assertEquals(1L, Evaluator.evaluate(stop, 2)); // 2 assertEquals(2L, Evaluator.evaluate(stop, 3)); // 2, 3 @@ -370,7 +370,7 @@ if (arg) { return i; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region40,.f,0);", stop.toString()); + assertEquals("return Phi(Region46,.f,0);", stop.toString()); } @Test @@ -477,7 +477,7 @@ while(arg) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop17,arg,Phi(Region42,.f,0));", stop.toString()); + assertEquals("return Phi(Loop21,arg,Phi(Region48,.f,0));", stop.toString()); } @Test diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter12Test.java index 4eaf81df..998053ec 100644 --- a/chapter15/src/test/java/com/seaofnodes/simple/Chapter12Test.java +++ b/chapter15/src/test/java/com/seaofnodes/simple/Chapter12Test.java @@ -64,7 +64,7 @@ while( 1 ) { return guess; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,(flt)arg,(((ToFloat/Phi_guess)+Phi_guess)/2.0));", stop.toString()); + assertEquals("return Phi(Loop10,(flt)arg,(((ToFloat/Phi_guess)+Phi_guess)/2.0));", stop.toString()); assertEquals(3.0, Evaluator.evaluate(stop, 9)); assertEquals(1.414213562373095, Evaluator.evaluate(stop, 2)); } diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter13Test.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter13Test.java index daf981bd..3313e731 100644 --- a/chapter15/src/test/java/com/seaofnodes/simple/Chapter13Test.java +++ b/chapter15/src/test/java/com/seaofnodes/simple/Chapter13Test.java @@ -35,7 +35,7 @@ while( arg ) { return head.next.i; """); try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Might be null accessing 'next'",e.getMessage()); } + catch( Exception e ) { assertEquals("Might be null accessing 'i'",e.getMessage()); } } @Test diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter14Test.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter14Test.java index 048cb93a..d64423de 100644 --- a/chapter15/src/test/java/com/seaofnodes/simple/Chapter14Test.java +++ b/chapter15/src/test/java/com/seaofnodes/simple/Chapter14Test.java @@ -59,7 +59,7 @@ while( b ) b = b + 456;// Truncate return b; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop9,123,((Phi_b+456)&255));", stop.toString()); + assertEquals("return Phi(Loop10,123,((Phi_b+456)&255));", stop.toString()); } @Test diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter15Test.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter15Test.java index 6ec7cc1a..8f9d2d14 100644 --- a/chapter15/src/test/java/com/seaofnodes/simple/Chapter15Test.java +++ b/chapter15/src/test/java/com/seaofnodes/simple/Chapter15Test.java @@ -12,6 +12,70 @@ import static org.junit.Assert.fail; public class Chapter15Test { + @Test public void testPartialAllocationMemory() { + StopNode stop = new Parser(""" + struct Other { int a; int b; int c; } + struct S { int x; flt y; S? next; } + Other other = new Other; + other.c = 7; + S a = new S; + a.x = arg; + a.y = 1.5; + S b = new S; + // S's aliases are global identities, not tuple or input positions. + // Bypass b's zeroing for a, preserving the unrelated Other slice. + return a.x + a.y + b.x + b.y + other.c; + """).parse().iterate(); + assertEquals(8.5, Evaluator.evaluate(stop,0)); + assertEquals(12.5, Evaluator.evaluate(stop,4)); + + stop = new Parser(""" + struct S { int x; flt y; S? next; } + S a = new S; S b = new S; + S p = a; if (arg) p = b; + // The memory result carries distinct int, float and pointer facts. + if (p.next != null) return 99; + return p.x + p.y; + """).parse().iterate(); + assertEquals("return 0.0;",stop.toString()); + assertEquals(0.0,Evaluator.evaluate(stop,1)); + } + + @Test public void testReadAcrossAllocation() { + StopNode stop = new Parser(""" + struct S { int x; } + 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; + S c = new S; + a.x = 33; b.x = 44; + // The partial input to c must not hide the read/write ordering. + // p can alias either object, so node inequality cannot bypass New. + return before*100 + p.x + c.x; + """).parse().iterate(); + assertEquals(1133L,Evaluator.evaluate(stop,0)); + assertEquals(2244L,Evaluator.evaluate(stop,1)); + } + + @Test public void testAllocationInLoop() { + StopNode stop = new Parser(""" + struct S { int x; flt y; S? next; } + S old = new S; old.x = 7; + S last = old; + while (arg > 0) { + S fresh = new S; + fresh.x = last.x + 1; + fresh.next = last; + last = fresh; + arg = arg - 1; + } + return old.x*100 + last.x; + """).parse().iterate(); + assertEquals(707L,Evaluator.evaluate(stop,0)); + assertEquals(712L,Evaluator.evaluate(stop,5)); + } + @Test public void testJig() { Parser parser = new Parser(""" @@ -34,7 +98,7 @@ if (arg) { return b[0] + b[1]; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region69,1,0);", stop.toString()); + assertEquals("return Phi(Region72,1,0);", stop.toString()); assertEquals(0L, Evaluator.evaluate(stop, 0)); assertEquals(1L, Evaluator.evaluate(stop, 1)); } @@ -151,7 +215,7 @@ else { return rez; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region118,1.2,Phi(Region115,2.3,.y));", stop.toString()); + assertEquals("return Phi(Region122,1.2,Phi(Region119,2.3,.y));", stop.toString()); assertEquals(3.14, Evaluator.evaluate(stop, 0)); } diff --git a/chapter15/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/chapter15/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java index 34921b94..9eed23de 100644 --- a/chapter15/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java +++ b/chapter15/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java @@ -187,7 +187,7 @@ public class Evaluator { Object[] body=null; int num; if( type.isAry() ) { - long sz = (Long)val(alloc.in(1)); + long sz = (Long)val(alloc.size()); long n = offToIdx(sz, type); if( n < 0 ) throw new NegativeArraySizeException(""+n); @@ -215,11 +215,7 @@ public class Evaluator { assert c instanceof TypeMemPtr; } } - Object[] mems = new Object[type._fields.length+2]; - // mems[0] is control - mems[1] = new Obj(type,body); // the ref - // mems[2+...] are memory aliases - return mems; + return new Object[]{new Obj(type,body),MEMORY}; } private Object load(LoadNode load) { @@ -312,6 +308,7 @@ public class Evaluator { case ToFloatNode cast -> (double)vall(cast.in(1)); case LoadNode load -> load(load); case StoreNode store -> store(store); + case MemMergeNode merge -> MEMORY; case NewNode alloc -> alloc(alloc); case CProjNode cproj -> ((Object[])val(cproj.ctrl()))[cproj._idx]; case ProjNode proj -> ((Object[])val( proj.in(0) ))[ proj._idx]; @@ -325,8 +322,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; @@ -337,7 +334,7 @@ public class Evaluator { case null: return Status.FALLTHROUGH; case ReturnNode ret: - return val(ret.in(1)); + return val(ret.expr()); case IfNode ifn: block = block.next()[isTrue(val(ifn.in(1))) ? 0 : 1]; if (block == null) return Status.FALLTHROUGH; diff --git a/chapter15/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java b/chapter15/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java index 59c15d3f..125f2f5e 100644 --- a/chapter15/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java +++ b/chapter15/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java @@ -31,6 +31,8 @@ import java.util.*; */ public class Scheduler { + private final HashMap<Node,ArrayList<NodeData>> readers = new HashMap<>(); + /** * A basic block with a schedule of the containing nodes. * @@ -252,7 +254,7 @@ public class Scheduler { // but they did not refine the placement // so do that now. var mem = l.in(1); - for(var out : mem._outputs) { + for(var out : l.antiDeps()) { if (out instanceof PhiNode p) { var r = p.in(0); for (int i = 1; i < p.nIns(); i++) { @@ -270,13 +272,9 @@ public class Scheduler { 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) od(out).ifPresent(this::decUsers); - } - } + var loads = readers.get(data.node); + if (loads != null) + for (var load : loads) decUsers(load); } // Now all nodes should be placed and have a block assigned @@ -442,7 +440,6 @@ public class Scheduler { 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()) { @@ -467,16 +464,16 @@ public class Scheduler { } else { for (var in : node._inputs) if (in != null && !(in instanceof CFGNode)) 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) od(out).ifPresent(d->d.users++); - } } + // Writes, including New behind an aggregate, schedule before their + // anti-dependent reads in this reverse scheduling pass. + for (var load : data.values()) + if (load.node instanceof LoadNode l) + for (var use : l.antiDeps()) + if ((use instanceof StoreNode || use instanceof NewNode) && data.containsKey(use)) { + load.users++; + readers.computeIfAbsent(use,k->new ArrayList<>()).add(load); + } } /** diff --git a/chapter16/README.md b/chapter16/README.md index a19c68db..1c598568 100644 --- a/chapter16/README.md +++ b/chapter16/README.md @@ -6,6 +6,7 @@ 2. [Initialization code](#initialization-code) 3. [Final fields](#final-fields) 4. [Multiple Decls](#multiple-declarations-of-the-same-type) +5. [Memory through constructors](#memory-through-constructors) In this chapter, we add constructors and final fields. @@ -125,4 +126,31 @@ struct Point { int x,y,z; }; // Three fields declared ``` int !x=3,!y=5; // Two int variables declared, both are final and initialized -```
\ No newline at end of file +``` +## Memory through constructors + +The lazy memory partitioning from [Chapter 10b](../chapter10b/README.md) continues +through constructors. The parser tracks one `$mem` variable alongside its scalar +variables. Branches and loops merge that binding with BulkMemPhi; field access +splits out precise MemPhis when needed. The variable records for types and final +fields live in ScopeNode. There is no separate parser table of memory aliases. + +A constructor computes its field values before the allocation. Reads, writes, +and loops in its body update the same `$mem` binding as ordinary code. New then +takes `{ctrl, $mem, size, field values...}` and produces `{ptr, $mem}`. The input +memory is a partial MemMerge containing the struct's aliases; the output memory +covers those same aliases. A whole-memory MemMerge preserves unrelated slices. +Arrays use the same layout, supplying their length and default element value as +initializer inputs. + +A Load from its own New can use the matching initializer input. Alias contents +combine the initializer's type with incoming contents from older objects, so a +new object does not erase facts or effects for existing objects. Loads bypass +only allocations proven to be distinct. Scheduling follows partial aggregates +when establishing read-before-write order. + +Sharing one allocation memory result makes Store-to-New folding more +conservative: the existing sole-use check now sees users of every covered +alias. This can leave a field Load where separate memory projections previously +allowed a constant fold. Phi factoring also retains the simple one-step Load +safety check. diff --git a/chapter16/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java b/chapter16/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java index 31302ffd..bb109fc9 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java @@ -148,13 +148,8 @@ public abstract class GlobalCodeMotion { // Loads need their memory inputs' uses also done if( n instanceof LoadNode ld ) - for( Node memuse : ld.mem()._outputs ) - if( !(memuse instanceof PhiNode) && - late[memuse._nid]==null && - // Load-use directly defines memory - (memuse._type instanceof TypeMem || - // Load-use indirectly defines memory - (memuse._type instanceof TypeTuple tt && tt._types[ld._alias] instanceof TypeMem)) ) + for( Node memuse : ld.antiDeps() ) + if( late[memuse._nid]==null ) continue outer; // All uses done, schedule @@ -166,9 +161,7 @@ public abstract class GlobalCodeMotion { for( Node def : n._inputs ) { if( def==null ) continue; if( late[def._nid]==null ) work.push(def); - for( Node out : def._outputs ) - if( out instanceof LoadNode ld && late[ld._nid]==null ) - work.push(ld); + wakeLoads(def,late,work,new BitSet()); } if( n instanceof LoopNode loop ) for( Node phi : loop._outputs ) @@ -225,13 +218,24 @@ public abstract class GlobalCodeMotion { best instanceof IfNode; } + // An allocation may consume a load's memory through a partial aggregate. + private static void wakeLoads(Node def, CFGNode[] late, WorkList<Node> work, BitSet visit) { + if( visit.get(def._nid) ) return; + visit.set(def._nid); + for( Node out : def._outputs ) + if( out instanceof LoadNode ld && late[ld._nid]==null ) work.push(ld); + if( def instanceof MemMergeNode ) + for( int i=1; i<def.nIns(); i++ ) + if( def.in(i)!=null ) wakeLoads(def.in(i),late,work,visit); + } + private static CFGNode find_anti_dep(CFGNode lca, LoadNode load, CFGNode early, CFGNode[] late, int[] anti) { // We could skip final-field loads here. // Walk LCA->early, flagging Load's block location choices for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() ) anti[cfg._nid] = load._nid; // Walk load->mem uses, looking for Stores causing an anti-dep - for( Node mem : load.mem()._outputs ) { + for( Node mem : load.antiDeps() ) { switch( mem ) { case StoreNode st: assert late[st._nid]!=null; @@ -248,10 +252,6 @@ public abstract class GlobalCodeMotion { if( phi.in(i)==load.mem() ) lca = anti_dep(load,phi.region().cfg(i),load.mem().cfg0(),lca,null,anti); break; - case LoadNode ld: break; // Loads do not cause anti-deps on other loads - case ReturnNode ret: break; // Load must already be ahead of Return - case ScopeMinNode ret: break; // Mem uses now on ScopeMin - case NeverNode never: break; default: throw Utils.TODO(); } } diff --git a/chapter16/src/main/java/com/seaofnodes/simple/Parser.java b/chapter16/src/main/java/com/seaofnodes/simple/Parser.java index 1f20a913..a7d31584 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/Parser.java @@ -163,11 +163,8 @@ public class Parser { _xScopes.push(_scope); // Enter a new scope for the initial control and arguments _scope.push(); - ScopeMinNode mem = new ScopeMinNode(); - mem.addDef(null); _scope.define(ScopeNode.CTRL, Type.CONTROL , false, new CProjNode(START, 0, ScopeNode.CTRL).peephole()); - mem.addDef( new ProjNode(START, 1, ScopeNode.MEM0).peephole()); - _scope.define(ScopeNode.MEM0, TypeMem.TOP , false, mem .peephole()); + _scope.define(ScopeNode.MEM0, TypeMem.BOT , false, new ProjNode(START, 1, ScopeNode.MEM0).peephole()); _scope.define(ScopeNode.ARG0, TypeInteger.BOT, false, new ProjNode(START, 2, ScopeNode.ARG0).peephole()); // Parse whole program @@ -430,7 +427,7 @@ public class Parser { // Defining a new variable vs updating an old one if( !isDecl ) { // Assigning over an existing name // Lookup - ScopeMinNode.Var def = _scope.lookup(name); + ScopeNode.Var def = _scope.lookup(name); if( def==null ) throw error("Undefined name '" + name + "'"); // TOP fields are for late-initialized fields; these have never @@ -526,7 +523,7 @@ public class Parser { Field[] fs = new Field[varlen-lexlen]; for( int i=lexlen; i<varlen; i++ ) { s.addDef(_scope.in(i)); - ScopeMinNode.Var v = _scope._vars.at(i); + ScopeNode.Var v = _scope._vars.at(i); fs[i-lexlen] = Field.make(v._name,v._type,ALIAS++,v._final); } TypeStruct ts = s._ts = TypeStruct.make(typeName, fs); @@ -758,7 +755,7 @@ public class Parser { // Expect an identifier now String name = _lexer.matchId(); if( name == null) throw errorSyntax("an identifier or expression"); - ScopeMinNode.Var n = _scope.lookup(name); + ScopeNode.Var n = _scope.lookup(name); if( n!=null ) return _scope.in(n); throw error("Undefined name '" + name + "'"); } @@ -822,21 +819,25 @@ public class Parser { Field[] fs = obj._fields; if( fs==null ) throw error("Unknown struct type '" + obj._name + "'"); - int len = fs.length; - Node[] ns = new Node[2+len+len]; - ns[0] = ctrl(); // Control in slot 0 - // Total allocated length in bytes - ns[1] = size; - // Memory aliases for every field - for( int i = 0; i < len; i++ ) - ns[2 +i] = memAlias(fs[i]._alias); - // Initial values for every field - for( int i = 0; i < len; i++ ) - ns[2+len+i] = init.get(i+idx); - Node nnn = new NewNode(TypeMemPtr.make(obj), ns).peephole(); - for( int i = 0; i < len; i++ ) - memAlias(fs[i]._alias, new ProjNode(nnn,i+2,memName(fs[i]._alias)).peephole()); - return new ProjNode(nnn,1,obj._name).peephole(); + Node prior = mem().keep(); + MemMergeNode input = new MemMergeNode(null); + for( Field f : fs ) input.alias(f._alias,prior); + // Constructor values follow control, memory, and allocation size. + Node[] ns = new Node[3+fs.length]; + ns[0] = ctrl(); + ns[1] = input.peephole(); + ns[2] = size; + for( int i=0; i<fs.length; i++ ) ns[3+i] = init.get(idx+i); + Node nnn = new NewNode(TypeMemPtr.make(obj),ns).peephole().keep(); + Node ptr = new ProjNode(nnn,0,obj._name).peephole().keep(); + Node out = new ProjNode(nnn,1,"$mem").peephole().keep(); + MemMergeNode after = new MemMergeNode(prior); + for( Field f : fs ) after.alias(f._alias,out); + mem(after.peephole()); + out.unkeep(); + nnn.unkeep(); + prior.unkeep(); + return ptr.unkeep(); } private static final Ary<Node> ALTMP = new Ary<>(Node.class); @@ -845,16 +846,23 @@ public class Parser { int scale= ary.aryScale(); Node size = new AddNode(con(base),new ShlNode(len,con(scale)).peephole()).peephole(); ALTMP.clear(); ALTMP.add(len); ALTMP.add(con(ary._fields[1]._type.makeInit())); - return newStruct(ary,size,0,ALTMP); + Node ptr = newStruct(ary,size,0,ALTMP); + len.unkeep(); + return ptr; } - // 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.mem(alias ); } - private void memAlias(int alias, Node st) { _scope.mem(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.mem(); } + private void mem(Node n) { _scope.mem(n); } + + private void store(String name, int alias, Type glb, Node ptr, Node off, Node val, boolean init, Node ctrl) { + Node prior = mem().keep(); + Node st = new StoreNode(name,alias,glb,prior,ptr,off,val,init); + st.setDef(0,ctrl); + st = st.peephole(); + mem(new MemMergeNode(prior,alias,st).peephole()); + prior.unkeep(); + } /** * Parse postfix expression. For now this is just a field @@ -894,6 +902,7 @@ public class Parser { // Get field type and layout offset from base type and field index fidx Field f = base._fields[fidx]; // Field from field index + expr.keep(); Node off = name.equals("[]") // If field is an array body // Array index math ? new AddNode(con(base.aryBase()),new ShlNode(require(parseExpression(),"]"),con(base.aryScale())).peephole()).peephole() @@ -902,19 +911,14 @@ public class Parser { // Disambiguate "obj.fld==x" boolean test from "obj.fld=x" field assignment if( matchOpx('=','=') ) { - Node val = parseExpression(); - // Auto-truncate when storing to narrow fields - val = zsMask(val,f._type); - Node st = new StoreNode(name, f._alias, f._type, memAlias(f._alias), expr, off, val, false); - // Arrays include control, as a proxy for a safety range check. - // Structs don't need this; they only need a NPE check which is - // done via the type system. - if( base.isAry() ) st.setDef(0,ctrl()); - memAlias(f._alias, st.peephole()); - return val; // "obj.a = expr" returns the expression while updating memory + off.keep(); + Node val = zsMask(parseExpression(),f._type).keep(); + // Array control stands in for the future bounds check. + store(name,f._alias,f._type,expr.unkeep(),off.unkeep(),val,false,base.isAry() ? ctrl() : null); + return val.unkeep(); } - Node load = new LoadNode(name, f._alias, f._type.glb(), memAlias(f._alias), expr, off); + Node load = new LoadNode(name, f._alias, f._type.glb(), mem(), expr.unkeep(), off); // Arrays include control, as a proxy for a safety range check // Structs don't need this; they only need a NPE check which is // done via the type system. diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java new file mode 100644 index 00000000..84f97189 --- /dev/null +++ b/chapter16/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 mmm.in(1)!=null && missingAlias(mmm)==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); + 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); + 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; + } + + // Every explicit slot requests that alias, including a partial allocation + // input whose slot still points to this bulk Phi. + private int missingAlias(MemMergeNode mmm) { + for( int alias=2; alias<mmm.nIns(); alias++ ) + if( mmm.in(alias)!=null && + mmm.alias(alias)!=mmm.in(1) && + !_aliases.get(alias) ) + 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,Type.BOTTOM)); + 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/chapter16/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/LoadNode.java index 60be3baa..46938f7b 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -1,9 +1,10 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.type.*; import java.util.BitSet; +import java.util.ArrayList; /** * Load represents extracting a value from inside a memory object, @@ -23,6 +24,25 @@ public class LoadNode extends MemOpNode { super(name, alias, glb, mem, ptr, off); } + @Override boolean canDrop(MemOpNode other, Node dep) { + return super.canDrop(other,dep) && !clobbered(dep); + } + + // Check only immediate memory users. Stores clobber memory; Phis and + // aggregates might lead to a clobber, so stop rather than search further. + private boolean clobbered(Node dep) { + Node mem = mem(); + mem.addDepForwards(dep); + for( Node use : mem._outputs ) { + if( use==null ) continue; + use.addDepForwards(dep); + if( use instanceof StoreNode || use instanceof PhiNode || + use instanceof MemMergeNode || + use instanceof NewNode ) return true; + } + return false; + } + // Debugger label @Override public String label() { return "ld_"+mlabel(); } @Override @@ -30,28 +50,29 @@ public class LoadNode extends MemOpNode { @Override public Type compute() { - if( mem()._type instanceof TypeMem mem && - // No constant folding if ptr might null-check - _declaredType != mem._t && err()==null ) { - return _declaredType.join(mem._t); - } - return _declaredType; + Type t = MemMergeNode.contents(mem(),_alias,this); + // No constant folding if the pointer still needs a null check. + return err()==null ? _declaredType.join(t) : _declaredType; } @Override public Node idealize() { + if( mem() instanceof MemMergeNode merge ) { + setDef(1,IterPeeps.add(merge.alias(_alias))); + return this; + } Node ptr = ptr(); // Simple Load-after-Store on same address. if( mem() instanceof StoreNode st && - ptr == st.ptr() && off() == st.off() ) { // Must check same object + _alias==st._alias && ptr == st.ptr() && off() == st.off() ) { // Must check same object assert _name.equals(st._name); // Equiv class aliasing is perfect return st.val(); } // Simple Load-after-New on same address. if( mem() instanceof ProjNode p && p.in(0) instanceof NewNode nnn && - ptr == nnn.proj(1) ) // Must check same object + ptr == nnn.proj(0) ) // Must check same object return nnn.in(nnn.findAlias(_alias)); // Load from New init // Load-after-Store on same address, but bypassing provably unrelated @@ -60,12 +81,16 @@ public class LoadNode extends MemOpNode { Node mem = mem(); outer: while( true ) { + mem.addDep(this); switch( mem ) { + case MemMergeNode merge: + mem = merge.alias(_alias); + break; case StoreNode st: - if( ptr == st.ptr() && off() == st.off() ) + if( _alias==st._alias && ptr == st.ptr() && off() == st.off() ) return st.val(); // Proved equal // Can we prove unequal? Offsets do not overlap? - if( !off()._type.join(st.off()._type).isHigh() && // Offsets overlap + if( _alias==st._alias && !off()._type.join(st.off()._type).isHigh() && // Offsets overlap !neverAlias(ptr,st.ptr()) ) // And might alias break outer; // Cannot tell, stop trying // Pointers cannot overlap @@ -79,11 +104,11 @@ public class LoadNode extends MemOpNode { return nnn1.in(nnn1.findAlias(_alias)); // Load from New init if( !(ptr instanceof ProjNode pproj && pproj.in(0) instanceof NewNode nnn2) ) break outer; // Cannot tell, ptr not related to New - mem = nnn1.in(_alias);// Bypass unrelated New + mem = nnn1.mem();// Bypass unrelated New break; - } else throw Utils.TODO(); + } else break outer; default: - throw Utils.TODO(); + break outer; } } @@ -94,7 +119,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 && off() instanceof ConstantNode ) { // Profit on RHS/Loop backedge if( profit(memphi,2) || @@ -110,7 +135,9 @@ public class LoadNode extends MemOpNode { } private Node ld( int idx ) { Node mem = mem(), ptr = ptr(); - return new LoadNode(_name,_alias,_declaredType,mem.in(idx),ptr instanceof PhiNode && ptr.in(0)==mem.in(0) ? ptr.in(idx) : ptr, off()).peephole(); + LoadNode ld = new LoadNode(_name,_alias,_declaredType,mem.in(idx),ptr instanceof PhiNode && ptr.in(0)==mem.in(0) ? ptr.in(idx) : ptr,off()); + ld.setDef(0,in(0)); + return ld.peephole(); } private static boolean neverAlias( Node ptr1, Node ptr2 ) { return ptr1.in(0) != ptr2.in(0) && @@ -123,8 +150,34 @@ public class LoadNode extends MemOpNode { private boolean profit(PhiNode phi, int idx) { Node px = phi.in(idx); if( px==null ) return false; - if( px._type instanceof TypeMem mem && mem._t.isHighOrConst() ) { px.addDep(this); return true; } - if( px instanceof StoreNode st1 && ptr()==st1.ptr() && off()==st1.off() ) { px.addDep(this); return true; } + px.addDep(this); + if( px._type instanceof TypeMem mem && mem._t.isHighOrConst() ) return true; + if( px instanceof StoreNode st1 && _alias==st1._alias && ptr()==st1.ptr() && off()==st1.off() ) return true; return false; } + + // Memory writers which must follow this read. A packaging node is not a + // write, but can hide New's input, so follow this alias through aggregates. + public ArrayList<Node> antiDeps() { + ArrayList<Node> deps = new ArrayList<>(); + antiDeps(mem(),deps,new BitSet()); + return deps; + } + + private void antiDeps(Node mem, ArrayList<Node> deps, BitSet visit) { + if( visit.get(mem._nid) ) return; + visit.set(mem._nid); + for( Node use : mem._outputs ) { + if( use instanceof MemMergeNode merge ) { + Node slice = _alias<merge.nIns() ? merge.in(_alias) : null; + if( (slice==mem) || (slice==null && merge.in(1)==mem) ) + antiDeps(merge,deps,visit); + } else if( (use instanceof StoreNode st && st._alias==_alias) || + (use instanceof NewNode nnn && nnn.field(_alias)!=null) || + (use instanceof MemPhiNode phi && phi._alias==_alias) || + (use instanceof BulkMemPhiNode phi && !phi.isSplit(_alias)) ) { + if( !deps.contains(use) ) deps.add(use); + } + } + } } diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java new file mode 100644 index 00000000..ff040b99 --- /dev/null +++ b/chapter16/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java @@ -0,0 +1,89 @@ +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 default slice and explicit alias overrides. A null default means partial + * memory: absent aliases are not covered, rather than having unknown contents. + */ +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); + n = n==null ? in(1) : n; + assert n!=null : "Alias not covered: "+alias; + return n; + } + + public void alias(int alias, Node mem) { + assert alias > 1; + while( nIns()<=alias ) addDef(null); + setDef(alias,mem); + } + + // Scalar contents of one alias. New's field inputs supply the initialized type; + // join it with the incoming contents for all previously allocated objects. + // Phis use their cached types, so this query does not recurse around loops. + static Type contents(Node mem, int alias, Node dep) { + mem.addDep(dep); + if( mem instanceof MemMergeNode merge ) + return contents(merge.alias(alias),alias,dep); + if( mem instanceof ProjNode proj && proj.in(0) instanceof NewNode nnn ) { + assert proj._idx==1 && nnn.field(alias)!=null; + nnn.addDep(dep); + Node init = nnn.in(nnn.findAlias(alias)).addDep(dep); + return contents(nnn.mem(),alias,dep).meet(init._type); + } + // There are no heap arguments in this chapter. Before the first New, + // the set of allocated objects (and hence stored values) is empty. + if( mem instanceof ProjNode proj && proj.in(0) instanceof StartNode ) return Type.TOP; + if( mem._type==Type.TOP || mem._type==TypeMem.TOP ) return Type.TOP; + if( mem._type instanceof TypeMem mt && mt._alias==alias ) return mt._t; + return Type.BOTTOM; + } + + @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)!=null && in(i)==in(1) ) { setDef(i,null); progress=true; } + if( in(i) instanceof MemMergeNode mem ) { + setDef(i,IterPeeps.add(mem.alias(i))); + progress=true; + } + if( in(i)!=null ) allDefault=false; + } + if( allDefault && in(1)!=null ) 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/chapter16/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/MemOpNode.java index c7a1efec..9c222ad2 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/node/MemOpNode.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/node/MemOpNode.java @@ -32,6 +32,13 @@ public abstract class MemOpNode extends Node { public Node ptr() { return in(2); } public Node off() { return in(3); } + // Extra conditions for factoring matching memory operations through a Phi. + // The caller has already checked the opcode, input shape and control. + boolean canDrop(MemOpNode other, Node dep) { + ptr().addDep(dep); + return _alias==other._alias && _declaredType==other._declaredType && err()==null; + } + @Override boolean eq(Node n) { MemOpNode mem = (MemOpNode)n; // Invariant diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java new file mode 100644 index 00000000..6aae00f9 --- /dev/null +++ b/chapter16/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java @@ -0,0 +1,42 @@ +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,Type.BOTTOM),inputs); + assert alias > 1; + _alias = alias; + } + + @Override public String label() { return "MemPhi_"+_alias; } + + @Override public Type compute() { + assert BulkMemPhiNode.checkMem(this,_alias); + if( !(region() instanceof RegionNode r) ) + return region()._type==Type.XCONTROL ? TypeMem.TOP : _type; + if( r.inProgress() ) return _declaredType; + Type t = Type.TOP; + for( int i=1; i<nIns(); i++ ) + if( r.in(i).addDep(this)._type!=Type.XCONTROL ) + t = t.meet(MemMergeNode.contents(in(i),_alias,this)); + return TypeMem.make(_alias,t); + } + + @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/chapter16/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/NewNode.java index 77800be2..bdccc681 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/node/NewNode.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/node/NewNode.java @@ -1,34 +1,33 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.Utils; import java.util.BitSet; -/** - * Allocation! Allocate a chunk of memory, and pre-zero it. - * The inputs include control and size, and ALL aliases being set. - * The output is large tuple, one for every alias plus the created pointer. - * New is expected to be followed by projections for every alias. +/** Allocate and initialize an object. Inputs {ctrl, $mem, size, fields...}; + * results {ptr, $mem}. + * The memory input and result cover only the aliases in the allocated struct. */ public class NewNode extends Node implements MultiNode { public final TypeMemPtr _ptr; - public final int _len; public NewNode(TypeMemPtr ptr, Node... nodes) { super(nodes); _ptr = ptr; - _len = ptr._obj._fields.length; - // Control in slot 0 - assert nodes[0]._type==Type.CONTROL || nodes[0]._type == Type.XCONTROL; - // Malloc-length in slot 1 - assert nodes[1]._type instanceof TypeInteger; - for( int i=0; i<_len; i++ ) { - // Memory slices for all fields. - assert nodes[2+ i]._type instanceof TypeMem; - // Value slices for all fields. - assert nodes[2 + _len + i]._type != null; - } + assert nodes.length==3+ptr._obj._fields.length; + assert nodes[0]._type==Type.CONTROL || nodes[0]._type==Type.XCONTROL; + assert nodes[1]._type instanceof TypeMem; + assert nodes[2]._type instanceof TypeInteger; + for( int i=3; i<nodes.length; i++ ) assert nodes[i]._type!=null; + } + + public Node mem() { return in(1); } + public Node size() { return in(2); } + + public Field field(int alias) { + for( Field f : _ptr._obj._fields ) + if( f._alias==alias ) return f; + return null; } @Override public String label() { @@ -42,22 +41,13 @@ public class NewNode extends Node implements MultiNode { // Find matching alias input int findAlias(int alias) { - return 2+_ptr._obj.findAlias(alias)+_len; + return 3+_ptr._obj.findAlias(alias); } @Override public TypeTuple compute() { - Field[] fs = _ptr._obj._fields; - Type[] ts = new Type[fs.length+2]; - ts[0] = Type.CONTROL; - ts[1] = _ptr; - for( int i=0; i<fs.length; i++ ) { - TypeMem mem = (TypeMem)in(i+2)._type; - Type tfld = in(2+_len+i)._type.meet(mem._t); - ts[i+2] = TypeMem.make(fs[i]._alias,tfld); - } - return TypeTuple.make(ts); + return TypeTuple.make(_ptr,TypeMem.BOT); } @Override diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter16/src/main/java/com/seaofnodes/simple/node/Node.java index 56a1ca72..b9d2632b 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/node/Node.java @@ -16,14 +16,14 @@ import java.util.function.Function; * The Node class provides common functionality used by all subtypes. * Subtypes of Node specialize by overriding methods. */ -public abstract class Node implements OutNode { +public abstract class Node implements OutNode, Cloneable { /** * 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; + public int _nid; /** * Inputs to the node. These are use-def references to Nodes. @@ -32,7 +32,7 @@ public abstract class Node implements OutNode { * Ordering is required because e.g. "a/b" is different from "b/a". * The first input (offset 0) is often a {@link CFGNode} node. */ - public final Ary<Node> _inputs; + public Ary<Node> _inputs; /** * Outputs reference Nodes that are not null and have this Node as an @@ -43,7 +43,7 @@ public abstract class Node implements OutNode { * walked in either direction. These outputs are typically used for * efficient optimizations but otherwise have no semantics meaning. */ - public final Ary<Node> _outputs; + public Ary<Node> _outputs; /** @@ -214,6 +214,7 @@ public abstract class Node implements OutNode { // Error is 'use' does not exist; ok for 'use' to be null. protected boolean delUse( Node use ) { _outputs.del(_outputs.find(use)); + moveDepsToWorklist(); // User-count and anti-dependence queries can now change. return _outputs.isEmpty(); } @@ -270,6 +271,7 @@ public abstract class Node implements OutNode { n.unlock(); int idx = n._inputs.find(this); n._inputs.set(idx,nnn); + n.moveDepsToWorklist(); // Rewiring can change a dependent query without changing type. nnn.addUse(n); } kill(); @@ -455,6 +457,14 @@ public abstract class Node implements OutNode { * 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; @@ -462,8 +472,8 @@ public abstract class Node implements OutNode { if( obs != null ) obs.dep(this, dep); if( _deps==null ) _deps = new Ary<>(Node.class); if( _deps .find(dep) != -1 ) return this; // Already on list - if( _inputs .find(dep) != -1 ) return this; // No need for deps on immediate neighbors - if( _outputs.find(dep) != -1 ) return this; + if( !forwards && _inputs .find(dep) != -1 ) return this; // No need for deps on immediate neighbors + if( !forwards && _outputs.find(dep) != -1 ) return this; _deps.add(dep); return this; } @@ -592,6 +602,20 @@ public abstract class Node implements OutNode { // Does not need to be implemented in isCFG() nodes. Node copy(Node lhs, Node rhs) { throw Utils.TODO("Binary ops need to implement copy"); } + // Exact-class copy preserving operation attributes, with fresh identity + // and no edges, dependencies, or GVN membership. + public final Node copyEmpty() { + Node n; + try { n = (Node)clone(); } + catch( CloneNotSupportedException e ) { throw new AssertionError(e); } + n._nid = UNIQUE_ID++; + n._inputs = new Ary<>(Node.class); + n._outputs = new Ary<>(Node.class); + n._deps = null; + n._hash = 0; + return n; + } + // Report any post-optimize errors String err() { return null; } diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/PhiNode.java index f533a7a4..6a9a8aae 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -16,6 +16,10 @@ public class PhiNode extends Node { 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_"+MemOpNode.mlabel(_label); } @Override @@ -65,24 +69,9 @@ public class PhiNode extends Node { if (live != null) return live; - // Pull "down" a common data op. One less op in the world. One more - // Phi, but Phis do not make code. - // Phi(op(A,B),op(Q,R),op(X,Y)) becomes - // op(Phi(A,Q,X), Phi(B,R,Y)). - Node op = in(1); - if( op.nIns()==3 && op.in(0)==null && same_op() ) { - assert !(op instanceof CFGNode); - 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); - } + // Phi(op(a,b,...),op(c,d,...)) -> op(Phi(a,c),Phi(b,d),...). + Node down; + if( same_op() && (down=drop_same_op())!=null ) return down; // If merging Phi(N, cast(N)) - we are losing the cast JOIN effects, so just remove. if( nIns()==3 ) { @@ -112,12 +101,72 @@ public class PhiNode extends Node { } private boolean same_op() { - for( int i=2; i<nIns(); i++ ) - if( in(1).getClass() != in(i).getClass() ) - return false; + Node op = in(1); + if( op instanceof CFGNode || op instanceof ConstantNode || op instanceof PhiNode || + op instanceof ProjNode || op instanceof NewNode || op instanceof ScopeNode || op instanceof MemMergeNode ) return false; + // A bulk Phi must split its aliases before factoring precise Stores. + if( this instanceof BulkMemPhiNode ) return false; + // Bulk splitting currently identifies parallel slices by Region/alias. + // Factoring can introduce another slice at a different memory point; + // wait until that Region's bulk partitioning has finished. + if( op instanceof MemOpNode ) + for( Node use : region()._outputs ) + if( use instanceof BulkMemPhiNode ) { + use.addDepForwards(this); + return false; + } + Node busy = null; + for( int i=1; i<nIns(); i++ ) { + Node n = in(i); + if( region().in(i).addDep(this)._type==Type.XCONTROL || + op.getClass()!=n.getClass() || n.nIns()!=op.nIns() || n.in(0)!=null || !op.eq(n) ) return false; + if( n instanceof MemOpNode mem && !mem.canDrop((MemOpNode)op,this) ) return false; + // Moving a Store must remove the old effect, not duplicate it. + // Allow one shared arm: the other arms disappear into the single + // factored operation, usually reducing the total operation count. + n.addDepForwards(this); + if( n.nOuts()>1 ) { + for( Node use : n._outputs ) + if( use!=null && use!=this ) use.addDepForwards(this); + if( n instanceof StoreNode || busy!=null ) return false; + busy=n; + } + for( int j=1; j<n.nIns(); j++ ) + if( n.in(j) instanceof ScopeNode || ((n.in(j)==null) != (op.in(j)==null)) ) return false; + } return true; } + private Node drop_same_op() { + Node op = in(1), cp = op.copyEmpty(); + cp._type = null; + cp.addDef(null); + for( int j=1; j<op.nIns(); j++ ) { + Node x = op.in(j); + boolean different = false; + for( int i=2; i<nIns(); i++ ) + if( in(i).in(j)!=x ) different=true; + if( different ) { + Node[] ins = new Node[nIns()]; + ins[0] = region(); + Type t = Type.TOP; + for( int i=1; i<nIns(); i++ ) { + ins[i] = in(i).in(j); + t = t.meet(ins[i]._type); + } + PhiNode phi = j==1 && op instanceof MemOpNode mem + ? new MemPhiNode(_label,mem._alias,ins) + : PhiNode.make(_label,t.glb(),ins); + x = phi.peephole(); + } + cp.addDef(x); + } + // Factoring must not widen the result (e.g. correlated And operands). + if( cp.compute().isa(compute()) ) return cp; + cp.kill(); + return null; + } + /** * If only single unique input, return it */ diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/RegionNode.java index 675d68ac..15efb9d1 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/node/RegionNode.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/node/RegionNode.java @@ -68,7 +68,7 @@ public class RegionNode extends CFGNode { if( !hasPhi() && // No Phi users, just a control user in(1) instanceof CProjNode p1 && in(2) instanceof CProjNode p2 && - p1.in(0).addDep(this)==p2.in(0).addDep(this) && + p1.addDepForwards(this).in(0)==p2.addDepForwards(this).in(0) && p1.in(0) instanceof IfNode iff ) return iff.ctrl(); diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/ReturnNode.java index de4eba52..978391e3 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -4,27 +4,17 @@ 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 all preceding memory effects and the return value alive: {ctrl, $mem, value}. */ public class ReturnNode extends CFGNode { public ReturnNode(Node ctrl, Node data, ScopeNode scope) { - // Add memory slices to Return, so all memory updates are live-on-exit. - super(ctrl, data); - if( scope!=null ) - for( int i=2; i<scope.mem().nIns(); i++ ) - addDef(scope.mem().in(i)); + // A synthetic never-taken loop exit has no source scope. + super(ctrl, scope == null ? null : scope.mem(), data); } public Node ctrl() { return in(0); } - public Node expr() { return in(1); } + public Node mem () { return in(1); } + public Node expr() { return in(2); } @Override public String label() { return "Return"; } @@ -38,7 +28,8 @@ public class ReturnNode extends CFGNode { @Override public Type compute() { - return TypeTuple.make(ctrl()._type,expr()._type); + // Return exposes the complete memory state. + return TypeTuple.make(ctrl()._type,TypeMem.BOT,expr()._type); } @Override diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/ScopeMinNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/ScopeMinNode.java deleted file mode 100644 index e4874e15..00000000 --- a/chapter16/src/main/java/com/seaofnodes/simple/node/ScopeMinNode.java +++ /dev/null @@ -1,137 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.type.*; -import java.util.*; - -import static com.seaofnodes.simple.Utils.TODO; - -/* */ -public class ScopeMinNode extends Node { - - /** The tracked fields are now complex enough to deserve a array-of-structs layout - */ - public static class Var { - public final int _idx; - public final String _name; - public final Type _type; - public final boolean _final; - public Var(int idx, String name, Type type, boolean xfinal) { - _idx = idx; - _name = name; - _type = type; - _final = xfinal; - } - } - - public ScopeMinNode() { _type = TypeMem.BOT; } - - - @Override public String label() { return "MEM"; } - - @Override - StringBuilder _print1(StringBuilder sb, BitSet visited) { - sb.append("MEM[ "); - for( int j=2; j<nIns(); j++ ) { - sb.append(j); - sb.append(":"); - Node n = in(j); - while( n instanceof ScopeNode loop ) { - sb.append("Lazy_"); - n = loop.in(j); - } - if( n==null ) sb.append("___ "); - else n._print0(sb, visited).append(" "); - } - sb.setLength(sb.length()-1); - return sb.append("]"); - } - - - @Override public Type compute() { return TypeMem.BOT; } - - @Override public Node idealize() { return null; } - - public Node in( Var v ) { return in(v._idx); } - - Node alias( int alias ) { - return in(alias<nIns() && in(alias)!=null ? alias : 1); - } - - Node alias( int alias, Node st ) { - while( alias >= nIns() ) addDef(null); - return setDef(alias,st); - } - - - // Read or update from memory. - // A shared implementation allows us to create lazy phis both during - // lookups and updates; the lazy phi creation is part of chapter 8. - Node _mem( int alias, Node st ) { - // Memory projections are made lazily; if one does not exist - // then it must be START.proj(1) - Node old = alias(alias); - if( old instanceof ScopeNode loop ) { - ScopeMinNode loopmem = loop.mem(); - Node memdef = loopmem.alias(alias); - // Lazy phi! - old = memdef instanceof PhiNode phi && loop.ctrl()==phi.region() - // Loop already has a real Phi, use it - ? memdef - // 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. - : loopmem.alias(alias, new PhiNode(Parser.memName(alias), TypeMem.BOT,loop.ctrl(),loopmem._mem(alias,null),null).peephole() ); - alias(alias,old); - } - // Memory projections are made lazily; expand as needed - return st==null ? old : alias(alias,st); // Not lazy, so this is the answer - } - - - void _merge(ScopeMinNode that, RegionNode r) { - int len = Math.max(nIns(),that.nIns()); - for( int i = 2; i < len; i++) - if( alias(i) != that.alias(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 - //Var v = _vars.at(i); - Node lhs = this._mem(i,null); - Node rhs = that._mem(i,null); - alias(i, new PhiNode(Parser.memName(i), TypeMem.BOT, r, lhs, rhs).peephole()); - } - } - - // Fill in the backedge of any inserted Phis - void _endLoopMem( ScopeNode scope, ScopeMinNode back, ScopeMinNode exit ) { - for( int i=2; i<back.nIns(); i++ ) { - if( back.in(i) != scope ) { - PhiNode phi = (PhiNode)in(i); - assert phi.region()==scope.ctrl() && phi.in(2)==null; - phi.setDef(2,back.in(i)); // Fill backedge - } - if( exit.alias(i) == scope ) // Replace a lazy-phi on the exit path also - exit.alias(i,in(i)); - } - } - - // Now one-time do a useless-phi removal - void _useless( ) { - for( int i=2; 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 - } - } - } - } - -} diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/ScopeNode.java index 02baeb8a..baa27134 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -1,19 +1,15 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.Ary; import com.seaofnodes.simple.type.*; import java.util.*; -import static com.seaofnodes.simple.Utils.TODO; - /** * The Scope node is purely a parser helper - it tracks names to nodes with a - * stack of hashmaps. + * stack of variable records. */ -public class ScopeNode extends ScopeMinNode { +public class ScopeNode extends Node { /** * The control is a name that binds to the currently active control @@ -23,15 +19,35 @@ public class ScopeNode extends ScopeMinNode { public static final String ARG0 = "arg"; public static final String MEM0 = "$mem"; + /** The tracked fields are now complex enough to deserve an array-of-structs layout + */ + public static class Var { + public final int _idx; + public final String _name; + public final Type _type; + public final boolean _final; + public Var(int idx, String name, Type type, boolean xfinal) { + _idx = idx; + _name = name; + _type = type; + _final = xfinal; + } + } + + public Node in(Var v) { return in(v._idx); } + + @Override public Type compute() { return Type.BOTTOM; } + @Override public Node idealize() { return null; } + // All active/live variables in all nested scopes, all run together public final Ary<Var> _vars; - // Since of each nested lexical scope + // Size of each nested lexical scope public final Ary<Integer> _lexSize; // A new ScopeNode - public ScopeNode() { _vars = new Ary<>(Var.class); _lexSize = new Ary<>(Integer.class); } + public ScopeNode() { _type = Type.BOTTOM; _vars = new Ary<>(Var.class); _lexSize = new Ary<>(Integer.class); } @Override public String label() { return "Scope"; } @@ -63,7 +79,8 @@ public class ScopeNode extends ScopeMinNode { public Node ctrl() { return in(0); } - public ScopeMinNode mem() { return (ScopeMinNode)in(1); } + public Node mem() { return in(lookup(MEM0)); } + public void mem(Node n) { update(MEM0,n); } /** * The ctrl of a ScopeNode is always bound to the currently active @@ -110,12 +127,6 @@ public class ScopeNode extends ScopeMinNode { return true; } - // Read from memory - public Node mem( int alias ) { return mem()._mem(alias,null); } - // Write to memory - public void mem( int alias, Node st ) { mem()._mem(alias,st); } - - /** * Lookup a name in all scopes starting from most deeply nested. * @@ -151,7 +162,7 @@ public class ScopeNode extends ScopeMinNode { // 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(v._idx,new PhiNode(v._name, v._type instanceof TypeMemPtr ? v._type : v._type.glb(), loop.ctrl(), loop.in(loop._update(v,null)._idx),null).peephole()); + : loop.setDef(v._idx,PhiNode.make(v._name, v._type instanceof TypeMemPtr ? v._type : v._type.glb(), loop.ctrl(), loop.in(loop._update(v,null)._idx),null).peephole()); setDef(v._idx,old); } if( st!=null ) setDef(v._idx,st); // Set new value @@ -182,20 +193,8 @@ public class ScopeNode extends ScopeMinNode { dup._lexSize.addAll(_lexSize); dup.addDef(ctrl()); // Control input is just copied - // Memory input is a shallow copy - ScopeMinNode memdup = new ScopeMinNode(), mem = mem(); - memdup.addDef(null); - memdup.addDef(loop ? this : mem.in(1)); - for( int i=2; i<mem.nIns(); i++ ) - // For lazy phis on loops we use a sentinel - // that will trigger phi creation on update - memdup.addDef(loop ? this : mem.in(i)); - dup.addDef(memdup); - - // Copy of other inputs - for( int i=2; i<nIns(); i++ ) - // For lazy phis on loops we use a sentinel - // that will trigger phi creation on update + // Memory is one binding, with the same lazy loop Phi as scalar names. + for( int i=1; i<nIns(); i++ ) dup.addDef(loop ? this : in(i)); return dup; } @@ -210,22 +209,21 @@ public class ScopeNode extends ScopeMinNode { */ public Node mergeScopes(ScopeNode that) { RegionNode r = (RegionNode) ctrl(new RegionNode(null,ctrl(), that.ctrl()).keep()); - mem()._merge(that.mem(),r); - this ._merge(that ,r); + _merge(that,r); that.kill(); // Kill merged scope IterPeeps.add(r); return r.unkeep().peephole(); } private void _merge(ScopeNode that, RegionNode r) { - for( int i = 2; i < nIns(); i++) + 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 Var v = _vars.at(i); Node lhs = this.in(this._update(v,null)); Node rhs = that.in(that._update(v,null)); - setDef(i, new PhiNode(v._name, v._type, r, lhs, rhs).peephole()); + setDef(i, PhiNode.make(v._name, v._type, r, lhs, rhs).peephole()); } } @@ -236,21 +234,15 @@ public class ScopeNode extends ScopeMinNode { assert ctrl instanceof LoopNode loop && loop.inProgress(); ctrl.setDef(2,back.ctrl()); - mem()._endLoopMem( this, back.mem(), exit.mem() ); - this ._endLoop ( this, back , exit ); + _endLoop(this,back,exit); back.kill(); // Loop backedge is dead // Now one-time do a useless-phi removal - mem()._useless(); - this ._useless(); - - // The exit mem's lazy default value had been the loop top, - // now it goes back to predating the loop. - exit.mem().setDef(1,mem().in(1)); + _useless(); } // Fill in the backedge of any inserted Phis void _endLoop( ScopeNode scope, Node back, Node exit ) { - for( int i=2; i<nIns(); i++ ) { + for( int i=1; i<nIns(); i++ ) { if( back.in(i) != scope ) { PhiNode phi = (PhiNode)in(i); assert phi.region()==scope.ctrl() && phi.in(2)==null; @@ -262,12 +254,30 @@ public class ScopeNode extends ScopeMinNode { } + // Now one-time do a useless-phi removal + void _useless( ) { + 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 ) { diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/StartNode.java index c03a4b06..6e1f13ee 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -1,19 +1,11 @@ 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.HashSet; import static com.seaofnodes.simple.Utils.TODO; -/** - * 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. - */ +/** Start supplies control, whole memory, and the argument, in that order. */ public class StartNode extends CFGNode implements MultiNode { final Type _arg; @@ -32,7 +24,7 @@ public class StartNode extends CFGNode implements MultiNode { @Override public CFGNode cfg0() { return this; } @Override public TypeTuple compute() { - return TypeTuple.make(Type.CONTROL,TypeMem.TOP,_arg); + return TypeTuple.make(Type.CONTROL,TypeMem.BOT,_arg); } @Override public Node idealize() { return null; } diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/StoreNode.java index efa4dba5..8d1b9ae1 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter16/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; @@ -26,6 +27,10 @@ public class StoreNode extends MemOpNode { } // Debugger label + @Override boolean canDrop(MemOpNode other, Node dep) { + return super.canDrop(other,dep) && _init==((StoreNode)other)._init; + } + @Override public String label() { return "st_"+mlabel(); } @Override public boolean isMem() { return true; } @@ -38,22 +43,23 @@ public class StoreNode extends MemOpNode { @Override public Type compute() { - Type val = val()._type; - TypeMem mem = (TypeMem)mem()._type; // Invariant - Type t = mem._alias==_alias - ? val.meet(mem._t) // Meet into existing memory - : Type.BOTTOM; + Type t = val()._type.meet(MemMergeNode.contents(mem(),_alias,this)); return TypeMem.make(_alias,t); } @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 - off()==st.off() && // And same offset (could be "same alias" but this handles arrays to same index) + ptr()==st.ptr() && _alias==st._alias && // Must check same object + off()==st.off() && // And same offset 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/chapter16/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter16/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index da54e8c8..dff0de0b 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -54,7 +54,7 @@ public class SimpleGraphAdapter extends GraphAdapter<Node> { return n instanceof CFGNode ? 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 || n instanceof ReturnNode || n instanceof NewNode) ) return Role.MEM; Node def = n.in(i); if( def != null && isMem(def) ) return Role.MEM; return Role.DATA; diff --git a/chapter16/src/test/java/com/seaofnodes/simple/Chapter04Test.java b/chapter16/src/test/java/com/seaofnodes/simple/Chapter04Test.java index a3109ff7..14d31245 100644 --- a/chapter16/src/test/java/com/seaofnodes/simple/Chapter04Test.java +++ b/chapter16/src/test/java/com/seaofnodes/simple/Chapter04Test.java @@ -84,7 +84,10 @@ public class Chapter04Test { StopNode stop = parser.parse(); ReturnNode ret = stop.ret(); assertTrue(ret.in(0) instanceof CProjNode); - assertTrue(ret.in(1) instanceof ProjNode); + assertTrue(ret.in(1) instanceof ProjNode); + assertEquals(1, ((ProjNode)ret.in(1))._idx); + assertTrue(ret.in(2) instanceof ProjNode); + assertEquals(2, ((ProjNode)ret.in(2))._idx); } @Test diff --git a/chapter16/src/test/java/com/seaofnodes/simple/Chapter05Test.java b/chapter16/src/test/java/com/seaofnodes/simple/Chapter05Test.java index 81ff2244..cd646126 100644 --- a/chapter16/src/test/java/com/seaofnodes/simple/Chapter05Test.java +++ b/chapter16/src/test/java/com/seaofnodes/simple/Chapter05Test.java @@ -22,7 +22,7 @@ else { return a;"""); StopNode ret = parser.parse().iterate(); - assertEquals("return Phi(Region23,(arg+2),(arg-3));", ret.toString()); + assertEquals("return Phi(Region21,(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(Region22,4,3);", ret.toString()); + assertEquals("return Phi(Region20,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(Region26,2,3));", ret.toString()); + assertEquals("return ((arg*2)+Phi(Region24,2,3));", ret.toString()); } @Test @@ -80,7 +80,7 @@ else a=b+1; return a+b;"""); StopNode ret = parser.parse().iterate(); - assertEquals("return ((Phi(Region37,(arg*2),arg)+arg)+Phi(Region,4,5));", ret.toString()); + assertEquals("return ((Phi(Region35,(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(Region43,Phi(Region28,2,3),Phi(Region41,4,5));", stop.toString()); + assertEquals("return Phi(Region39,Phi(Region25,2,3),Phi(Region37,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(Region20,1,0))+Phi(Region37,2,0));", stop.toString()); + assertEquals("return ((arg+Phi(Region18,1,0))+Phi(Region34,2,0));", stop.toString()); } @Test @@ -130,7 +130,7 @@ if( arg==1 ) } return a;"""); StopNode ret = parser.parse().iterate(); - assertEquals("return (arg==Phi(Region22,3,2));", ret.toString()); + assertEquals("return (arg==Phi(Region20,3,2));", ret.toString()); } @Test diff --git a/chapter16/src/test/java/com/seaofnodes/simple/Chapter06Test.java b/chapter16/src/test/java/com/seaofnodes/simple/Chapter06Test.java index ff1f3251..d2c46df5 100644 --- a/chapter16/src/test/java/com/seaofnodes/simple/Chapter06Test.java +++ b/chapter16/src/test/java/com/seaofnodes/simple/Chapter06Test.java @@ -65,7 +65,7 @@ else b=5; return b;""", TypeInteger.BOT); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region43,42,5);", stop.toString()); + assertEquals("return Phi(Region40,42,5);", stop.toString()); } @Test @@ -84,7 +84,7 @@ else b=5; return b;""", TypeInteger.BOT); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region38,2,5);", stop.toString()); + assertEquals("return Phi(Region33,2,5);", stop.toString()); } @Test @@ -144,7 +144,7 @@ else return a; """, TypeInteger.BOT); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region48,3,Phi(Region46,4,5));", stop.toString()); + assertEquals("return Phi(Region44,3,Phi(Region42,4,5));", stop.toString()); } @Test @@ -200,7 +200,7 @@ if( arg ) { return a+b; """); StopNode ret = parser.parse().iterate(); - assertEquals("return Phi(Region35,4,1);", ret.toString()); + assertEquals("return Phi(Region29,4,1);", ret.toString()); } @@ -251,7 +251,7 @@ if( arg ) { return a+b+c; """); StopNode ret = parser.parse().iterate(); - assertEquals("return (Phi(Region48,Phi(Region29,2,3),0)+Phi(Region,3,1));", ret.toString()); + assertEquals("return (Phi(Region41,Phi(Region26,2,3),0)+Phi(Region,3,1));", ret.toString()); } diff --git a/chapter16/src/test/java/com/seaofnodes/simple/Chapter07Test.java b/chapter16/src/test/java/com/seaofnodes/simple/Chapter07Test.java index 390e1da5..443b0d17 100644 --- a/chapter16/src/test/java/com/seaofnodes/simple/Chapter07Test.java +++ b/chapter16/src/test/java/com/seaofnodes/simple/Chapter07Test.java @@ -17,7 +17,7 @@ public class Chapter07Test { return arg; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop10,arg,(Phi_arg+1));", stop.toString()); + assertEquals("return Phi(Loop9,arg,(Phi_arg+1));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -34,7 +34,7 @@ if(arg){}else{ return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region31,1,Phi(Loop18,1,(Phi_a+1)));", stop.toString()); + assertEquals("return Phi(Region27,1,Phi(Loop16,1,(Phi_a+1)));", stop.toString()); } @Test @@ -54,7 +54,7 @@ while(i < arg) { return sum; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop12,0,Phi(Loop26,Phi_sum,(Phi_sum+Phi(Loop,0,(Phi_j+1)))));", stop.toString()); + assertEquals("return Phi(Loop11,0,Phi(Loop23,Phi_sum,(Phi_sum+Phi(Loop,0,(Phi_j+1)))));", stop.toString()); } @Test @@ -70,7 +70,7 @@ while(a < 10) { return b; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop12,2,Phi(Region34,Phi_b,4));", stop.toString()); + assertEquals("return Phi(Loop11,2,Phi(Region30,Phi_b,4));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -89,7 +89,7 @@ while(a < 10) { return b; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop12,2,(Phi(Region34,Phi_b,4)+1));", stop.toString()); + assertEquals("return Phi(Loop11,2,(Phi(Region30,Phi_b,4)+1));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -106,7 +106,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop11,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -122,7 +122,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop11,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -135,7 +135,7 @@ while(arg) a = 2; return a; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop11,1,2);", stop.toString()); + assertEquals("return Phi(Loop10,1,2);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -148,7 +148,7 @@ while(arg) a = 2; return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop11,1,2);", stop.toString()); + assertEquals("return Phi(Loop10,1,2);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -164,7 +164,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop11,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -180,7 +180,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop11,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop10,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -197,7 +197,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse(); - assertEquals("return Phi(Loop12,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop11,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -214,7 +214,7 @@ while(a < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop12,1,(Phi_a+3));", stop.toString()); + assertEquals("return Phi(Loop11,1,(Phi_a+3));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } diff --git a/chapter16/src/test/java/com/seaofnodes/simple/Chapter08Test.java b/chapter16/src/test/java/com/seaofnodes/simple/Chapter08Test.java index c199b78c..89ce8313 100644 --- a/chapter16/src/test/java/com/seaofnodes/simple/Chapter08Test.java +++ b/chapter16/src/test/java/com/seaofnodes/simple/Chapter08Test.java @@ -23,7 +23,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region45,Phi(Region32,Phi(Loop10,arg,(Phi_arg+1)),Add),Add);", stop.toString()); + assertEquals("return Phi(Region38,Phi(Region27,Phi(Loop9,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)); @@ -46,7 +46,7 @@ while(arg < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop11,1,Phi(Region52,Phi_a,(Phi_a+1)));", stop.toString()); + assertEquals("return Phi(Loop10,1,Phi(Region44,Phi_a,(Phi_a+1)));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -65,7 +65,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region43,Phi(Loop10,arg,(Phi_arg+1)),Add);", stop.toString()); + assertEquals("return Phi(Region36,Phi(Loop9,arg,(Phi_arg+1)),Add);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof RegionNode); } @@ -81,7 +81,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region32,Phi(Loop10,arg,(Phi_arg+1)),Add);", stop.toString()); + assertEquals("return Phi(Region27,Phi(Loop9,arg,(Phi_arg+1)),Add);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof RegionNode); } @@ -100,7 +100,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,arg,(Phi_arg+1));", stop.toString()); + assertEquals("return Phi(Loop9,arg,(Phi_arg+1));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -117,7 +117,7 @@ while(arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop10,arg,(Phi_arg+1));", stop.toString()); + assertEquals("return Phi(Loop9,arg,(Phi_arg+1));", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode); } @@ -185,7 +185,7 @@ while(arg < 10) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region35,Phi(Loop11,1,(Phi_a+1)),Add);", stop.toString()); + assertEquals("return Phi(Region30,Phi(Loop10,1,(Phi_a+1)),Add);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof RegionNode); } @@ -201,7 +201,7 @@ while(1) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return (Phi(Loop11,1,Add)+1);", stop.toString()); + assertEquals("return (Phi(Loop10,1,Add)+1);", stop.toString()); assertTrue(stop.ret().ctrl() instanceof CProjNode prj && prj._idx==1 ); } diff --git a/chapter16/src/test/java/com/seaofnodes/simple/Chapter09Test.java b/chapter16/src/test/java/com/seaofnodes/simple/Chapter09Test.java index b1d9d6e5..49ee75a2 100644 --- a/chapter16/src/test/java/com/seaofnodes/simple/Chapter09Test.java +++ b/chapter16/src/test/java/com/seaofnodes/simple/Chapter09Test.java @@ -69,7 +69,7 @@ while (arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop11,arg,(Phi_arg+2));", stop.toString()); + assertEquals("return Phi(Loop10,arg,(Phi_arg+2));", stop.toString()); Assert.assertEquals(11L, Evaluator.evaluate(stop, 1)); } @@ -86,7 +86,7 @@ while (arg < 10) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop12,arg,(Phi_arg+4));", stop.toString()); + assertEquals("return Phi(Loop11,arg,(Phi_arg+4));", stop.toString()); Assert.assertEquals(13L, Evaluator.evaluate(stop, 1)); } @@ -115,7 +115,7 @@ while (arg) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop18,arg,(Phi_arg+1));", stop.toString()); + assertEquals("return Phi(Loop17,arg,(Phi_arg+1));", stop.toString()); } @Test @@ -185,7 +185,7 @@ while(arg) { return a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop13,0,(-(Phi_a+3)));", stop.toString()); + assertEquals("return Phi(Loop12,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(Loop10,arg,(Phi_arg-1));", stop.toString()); + assertEquals("return Phi(Loop9,arg,(Phi_arg-1));", stop.toString()); } @Test diff --git a/chapter16/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter16/src/test/java/com/seaofnodes/simple/Chapter10Test.java index 408a4814..75119a75 100644 --- a/chapter16/src/test/java/com/seaofnodes/simple/Chapter10Test.java +++ b/chapter16/src/test/java/com/seaofnodes/simple/Chapter10Test.java @@ -1,7 +1,8 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.evaluator.Evaluator; -import com.seaofnodes.simple.node.StopNode; +import com.seaofnodes.simple.node.*; +import java.util.BitSet; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -160,7 +161,7 @@ while (arg) { return bar.a; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop16,0,(Phi_a+2));", stop.toString()); + assertEquals("return Phi(Loop17,0,(Phi_a+2));", stop.toString()); } @Test @@ -282,7 +283,7 @@ while( i.x < i.len ) { return sum; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop21,0,(Phi(Loop,0,(Phi_x+1))+Phi_sum));", stop.toString()); + assertEquals("return Phi(Loop22,0,(Phi(Loop,0,(Phi_x+1))+Phi_sum));", stop.toString()); } @@ -301,7 +302,7 @@ while(arg) { return ret; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop16,s0,Phi(Region43,s0,Phi_ret));", stop.toString()); + assertEquals("return Phi(Loop17,s0,Phi(Region44,s0,Phi_ret));", stop.toString()); } @Test @@ -319,7 +320,7 @@ while(arg) { return ret; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop20,s0,Phi(Region44,s0,Phi_ret));", stop.toString()); + assertEquals("return Phi(Loop23,s0,Phi(Region45,s0,Phi_ret));", stop.toString()); } @@ -336,7 +337,7 @@ while(arg < 10) { return ret; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop16,s0,Phi(Region41,s0,Phi_ret));", stop.toString()); + assertEquals("return Phi(Loop17,s0,Phi(Region41,s0,Phi_ret));", stop.toString()); } @Test @@ -452,4 +453,120 @@ 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)); + } + + 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()); + } + } + + @Test public void testKeepLoadsAtMerge() { + StopNode stop = new Parser(""" + struct S { int x; }; + S a = new S; S b = new S; + a.x = arg; b.x = arg+1; + S p = a; S q = b; + if (arg<0) { p=b; q=a; } + int v; + if (arg>1) v=p.x; else v=q.x; + return v; + """).parse().iterate(); + // The one-step safety check stops at the memory aggregate. + assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet())); + assertEquals(-1L,Evaluator.evaluate(stop,-1)); + assertEquals(1L,Evaluator.evaluate(stop,0)); + assertEquals(3L,Evaluator.evaluate(stop,3)); + } + + @Test public void testDropStores() { + StopNode stop = new Parser(""" + struct S { int x; }; + S s = new S; + if (arg) s.x=arg+1; else s.x=arg+2; + return s; + """).parse().iterate(); + assertEquals(1,countMemoryNodes(stop,StoreNode.class,new BitSet())); + assertEquals(2L,((Evaluator.Obj)Evaluator.evaluate(stop,0)).fields()[0]); + assertEquals(4L,((Evaluator.Obj)Evaluator.evaluate(stop,3)).fields()[0]); + } + + @Test public void testKeepReadsBeforeWrites() { + StopNode stop = new Parser(""" + struct S { int x; int y; }; + S a = new S; S b = new S; + a.x=arg; b.x=arg+1; + S p=a; S q=b; + if (arg<0) { p=b; q=a; } + int v; + if (arg>1) { v=p.x; p.x=41; p.y=5; } + else { v=q.x; q.x=42; } + return v; + """).parse().iterate(); + assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet())); + assertEquals(-1L,Evaluator.evaluate(stop,-1)); + assertEquals(1L,Evaluator.evaluate(stop,0)); + assertEquals(3L,Evaluator.evaluate(stop,3)); + } + + private static int countMemoryNodes(Node n, Class<?> kind, BitSet seen) { + if (n==null || seen.get(n._nid)) return 0; + seen.set(n._nid); + int cnt=kind.isInstance(n) ? 1 : 0; + for (Node def : n._inputs) cnt+=countMemoryNodes(def,kind,seen); + return cnt; + } } diff --git a/chapter16/src/test/java/com/seaofnodes/simple/Chapter10bTest.java b/chapter16/src/test/java/com/seaofnodes/simple/Chapter10bTest.java new file mode 100644 index 00000000..4281f0a1 --- /dev/null +++ b/chapter16/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/chapter16/src/test/java/com/seaofnodes/simple/Chapter11Test.java b/chapter16/src/test/java/com/seaofnodes/simple/Chapter11Test.java index 30bcab1e..1183da5f 100644 --- a/chapter16/src/test/java/com/seaofnodes/simple/Chapter11Test.java +++ b/chapter16/src/test/java/com/seaofnodes/simple/Chapter11Test.java @@ -325,7 +325,7 @@ while( prime <= arg ) { return primeCount; """); StopNode stop = parser.parse().iterate(); - assertEquals("Stop[ return 0; return Phi(Loop23,1,Phi(Region100,Phi_primeCount,Phi(Region93,(Phi_primeCount+1),Phi_primeCount))); ]", stop.toString()); + assertEquals("Stop[ return 0; return Phi(Loop21,1,Phi(Region88,Phi_primeCount,Phi(Region82,(Phi_primeCount+1),Phi_primeCount))); ]", stop.toString()); assertEquals(0L, Evaluator.evaluate(stop, 1)); // No primes 1 or below assertEquals(1L, Evaluator.evaluate(stop, 2)); // 2 assertEquals(2L, Evaluator.evaluate(stop, 3)); // 2, 3 @@ -370,7 +370,7 @@ if (arg) { return i; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region45,.f,0);", stop.toString()); + assertEquals("return Phi(Region48,.f,0);", stop.toString()); } @Test @@ -477,7 +477,7 @@ while(arg) { return arg; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop20,arg,Phi(Region48,.f,0));", stop.toString()); + assertEquals("return Phi(Loop23,arg,Phi(Region50,.f,0));", stop.toString()); } @Test diff --git a/chapter16/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter16/src/test/java/com/seaofnodes/simple/Chapter12Test.java index c93f905d..998053ec 100644 --- a/chapter16/src/test/java/com/seaofnodes/simple/Chapter12Test.java +++ b/chapter16/src/test/java/com/seaofnodes/simple/Chapter12Test.java @@ -64,7 +64,7 @@ while( 1 ) { return guess; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop11,(flt)arg,(((ToFloat/Phi_guess)+Phi_guess)/2.0));", stop.toString()); + assertEquals("return Phi(Loop10,(flt)arg,(((ToFloat/Phi_guess)+Phi_guess)/2.0));", stop.toString()); assertEquals(3.0, Evaluator.evaluate(stop, 9)); assertEquals(1.414213562373095, Evaluator.evaluate(stop, 2)); } diff --git a/chapter16/src/test/java/com/seaofnodes/simple/Chapter14Test.java b/chapter16/src/test/java/com/seaofnodes/simple/Chapter14Test.java index 7d7bb25a..03a02795 100644 --- a/chapter16/src/test/java/com/seaofnodes/simple/Chapter14Test.java +++ b/chapter16/src/test/java/com/seaofnodes/simple/Chapter14Test.java @@ -59,7 +59,7 @@ while( b ) b = b + 456;// Truncate return b; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Loop11,123,((Phi_b+456)&255));", stop.toString()); + assertEquals("return Phi(Loop10,123,((Phi_b+456)&255));", stop.toString()); } @Test @@ -175,7 +175,7 @@ while(v1) break; return v1; """); StopNode stop = parser.parse().iterate(); - assertEquals("return (Phi(Loop18,((1<<arg)&1),0)&Phi(Loop,Shl,4294967295));", stop.toString()); + assertEquals("return (Phi(Loop17,((1<<arg)&1),0)&Phi(Loop,Shl,4294967295));", stop.toString()); assertEquals(1L, Evaluator.evaluate(stop, 0)); } diff --git a/chapter16/src/test/java/com/seaofnodes/simple/Chapter15Test.java b/chapter16/src/test/java/com/seaofnodes/simple/Chapter15Test.java index 8161862f..643b45e7 100644 --- a/chapter16/src/test/java/com/seaofnodes/simple/Chapter15Test.java +++ b/chapter16/src/test/java/com/seaofnodes/simple/Chapter15Test.java @@ -130,7 +130,8 @@ else { return rez; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region117,1.2,Phi(Region114,2.3,3.14));", stop.toString()); + // Shared allocation memory prevents folding the field Store into New. + assertEquals("return Phi(Region121,1.2,Phi(Region118,2.3,.y));", stop.toString()); assertEquals(3.14, Evaluator.evaluate(stop, 0)); assertEquals(1.2 , Evaluator.evaluate(stop, 1)); } diff --git a/chapter16/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/chapter16/src/test/java/com/seaofnodes/simple/Chapter16Test.java index 6fb41233..76fdb31e 100644 --- a/chapter16/src/test/java/com/seaofnodes/simple/Chapter16Test.java +++ b/chapter16/src/test/java/com/seaofnodes/simple/Chapter16Test.java @@ -73,7 +73,7 @@ if( arg ) { int x = y; x = x*x; y=x; } // Shadow final x return y; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region21,9,3);", stop.toString()); + assertEquals("return Phi(Region19,9,3);", stop.toString()); assertEquals(3L, Evaluator.evaluate(stop, 0)); assertEquals(9L, Evaluator.evaluate(stop, 1)); } @@ -205,4 +205,27 @@ return new Square; assertEquals("Obj<Square>{side=3.0,diag=1.7320508075688772}", Evaluator.evaluate(stop, 3).toString()); assertEquals("Obj<Square>{side=4.0,diag=2.0}", Evaluator.evaluate(stop, 4).toString()); } + static final String CONSTRUCTOR_MEMORY = """ + struct S { int x; int y; }; + struct T { int z=arg+40; }; + T t = new T; + S a = new S { x=11; y=7; }; + S b = new S { x=22; y=9; }; + S p=a; + if (arg) p=b; + int before=p.x; + S c = new S { + x=p.x+1; + { int i=0; while (i<2) { p.y=p.y+1; i=i+1; } } + y=p.y; + }; + p.x=33; + return before*10000+c.x*100+c.y+t.z; + """; + + @Test public void testConstructorMemory() { + StopNode stop = new Parser(CONSTRUCTOR_MEMORY).parse().iterate(); + assertEquals(111249L,Evaluator.evaluate(stop,0)); + assertEquals(222352L,Evaluator.evaluate(stop,1)); + } } diff --git a/chapter16/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/chapter16/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java index c215de00..3eb406eb 100644 --- a/chapter16/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java +++ b/chapter16/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java @@ -187,7 +187,7 @@ public class Evaluator { Object[] body=null; int num; if( type.isAry() ) { - long sz = (Long)val(alloc.in(1)); + long sz = (Long)val(alloc.size()); long n = offToIdx(sz, type); if( n < 0 ) throw new NegativeArraySizeException(""+n); @@ -201,17 +201,13 @@ public class Evaluator { assert elem instanceof TypeMemPtr; } // Length value - body[0] = vall(alloc.in(2+2)); + body[0] = vall(alloc.in(3)); } else { body = new Object[num = type._fields.length]; for (int i=0; i<num; i++) - body[i] = val(alloc.in(2+i+num)); + body[i] = val(alloc.in(3+i)); } - Object[] mems = new Object[type._fields.length+2]; - // mems[0] is control - mems[1] = new Obj(type,body); // the ref - // mems[2+...] are memory aliases - return mems; + return new Object[]{new Obj(type,body),MEMORY}; } private Object load(LoadNode load) { @@ -304,10 +300,10 @@ public class Evaluator { case ToFloatNode cast -> (double)vall(cast.in(1)); case LoadNode load -> load(load); case StoreNode store -> store(store); + case MemMergeNode merge -> MEMORY; case NewNode alloc -> alloc(alloc); case CProjNode cproj -> ((Object[])val(cproj.ctrl()))[cproj._idx]; case ProjNode proj -> ((Object[])val( proj.in(0) ))[ proj._idx]; - case ScopeMinNode mem -> null; default -> throw new AssertionError("Unexpected node " + node); }; } @@ -330,7 +326,7 @@ public class Evaluator { case null: return Status.FALLTHROUGH; case ReturnNode ret: - return val(ret.in(1)); + return val(ret.expr()); case IfNode ifn: block = block.next()[isTrue(val(ifn.in(1))) ? 0 : 1]; if (block == null) return Status.FALLTHROUGH; diff --git a/chapter16/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java b/chapter16/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java index 06169e2c..afcc700f 100644 --- a/chapter16/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java +++ b/chapter16/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java @@ -31,6 +31,8 @@ import java.util.*; */ public class Scheduler { + private final HashMap<Node,ArrayList<NodeData>> readers = new HashMap<>(); + /** * A basic block with a schedule of the containing nodes. * @@ -252,7 +254,7 @@ public class Scheduler { // but they did not refine the placement // so do that now. var mem = l.in(1); - for(var out : mem._outputs) { + for(var out : l.antiDeps()) { if (out instanceof PhiNode p) { var r = p.in(0); for (int i = 1; i < p.nIns(); i++) { @@ -270,13 +272,9 @@ public class Scheduler { 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) od(out).ifPresent(this::decUsers); - } - } + var loads = readers.get(data.node); + if (loads != null) + for (var load : loads) decUsers(load); } // Now all nodes should be placed and have a block assigned @@ -442,7 +440,6 @@ public class Scheduler { 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()) { @@ -467,16 +464,16 @@ public class Scheduler { } else { for (var in : node._inputs) if (in != null && !(in instanceof CFGNode)) 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) od(out).ifPresent(d->d.users++); - } } + // Writes, including New behind an aggregate, schedule before their + // anti-dependent reads in this reverse scheduling pass. + for (var load : data.values()) + if (load.node instanceof LoadNode l) + for (var use : l.antiDeps()) + if ((use instanceof StoreNode || use instanceof NewNode) && data.containsKey(use)) { + load.users++; + readers.computeIfAbsent(use,k->new ArrayList<>()).add(load); + } } /** diff --git a/chapter17/README.md b/chapter17/README.md index cdbca21b..a3a2d992 100644 --- a/chapter17/README.md +++ b/chapter17/README.md @@ -165,3 +165,18 @@ for( int i=0; i<ary#; i++ ) return i; return -1; ``` + +## Memory effects + +The lazy memory representation from [Chapter 10b](../chapter10b/README.md) and +constructor layout from [Chapter 16](../chapter16/README.md#memory-through-constructors) +continue here. ScopeNode tracks one `$mem` binding. Field and array writes, +including increments, update their alias in a MemMerge. Branches, conditional +expressions, and loops merge whole memory with BulkMemPhi; MemPhis are discovered +as field uses require them. + +Readonly references retain their deep immutability when Loads fold through +Stores or allocations. Forward reference types still resolve lazily. Phi +factoring uses the same one-step Load safety check. Allocation memory remains +shared across the struct's aliases, so the sole-use Store-to-New check may +leave a Load that separate memory projections allowed to fold. diff --git a/chapter17/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java b/chapter17/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java index ee6e1f1a..eba99128 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java @@ -109,12 +109,8 @@ public abstract class GlobalCodeMotion { // Loads need their memory inputs' uses also done if( n instanceof LoadNode ld ) - for( Node memuse : ld.mem()._outputs ) - if( late[memuse._nid]==null && - // Load-use directly defines memory - (memuse._type instanceof TypeMem || - // Load-use indirectly defines memory - (memuse._type instanceof TypeTuple tt && tt._types[ld._alias] instanceof TypeMem)) ) + for( Node memuse : ld.antiDeps() ) + if( late[memuse._nid]==null ) continue outer; // All uses done, schedule @@ -126,9 +122,7 @@ public abstract class GlobalCodeMotion { for( Node def : n._inputs ) { if( def==null ) continue; if( late[def._nid]==null ) work.push(def); - for( Node out : def._outputs ) - if( out instanceof LoadNode ld && late[ld._nid]==null ) - work.push(ld); + wakeLoads(def,late,work,new BitSet()); } if( n instanceof LoopNode loop ) for( Node phi : loop._outputs ) @@ -185,13 +179,24 @@ public abstract class GlobalCodeMotion { best instanceof IfNode; } + // An allocation may consume a load's memory through a partial aggregate. + private static void wakeLoads(Node def, CFGNode[] late, WorkList<Node> work, BitSet visit) { + if( visit.get(def._nid) ) return; + visit.set(def._nid); + for( Node out : def._outputs ) + if( out instanceof LoadNode ld && late[ld._nid]==null ) work.push(ld); + if( def instanceof MemMergeNode ) + for( int i=1; i<def.nIns(); i++ ) + if( def.in(i)!=null ) wakeLoads(def.in(i),late,work,visit); + } + private static CFGNode find_anti_dep(CFGNode lca, LoadNode load, CFGNode early, CFGNode[] late, int[] anti) { // We could skip final-field loads here. // Walk LCA->early, flagging Load's block location choices for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() ) anti[cfg._nid] = load._nid; // Walk load->mem uses, looking for Stores causing an anti-dep - for( Node mem : load.mem()._outputs ) { + for( Node mem : load.antiDeps() ) { switch( mem ) { case StoreNode st: assert late[st._nid]!=null; @@ -208,10 +213,6 @@ public abstract class GlobalCodeMotion { if( phi.in(i)==load.mem() ) lca = anti_dep(load,phi.region().cfg(i),load.mem().cfg0(),lca,null,anti); break; - case LoadNode ld: break; // Loads do not cause anti-deps on other loads - case ReturnNode ret: break; // Load must already be ahead of Return - case ScopeMinNode ret: break; // Mem uses now on ScopeMin - case NeverNode never: break; default: throw Utils.TODO(); } } diff --git a/chapter17/src/main/java/com/seaofnodes/simple/Parser.java b/chapter17/src/main/java/com/seaofnodes/simple/Parser.java index 86b59dc3..9f81d556 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/Parser.java @@ -164,11 +164,8 @@ public class Parser { _xScopes.push(_scope); // Enter a new scope for the initial control and arguments _scope.push(); - ScopeMinNode mem = new ScopeMinNode(); - mem.addDef(null); _scope.define(ScopeNode.CTRL, Type.CONTROL , false, new CProjNode(START, 0, ScopeNode.CTRL).peephole()); - mem.addDef( new ProjNode(START, 1, ScopeNode.MEM0).peephole()); - _scope.define(ScopeNode.MEM0, TypeMem.TOP , false, mem .peephole()); + _scope.define(ScopeNode.MEM0, TypeMem.BOT , false, new ProjNode(START, 1, ScopeNode.MEM0).peephole()); _scope.define(ScopeNode.ARG0, TypeInteger.BOT, false, new ProjNode(START, 2, ScopeNode.ARG0).peephole()); // Parse whole program @@ -525,7 +522,7 @@ public class Parser { Node expr = parseAsgn(); // Final variable to update - ScopeMinNode.Var def = _scope.lookup(name); + ScopeNode.Var def = _scope.lookup(name); if( def==null ) throw error("Undefined name '" + name + "'"); @@ -654,7 +651,7 @@ public class Parser { Field[] fs = new Field[varlen-lexlen]; for( int i=lexlen; i<varlen; i++ ) { s.addDef(_scope.in(i)); - ScopeMinNode.Var v = _scope._vars.at(i); + ScopeNode.Var v = _scope._vars.at(i); fs[i-lexlen] = Field.make(v._name,v.type(),ALIAS++,v._final); } TypeStruct ts = s._ts = TypeStruct.make(typeName, fs); @@ -873,7 +870,7 @@ public class Parser { int delta = _lexer.peek(-1)=='+' ? 1 : -1; // Pre vs post String name = _lexer.matchId(); if( name!=null ) { - ScopeMinNode.Var n = _scope.lookup(name); + ScopeNode.Var n = _scope.lookup(name); if( n != null ) { if( n._final ) throw error("Cannot reassign final '"+n._name+"'"); @@ -906,7 +903,7 @@ public class Parser { if( match("(") ) return require(parseAsgn(), ")"); if( matchx("new" ) ) return alloc(); // Expect an identifier now - ScopeMinNode.Var n = requireLookupId("an identifier or expression"); + ScopeNode.Var n = requireLookupId("an identifier or expression"); Node rvalue = _scope.in(n); if( rvalue._type == Type.BOTTOM ) throw error("Cannot read uninitialized field '"+n._name+"'"); @@ -941,11 +938,11 @@ public class Parser { return pre ? op : rvalue.unkeep(); } - ScopeMinNode.Var requireLookupId(String msg) { + ScopeNode.Var requireLookupId(String msg) { String id = _lexer.matchId(); if( id == null || KEYWORDS.contains(id) ) throw errorSyntax(msg); - ScopeMinNode.Var n = _scope.lookup(id); + ScopeNode.Var n = _scope.lookup(id); if( n==null ) throw error("Undefined name '" + id + "'"); return n; @@ -1010,21 +1007,25 @@ public class Parser { Field[] fs = obj._fields; if( fs==null ) throw error("Unknown struct type '" + obj._name + "'"); - int len = fs.length; - Node[] ns = new Node[2+len+len]; - ns[0] = ctrl(); // Control in slot 0 - // Total allocated length in bytes - ns[1] = size; - // Memory aliases for every field - for( int i = 0; i < len; i++ ) - ns[2 +i] = memAlias(fs[i]._alias); - // Initial values for every field - for( int i = 0; i < len; i++ ) - ns[2+len+i] = init.get(i+idx); - Node nnn = new NewNode(TypeMemPtr.make(obj), ns).peephole(); - for( int i = 0; i < len; i++ ) - memAlias(fs[i]._alias, new ProjNode(nnn,i+2,memName(fs[i]._alias)).peephole()); - return new ProjNode(nnn,1,obj._name).peephole(); + Node prior = mem().keep(); + MemMergeNode input = new MemMergeNode(null); + for( Field f : fs ) input.alias(f._alias,prior); + // Constructor values follow control, memory, and allocation size. + Node[] ns = new Node[3+fs.length]; + ns[0] = ctrl(); + ns[1] = input.peephole(); + ns[2] = size; + for( int i=0; i<fs.length; i++ ) ns[3+i] = init.get(idx+i); + Node nnn = new NewNode(TypeMemPtr.make(obj),ns).peephole().keep(); + Node ptr = new ProjNode(nnn,0,obj._name).peephole().keep(); + Node out = new ProjNode(nnn,1,"$mem").peephole().keep(); + MemMergeNode after = new MemMergeNode(prior); + for( Field f : fs ) after.alias(f._alias,out); + mem(after.peephole()); + out.unkeep(); + nnn.unkeep(); + prior.unkeep(); + return ptr.unkeep(); } private static final Ary<Node> ALTMP = new Ary<>(Node.class); @@ -1036,13 +1037,18 @@ public class Parser { return newStruct(ary,size,0,ALTMP); } - // 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.mem(alias ); } - private void memAlias(int alias, Node st) { _scope.mem(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.mem(); } + private void mem(Node n) { _scope.mem(n); } + + private void store(String name, int alias, Type glb, Node ptr, Node off, Node val, boolean init, Node ctrl) { + Node prior = mem().keep(); + Node st = new StoreNode(name,alias,glb,prior,ptr,off,val,init); + st.setDef(0,ctrl); + st = st.peephole(); + mem(new MemMergeNode(prior,alias,st).peephole()); + prior.unkeep(); + } /** * Parse postfix expression. For now this is just a field @@ -1085,6 +1091,7 @@ public class Parser { if( tf instanceof TypeMemPtr ftmp && ftmp.isFRef() ) tf = ftmp.makeFrom(((TypeMemPtr)(TYPES.get(ftmp._obj._name)))._obj); + expr.keep(); // Field offset; fixed for structs, computed for arrays Node off = (name.equals("[]") // If field is an array body // Array index math @@ -1097,16 +1104,11 @@ public class Parser { Node val = parseAsgn().keep(); Node lift = liftExpr( val, tf, f._final ); - Node st = new StoreNode(name, f._alias, tf, memAlias(f._alias), expr, off.unkeep(), lift, false); - // Arrays include control, as a proxy for a safety range check. - // Structs don't need this; they only need a NPE check which is - // done via the type system. - if( base.isAry() ) st.setDef(0,ctrl()); - memAlias(f._alias, st.peephole()); + store(name,f._alias,tf,expr.unkeep(),off.unkeep(),lift,false,base.isAry() ? ctrl() : null); return val.unkeep(); // "obj.a = expr" returns the expression while updating memory } - Node load = new LoadNode(name, f._alias, tf.glb(), memAlias(f._alias), expr.keep(), off); + Node load = new LoadNode(name, f._alias, tf.glb(), mem(), expr, off); // Arrays include control, as a proxy for a safety range check // Structs don't need this; they only need a NPE check which is // done via the type system. @@ -1119,12 +1121,7 @@ public class Parser { throw error("Cannot reassign final '"+f._fname+"'"); Node inc = peep(new AddNode(load,con( _lexer.peek(-1)=='+' ? 1 : -1))); Node val = zsMask(inc,tf); - Node st = new StoreNode(name, f._alias, tf, memAlias(f._alias), expr.unkeep(), off, val, false); - // Arrays include control, as a proxy for a safety range check. - // Structs don't need this; they only need a NPE check which is - // done via the type system. - if( base.isAry() ) st.setDef(0,ctrl()); - memAlias(f._alias, peep(st)); + store(name,f._alias,tf,expr.unkeep(),off,val,false,base.isAry() ? ctrl() : null); // And use the original loaded value as the result } else expr.unkill(); off.unkill(); diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java new file mode 100644 index 00000000..84f97189 --- /dev/null +++ b/chapter17/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 mmm.in(1)!=null && missingAlias(mmm)==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); + 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); + 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; + } + + // Every explicit slot requests that alias, including a partial allocation + // input whose slot still points to this bulk Phi. + private int missingAlias(MemMergeNode mmm) { + for( int alias=2; alias<mmm.nIns(); alias++ ) + if( mmm.in(alias)!=null && + mmm.alias(alias)!=mmm.in(1) && + !_aliases.get(alias) ) + 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,Type.BOTTOM)); + 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/chapter17/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/LoadNode.java index 17ed127c..2b430df4 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -1,9 +1,10 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.type.*; import java.util.BitSet; +import java.util.ArrayList; /** * Load represents extracting a value from inside a memory object, @@ -23,6 +24,25 @@ public class LoadNode extends MemOpNode { super(name, alias, glb, mem, ptr, off); } + @Override boolean canDrop(MemOpNode other, Node dep) { + return super.canDrop(other,dep) && !clobbered(dep); + } + + // Check only immediate memory users. Stores clobber memory; Phis and + // aggregates might lead to a clobber, so stop rather than search further. + private boolean clobbered(Node dep) { + Node mem = mem(); + mem.addDepForwards(dep); + for( Node use : mem._outputs ) { + if( use==null ) continue; + use.addDepForwards(dep); + if( use instanceof StoreNode || use instanceof PhiNode || + use instanceof MemMergeNode || + use instanceof NewNode ) return true; + } + return false; + } + // Debugger label @Override public String label() { return "ld_"+mlabel(); } @Override @@ -30,31 +50,31 @@ public class LoadNode extends MemOpNode { @Override public Type compute() { - if( mem()._type instanceof TypeMem mem ) { - // Update declared forward ref to the actual - if( _declaredType.isFRef() && mem._t instanceof TypeMemPtr tmp && !tmp.isFRef() ) - _declaredType = tmp; - // No lifting if ptr might null-check - if( err()==null ) - return _declaredType.join(mem._t); - } - return _declaredType; + Type t = MemMergeNode.contents(mem(),_alias,this); + // Update declared forward ref to the actual. + if( _declaredType.isFRef() && t instanceof TypeMemPtr tmp && !tmp.isFRef() ) + _declaredType = tmp; + return err()==null ? _declaredType.join(t) : _declaredType; } @Override public Node idealize() { + if( mem() instanceof MemMergeNode merge ) { + setDef(1,IterPeeps.add(merge.alias(_alias))); + return this; + } Node ptr = ptr(); // Simple Load-after-Store on same address. if( mem() instanceof StoreNode st && - ptr == st.ptr() && off() == st.off() ) { // Must check same object + _alias==st._alias && ptr == st.ptr() && off() == st.off() ) { // Must check same object assert _name.equals(st._name); // Equiv class aliasing is perfect return st.val(); } // Simple Load-after-New on same address. if( mem() instanceof ProjNode p && p.in(0) instanceof NewNode nnn && - ptr == nnn.proj(1) ) // Must check same object + ptr == nnn.proj(0) ) // Must check same object return nnn.in(nnn.findAlias(_alias)); // Load from New init // Load-after-Store on same address, but bypassing provably unrelated @@ -65,12 +85,16 @@ public class LoadNode extends MemOpNode { Node mem = mem(); outer: while( true ) { + mem.addDep(this); switch( mem ) { + case MemMergeNode merge: + mem = merge.alias(_alias); + break; case StoreNode st: - if( ptr == st.ptr() && off() == st.off() ) + if( _alias==st._alias && ptr == st.ptr() && off() == st.off() ) return castRO(st.val()); // Proved equal // Can we prove unequal? Offsets do not overlap? - if( !off()._type.join(st.off()._type).isHigh() && // Offsets overlap + if( _alias==st._alias && !off()._type.join(st.off()._type).isHigh() && // Offsets overlap !neverAlias(ptr,st.ptr()) ) // And might alias break outer; // Cannot tell, stop trying // Pointers cannot overlap @@ -84,13 +108,11 @@ public class LoadNode extends MemOpNode { return castRO(nnn1.in(nnn1.findAlias(_alias))); // Load from New init if( !(ptr instanceof ProjNode pproj && pproj.in(0) instanceof NewNode nnn2) ) break outer; // Cannot tell, ptr not related to New - mem = nnn1.in(_alias);// Bypass unrelated New + mem = nnn1.mem();// Bypass unrelated New break; - } else if( mproj.in(0) instanceof StartNode ) { - break outer; - } else throw Utils.TODO(); + } else break outer; default: - throw Utils.TODO(); + break outer; } } @@ -101,7 +123,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 && // Offset can be hoisted off() instanceof ConstantNode && // Pointer can be hoisted @@ -122,7 +144,9 @@ public class LoadNode extends MemOpNode { private Node ld( int idx ) { Node mem = mem(), ptr = ptr(); - return new LoadNode(_name,_alias,_declaredType,mem.in(idx),ptr instanceof PhiNode && ptr.in(0)==mem.in(0) ? ptr.in(idx) : ptr, off()).peephole(); + LoadNode ld = new LoadNode(_name,_alias,_declaredType,mem.in(idx),ptr instanceof PhiNode && ptr.in(0)==mem.in(0) ? ptr.in(idx) : ptr,off()); + ld.setDef(0,in(0)); + return ld.peephole(); } private static boolean neverAlias( Node ptr1, Node ptr2 ) { @@ -155,8 +179,9 @@ public class LoadNode extends MemOpNode { private boolean profit(PhiNode phi, int idx) { Node px = phi.in(idx); if( px==null ) return false; - if( px._type instanceof TypeMem mem && mem._t.isHighOrConst() ) { px.addDep(this); return true; } - if( px instanceof StoreNode st1 && ptr()==st1.ptr() && off()==st1.off() ) { px.addDep(this); return true; } + px.addDep(this); + if( px._type instanceof TypeMem mem && mem._t.isHighOrConst() ) return true; + if( px instanceof StoreNode st1 && _alias==st1._alias && ptr()==st1.ptr() && off()==st1.off() ) return true; return false; } @@ -166,4 +191,29 @@ public class LoadNode extends MemOpNode { return new ReadOnlyNode(rez).peephole(); return rez; } + + // Memory writers which must follow this read. A packaging node is not a + // write, but can hide New's input, so follow this alias through aggregates. + public ArrayList<Node> antiDeps() { + ArrayList<Node> deps = new ArrayList<>(); + antiDeps(mem(),deps,new BitSet()); + return deps; + } + + private void antiDeps(Node mem, ArrayList<Node> deps, BitSet visit) { + if( visit.get(mem._nid) ) return; + visit.set(mem._nid); + for( Node use : mem._outputs ) { + if( use instanceof MemMergeNode merge ) { + Node slice = _alias<merge.nIns() ? merge.in(_alias) : null; + if( (slice==mem) || (slice==null && merge.in(1)==mem) ) + antiDeps(merge,deps,visit); + } else if( (use instanceof StoreNode st && st._alias==_alias) || + (use instanceof NewNode nnn && nnn.field(_alias)!=null) || + (use instanceof MemPhiNode phi && phi._alias==_alias) || + (use instanceof BulkMemPhiNode phi && !phi.isSplit(_alias)) ) { + if( !deps.contains(use) ) deps.add(use); + } + } + } } diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java new file mode 100644 index 00000000..ff040b99 --- /dev/null +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java @@ -0,0 +1,89 @@ +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 default slice and explicit alias overrides. A null default means partial + * memory: absent aliases are not covered, rather than having unknown contents. + */ +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); + n = n==null ? in(1) : n; + assert n!=null : "Alias not covered: "+alias; + return n; + } + + public void alias(int alias, Node mem) { + assert alias > 1; + while( nIns()<=alias ) addDef(null); + setDef(alias,mem); + } + + // Scalar contents of one alias. New's field inputs supply the initialized type; + // join it with the incoming contents for all previously allocated objects. + // Phis use their cached types, so this query does not recurse around loops. + static Type contents(Node mem, int alias, Node dep) { + mem.addDep(dep); + if( mem instanceof MemMergeNode merge ) + return contents(merge.alias(alias),alias,dep); + if( mem instanceof ProjNode proj && proj.in(0) instanceof NewNode nnn ) { + assert proj._idx==1 && nnn.field(alias)!=null; + nnn.addDep(dep); + Node init = nnn.in(nnn.findAlias(alias)).addDep(dep); + return contents(nnn.mem(),alias,dep).meet(init._type); + } + // There are no heap arguments in this chapter. Before the first New, + // the set of allocated objects (and hence stored values) is empty. + if( mem instanceof ProjNode proj && proj.in(0) instanceof StartNode ) return Type.TOP; + if( mem._type==Type.TOP || mem._type==TypeMem.TOP ) return Type.TOP; + if( mem._type instanceof TypeMem mt && mt._alias==alias ) return mt._t; + return Type.BOTTOM; + } + + @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)!=null && in(i)==in(1) ) { setDef(i,null); progress=true; } + if( in(i) instanceof MemMergeNode mem ) { + setDef(i,IterPeeps.add(mem.alias(i))); + progress=true; + } + if( in(i)!=null ) allDefault=false; + } + if( allDefault && in(1)!=null ) 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/chapter17/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/MemOpNode.java index 82aab8ac..0bf9b6ac 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/MemOpNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/MemOpNode.java @@ -33,6 +33,13 @@ public abstract class MemOpNode extends Node { public Node ptr() { return in(2); } public Node off() { return in(3); } + // Extra conditions for factoring matching memory operations through a Phi. + // The caller has already checked the opcode, input shape and control. + boolean canDrop(MemOpNode other, Node dep) { + ptr().addDep(dep); + return _alias==other._alias && _declaredType==other._declaredType && err()==null; + } + @Override boolean eq(Node n) { MemOpNode mem = (MemOpNode)n; // Invariant diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java new file mode 100644 index 00000000..6aae00f9 --- /dev/null +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java @@ -0,0 +1,42 @@ +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,Type.BOTTOM),inputs); + assert alias > 1; + _alias = alias; + } + + @Override public String label() { return "MemPhi_"+_alias; } + + @Override public Type compute() { + assert BulkMemPhiNode.checkMem(this,_alias); + if( !(region() instanceof RegionNode r) ) + return region()._type==Type.XCONTROL ? TypeMem.TOP : _type; + if( r.inProgress() ) return _declaredType; + Type t = Type.TOP; + for( int i=1; i<nIns(); i++ ) + if( r.in(i).addDep(this)._type!=Type.XCONTROL ) + t = t.meet(MemMergeNode.contents(in(i),_alias,this)); + return TypeMem.make(_alias,t); + } + + @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/chapter17/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/NewNode.java index 77800be2..bdccc681 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/NewNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/NewNode.java @@ -1,34 +1,33 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.Utils; import java.util.BitSet; -/** - * Allocation! Allocate a chunk of memory, and pre-zero it. - * The inputs include control and size, and ALL aliases being set. - * The output is large tuple, one for every alias plus the created pointer. - * New is expected to be followed by projections for every alias. +/** Allocate and initialize an object. Inputs {ctrl, $mem, size, fields...}; + * results {ptr, $mem}. + * The memory input and result cover only the aliases in the allocated struct. */ public class NewNode extends Node implements MultiNode { public final TypeMemPtr _ptr; - public final int _len; public NewNode(TypeMemPtr ptr, Node... nodes) { super(nodes); _ptr = ptr; - _len = ptr._obj._fields.length; - // Control in slot 0 - assert nodes[0]._type==Type.CONTROL || nodes[0]._type == Type.XCONTROL; - // Malloc-length in slot 1 - assert nodes[1]._type instanceof TypeInteger; - for( int i=0; i<_len; i++ ) { - // Memory slices for all fields. - assert nodes[2+ i]._type instanceof TypeMem; - // Value slices for all fields. - assert nodes[2 + _len + i]._type != null; - } + assert nodes.length==3+ptr._obj._fields.length; + assert nodes[0]._type==Type.CONTROL || nodes[0]._type==Type.XCONTROL; + assert nodes[1]._type instanceof TypeMem; + assert nodes[2]._type instanceof TypeInteger; + for( int i=3; i<nodes.length; i++ ) assert nodes[i]._type!=null; + } + + public Node mem() { return in(1); } + public Node size() { return in(2); } + + public Field field(int alias) { + for( Field f : _ptr._obj._fields ) + if( f._alias==alias ) return f; + return null; } @Override public String label() { @@ -42,22 +41,13 @@ public class NewNode extends Node implements MultiNode { // Find matching alias input int findAlias(int alias) { - return 2+_ptr._obj.findAlias(alias)+_len; + return 3+_ptr._obj.findAlias(alias); } @Override public TypeTuple compute() { - Field[] fs = _ptr._obj._fields; - Type[] ts = new Type[fs.length+2]; - ts[0] = Type.CONTROL; - ts[1] = _ptr; - for( int i=0; i<fs.length; i++ ) { - TypeMem mem = (TypeMem)in(i+2)._type; - Type tfld = in(2+_len+i)._type.meet(mem._t); - ts[i+2] = TypeMem.make(fs[i]._alias,tfld); - } - return TypeTuple.make(ts); + return TypeTuple.make(_ptr,TypeMem.BOT); } @Override diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter17/src/main/java/com/seaofnodes/simple/node/Node.java index 24934c7b..dc952d87 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/Node.java @@ -16,14 +16,14 @@ import java.util.function.Function; * The Node class provides common functionality used by all subtypes. * Subtypes of Node specialize by overriding methods. */ -public abstract class Node implements OutNode { +public abstract class Node implements OutNode, Cloneable { /** * 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; + public int _nid; /** * Inputs to the node. These are use-def references to Nodes. @@ -32,7 +32,7 @@ public abstract class Node implements OutNode { * Ordering is required because e.g. "a/b" is different from "b/a". * The first input (offset 0) is often a {@link CFGNode} node. */ - public final Ary<Node> _inputs; + public Ary<Node> _inputs; /** * Outputs reference Nodes that are not null and have this Node as an @@ -43,7 +43,7 @@ public abstract class Node implements OutNode { * walked in either direction. These outputs are typically used for * efficient optimizations but otherwise have no semantics meaning. */ - public final Ary<Node> _outputs; + public Ary<Node> _outputs; /** @@ -214,6 +214,7 @@ public abstract class Node implements OutNode { // Error is 'use' does not exist; ok for 'use' to be null. protected boolean delUse( Node use ) { _outputs.del(_outputs.find(use)); + moveDepsToWorklist(); // User-count and anti-dependence queries can now change. return _outputs.isEmpty(); } @@ -274,6 +275,7 @@ public abstract class Node implements OutNode { n.unlock(); int idx = n._inputs.find(this); n._inputs.set(idx,nnn); + n.moveDepsToWorklist(); // Rewiring can change a dependent query without changing type. nnn.addUse(n); IterPeeps.addAll(n._outputs); } @@ -460,6 +462,14 @@ public abstract class Node implements OutNode { * 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; @@ -467,8 +477,8 @@ public abstract class Node implements OutNode { if( obs != null ) obs.dep(this, dep); if( _deps==null ) _deps = new Ary<>(Node.class); if( _deps .find(dep) != -1 ) return this; // Already on list - if( _inputs .find(dep) != -1 ) return this; // No need for deps on immediate neighbors - if( _outputs.find(dep) != -1 ) return this; + if( !forwards && _inputs .find(dep) != -1 ) return this; // No need for deps on immediate neighbors + if( !forwards && _outputs.find(dep) != -1 ) return this; _deps.add(dep); return this; } @@ -594,6 +604,20 @@ public abstract class Node implements OutNode { // Does not need to be implemented in isCFG() nodes. Node copy(Node lhs, Node rhs) { throw Utils.TODO("Binary ops need to implement copy"); } + // Exact-class copy preserving operation attributes, with fresh identity + // and no edges, dependencies, or GVN membership. + public final Node copyEmpty() { + Node n; + try { n = (Node)clone(); } + catch( CloneNotSupportedException e ) { throw new AssertionError(e); } + n._nid = UNIQUE_ID++; + n._inputs = new Ary<>(Node.class); + n._outputs = new Ary<>(Node.class); + n._deps = null; + n._hash = 0; + return n; + } + // Report any post-optimize errors String err() { return null; } diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/PhiNode.java index 0532d2b7..c6cbd3dd 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -16,6 +16,10 @@ public class PhiNode extends Node { 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_"+MemOpNode.mlabel(_label); } @Override @@ -65,24 +69,9 @@ public class PhiNode extends Node { if (live != null) return live; - // Pull "down" a common data op. One less op in the world. One more - // Phi, but Phis do not make code. - // Phi(op(A,B),op(Q,R),op(X,Y)) becomes - // op(Phi(A,Q,X), Phi(B,R,Y)). - Node op = in(1); - if( op.nIns()==3 && op.in(0)==null && same_op() ) { - assert !(op instanceof CFGNode); - 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); - } + // Phi(op(a,b,...),op(c,d,...)) -> op(Phi(a,c),Phi(b,d),...). + Node down; + if( same_op() && (down=drop_same_op())!=null ) return down; // If merging Phi(N, cast(N)) - we are losing the cast JOIN effects, so just remove. if( nIns()==3 ) { @@ -112,12 +101,72 @@ public class PhiNode extends Node { } private boolean same_op() { - for( int i=2; i<nIns(); i++ ) - if( in(1).getClass() != in(i).getClass() ) - return false; + Node op = in(1); + if( op instanceof CFGNode || op instanceof ConstantNode || op instanceof PhiNode || + op instanceof ProjNode || op instanceof NewNode || op instanceof ScopeNode || op instanceof MemMergeNode ) return false; + // A bulk Phi must split its aliases before factoring precise Stores. + if( this instanceof BulkMemPhiNode ) return false; + // Bulk splitting currently identifies parallel slices by Region/alias. + // Factoring can introduce another slice at a different memory point; + // wait until that Region's bulk partitioning has finished. + if( op instanceof MemOpNode ) + for( Node use : region()._outputs ) + if( use instanceof BulkMemPhiNode ) { + use.addDepForwards(this); + return false; + } + Node busy = null; + for( int i=1; i<nIns(); i++ ) { + Node n = in(i); + if( region().in(i).addDep(this)._type==Type.XCONTROL || + op.getClass()!=n.getClass() || n.nIns()!=op.nIns() || n.in(0)!=null || !op.eq(n) ) return false; + if( n instanceof MemOpNode mem && !mem.canDrop((MemOpNode)op,this) ) return false; + // Moving a Store must remove the old effect, not duplicate it. + // Allow one shared arm: the other arms disappear into the single + // factored operation, usually reducing the total operation count. + n.addDepForwards(this); + if( n.nOuts()>1 ) { + for( Node use : n._outputs ) + if( use!=null && use!=this ) use.addDepForwards(this); + if( n instanceof StoreNode || busy!=null ) return false; + busy=n; + } + for( int j=1; j<n.nIns(); j++ ) + if( n.in(j) instanceof ScopeNode || ((n.in(j)==null) != (op.in(j)==null)) ) return false; + } return true; } + private Node drop_same_op() { + Node op = in(1), cp = op.copyEmpty(); + cp._type = null; + cp.addDef(null); + for( int j=1; j<op.nIns(); j++ ) { + Node x = op.in(j); + boolean different = false; + for( int i=2; i<nIns(); i++ ) + if( in(i).in(j)!=x ) different=true; + if( different ) { + Node[] ins = new Node[nIns()]; + ins[0] = region(); + Type t = Type.TOP; + for( int i=1; i<nIns(); i++ ) { + ins[i] = in(i).in(j); + t = t.meet(ins[i]._type); + } + PhiNode phi = j==1 && op instanceof MemOpNode mem + ? new MemPhiNode(_label,mem._alias,ins) + : PhiNode.make(_label,t.glb(),ins); + x = phi.peephole(); + } + cp.addDef(x); + } + // Factoring must not widen the result (e.g. correlated And operands). + if( cp.compute().isa(compute()) ) return cp; + cp.kill(); + return null; + } + /** * If only single unique input, return it */ diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/RegionNode.java index 59da86ff..2155bd94 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/RegionNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/RegionNode.java @@ -68,7 +68,7 @@ public class RegionNode extends CFGNode { if( !hasPhi() && // No Phi users, just a control user in(1) instanceof CProjNode p1 && in(2) instanceof CProjNode p2 && - p1.in(0).addDep(this)==p2.in(0).addDep(this) && + p1.addDepForwards(this).in(0)==p2.addDepForwards(this).in(0) && p1.in(0) instanceof IfNode iff ) return iff.ctrl(); diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/ReturnNode.java index b586a44f..1049fa18 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -4,27 +4,17 @@ 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 all preceding memory effects and the return value alive: {ctrl, $mem, value}. */ public class ReturnNode extends CFGNode { public ReturnNode(Node ctrl, Node data, ScopeNode scope) { - // Add memory slices to Return, so all memory updates are live-on-exit. - super(ctrl, data); - if( scope!=null ) - for( int i=2; i<scope.mem().nIns(); i++ ) - addDef(scope.mem().in(i)); + // A synthetic never-taken loop exit has no source scope. + super(ctrl, scope == null ? null : scope.mem(), data); } public Node ctrl() { return in(0); } - public Node expr() { return in(1); } + public Node mem () { return in(1); } + public Node expr() { return in(2); } @Override public String label() { return "Return"; } @@ -38,7 +28,8 @@ public class ReturnNode extends CFGNode { @Override public Type compute() { - return TypeTuple.make(ctrl()._type,expr()._type); + // Return exposes the complete memory state. + return TypeTuple.make(ctrl()._type,TypeMem.BOT,expr()._type); } @Override diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/ScopeMinNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/ScopeMinNode.java deleted file mode 100644 index 9134f9c3..00000000 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/ScopeMinNode.java +++ /dev/null @@ -1,150 +0,0 @@ -package com.seaofnodes.simple.node; - -import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.Utils; -import com.seaofnodes.simple.type.*; -import java.util.*; - -import static com.seaofnodes.simple.Utils.TODO; - -/* */ -public class ScopeMinNode extends Node { - - /** The tracked fields are now complex enough to deserve a array-of-structs layout - */ - public static class Var { - public final int _idx; // index in containing scope - public final String _name; // Declared name - private Type _type; // Declared type - public final boolean _final; // Final field - public Var(int idx, String name, Type type, boolean xfinal) { - _idx = idx; - _name = name; - _type = type; - _final = xfinal; - } - public Type type() { - if( !_type.isFRef() ) return _type; - // Update self to no longer use the forward ref type - Type def = Parser.TYPES.get(((TypeMemPtr)_type)._obj._name); - return (_type=_type.meet(def)); - } - public Type lazyGLB() { - Type t = type(); - return t instanceof TypeMemPtr ? t : t.glb(); - } - @Override public String toString() { - return _type.toString()+(_final ? " ": " !")+_name; - } - } - - public ScopeMinNode() { _type = TypeMem.BOT; } - - - @Override public String label() { return "MEM"; } - - @Override - StringBuilder _print1(StringBuilder sb, BitSet visited) { - sb.append("MEM[ "); - for( int j=2; j<nIns(); j++ ) { - sb.append(j); - sb.append(":"); - Node n = in(j); - while( n instanceof ScopeNode loop ) { - sb.append("Lazy_"); - n = loop.in(j); - } - if( n==null ) sb.append("___ "); - else n._print0(sb, visited).append(" "); - } - sb.setLength(sb.length()-1); - return sb.append("]"); - } - - - @Override public Type compute() { return TypeMem.BOT; } - - @Override public Node idealize() { return null; } - - public Node in( Var v ) { return in(v._idx); } - - Node alias( int alias ) { - return in(alias<nIns() && in(alias)!=null ? alias : 1); - } - - Node alias( int alias, Node st ) { - while( alias >= nIns() ) addDef(null); - return setDef(alias,st); - } - - - // Read or update from memory. - // A shared implementation allows us to create lazy phis both during - // lookups and updates; the lazy phi creation is part of chapter 8. - Node _mem( int alias, Node st ) { - // Memory projections are made lazily; if one does not exist - // then it must be START.proj(1) - Node old = alias(alias); - if( old instanceof ScopeNode loop ) { - ScopeMinNode loopmem = loop.mem(); - Node memdef = loopmem.alias(alias); - // Lazy phi! - old = memdef instanceof PhiNode phi && loop.ctrl()==phi.region() - // Loop already has a real Phi, use it - ? memdef - // 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. - : loopmem.alias(alias, new PhiNode(Parser.memName(alias), TypeMem.BOT,loop.ctrl(),loopmem._mem(alias,null),null).peephole() ); - alias(alias,old); - } - // Memory projections are made lazily; expand as needed - return st==null ? old : alias(alias,st); // Not lazy, so this is the answer - } - - - void _merge(ScopeMinNode that, RegionNode r) { - int len = Math.max(nIns(),that.nIns()); - for( int i = 2; i < len; i++) - if( alias(i) != that.alias(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 - //Var v = _vars.at(i); - Node lhs = this._mem(i,null); - Node rhs = that._mem(i,null); - alias(i, new PhiNode(Parser.memName(i), TypeMem.BOT, r, lhs, rhs).peephole()); - } - } - - // Fill in the backedge of any inserted Phis - void _endLoopMem( ScopeNode scope, ScopeMinNode back, ScopeMinNode exit ) { - for( int i=2; i<back.nIns(); i++ ) { - if( back.in(i) != scope ) { - PhiNode phi = (PhiNode)in(i); - assert phi.region()==scope.ctrl() && phi.in(2)==null; - phi.setDef(2,back.in(i)); // Fill backedge - } - if( exit.alias(i) == scope ) // Replace a lazy-phi on the exit path also - exit.alias(i,in(i)); - } - } - - // Now one-time do a useless-phi removal - void _useless( ) { - for( int i=2; 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 - } - } - } - } - -} diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/ScopeNode.java index e71c669f..d44535dc 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -1,17 +1,16 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.IterPeeps; +import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.Ary; import com.seaofnodes.simple.type.*; import java.util.*; -import static com.seaofnodes.simple.Utils.TODO; - /** * The Scope node is purely a parser helper - it tracks names to nodes with a - * stack of hashmaps. + * stack of variable records. */ -public class ScopeNode extends ScopeMinNode { +public class ScopeNode extends Node { /** * The control is a name that binds to the currently active control @@ -21,10 +20,43 @@ public class ScopeNode extends ScopeMinNode { public static final String ARG0 = "arg"; public static final String MEM0 = "$mem"; + /** The tracked fields are now complex enough to deserve a array-of-structs layout + */ + public static class Var { + public final int _idx; // index in containing scope + public final String _name; // Declared name + private Type _type; // Declared type + public final boolean _final; // Final field + public Var(int idx, String name, Type type, boolean xfinal) { + _idx = idx; + _name = name; + _type = type; + _final = xfinal; + } + public Type type() { + if( !_type.isFRef() ) return _type; + // Update self to no longer use the forward ref type + Type def = Parser.TYPES.get(((TypeMemPtr)_type)._obj._name); + return (_type=_type.meet(def)); + } + public Type lazyGLB() { + Type t = type(); + return t instanceof TypeMemPtr ? t : t.glb(); + } + @Override public String toString() { + return _type.toString()+(_final ? " ": " !")+_name; + } + } + + public Node in(Var v) { return in(v._idx); } + + @Override public Type compute() { return Type.BOTTOM; } + @Override public Node idealize() { return null; } + // All active/live variables in all nested scopes, all run together public final Ary<Var> _vars; - // Since of each nested lexical scope + // Size of each nested lexical scope public final Ary<Integer> _lexSize; // True if parsing inside of a constructor @@ -35,6 +67,7 @@ public class ScopeNode extends ScopeMinNode { // A new ScopeNode public ScopeNode() { + _type = Type.BOTTOM; _vars = new Ary<>(Var .class); _lexSize= new Ary<>(Integer.class); _inCons = new Ary<>(Boolean.class); @@ -70,7 +103,8 @@ public class ScopeNode extends ScopeMinNode { public Node ctrl() { return in(0); } - public ScopeMinNode mem() { return (ScopeMinNode)in(1); } + public Node mem() { return in(lookup(MEM0)); } + public void mem(Node n) { update(MEM0,n); } /** * The ctrl of a ScopeNode is always bound to the currently active @@ -124,12 +158,6 @@ public class ScopeNode extends ScopeMinNode { return true; } - // Read from memory - public Node mem( int alias ) { return mem()._mem(alias,null); } - // Write to memory - public void mem( int alias, Node st ) { mem()._mem(alias,st); } - - /** * Lookup a name in all scopes starting from most deeply nested. * @@ -165,7 +193,7 @@ public class ScopeNode extends ScopeMinNode { // 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(v._idx,new PhiNode(v._name, v.lazyGLB(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).peephole()); + : loop.setDef(v._idx,PhiNode.make(v._name, v.lazyGLB(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).peephole()); setDef(v._idx,old); } if( st!=null ) setDef(v._idx,st); // Set new value @@ -203,20 +231,8 @@ public class ScopeNode extends ScopeMinNode { n.keep(); dup.addDef(ctrl()); // Control input is just copied - // Memory input is a shallow copy - ScopeMinNode memdup = new ScopeMinNode(), mem = mem(); - memdup.addDef(null); - memdup.addDef(loop ? this : mem.in(1)); - for( int i=2; i<mem.nIns(); i++ ) - // For lazy phis on loops we use a sentinel - // that will trigger phi creation on update - memdup.addDef(loop ? this : mem.in(i)); - dup.addDef(memdup); - - // Copy of other inputs - for( int i=2; i<nIns(); i++ ) - // For lazy phis on loops we use a sentinel - // that will trigger phi creation on update + // Memory is one binding, with the same lazy loop Phi as scalar names. + for( int i=1; i<nIns(); i++ ) dup.addDef(loop ? this : in(i)); return dup; } @@ -231,22 +247,21 @@ public class ScopeNode extends ScopeMinNode { */ public RegionNode mergeScopes(ScopeNode that) { RegionNode r = (RegionNode) ctrl(new RegionNode(null,ctrl(), that.ctrl()).keep()); - mem()._merge(that.mem(),r); - this ._merge(that ,r); + _merge(that,r); that.kill(); // Kill merged scope IterPeeps.add(r); return r.unkeep(); } private void _merge(ScopeNode that, RegionNode r) { - for( int i = 2; i < nIns(); i++) + 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 Var v = _vars.at(i); Node lhs = this.in(this.update(v,null)); Node rhs = that.in(that.update(v,null)); - setDef(i, new PhiNode(v._name, v.type(), r, lhs, rhs).peephole()); + setDef(i, PhiNode.make(v._name, v.type(), r, lhs, rhs).peephole()); } } @@ -257,21 +272,15 @@ public class ScopeNode extends ScopeMinNode { assert ctrl instanceof LoopNode loop && loop.inProgress(); ctrl.setDef(2,back.ctrl()); - mem()._endLoopMem( this, back.mem(), exit.mem() ); - this ._endLoop ( this, back , exit ); + _endLoop(this,back,exit); back.kill(); // Loop backedge is dead // Now one-time do a useless-phi removal - mem()._useless(); - this ._useless(); - - // The exit mem's lazy default value had been the loop top, - // now it goes back to predating the loop. - exit.mem().setDef(1,mem().in(1)); + _useless(); } // Fill in the backedge of any inserted Phis void _endLoop( ScopeNode scope, Node back, Node exit ) { - for( int i=2; i<nIns(); i++ ) { + for( int i=1; i<nIns(); i++ ) { if( back.in(i) != scope ) { PhiNode phi = (PhiNode)in(i); assert phi.region()==scope.ctrl() && phi.in(2)==null; @@ -283,6 +292,23 @@ public class ScopeNode extends ScopeMinNode { } + // Now one-time do a useless-phi removal + void _useless( ) { + 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. public void addGuards( Node ctrl, Node pred, boolean invert ) { diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/StartNode.java index ab6e4931..0dd10c22 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -1,19 +1,11 @@ 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.HashSet; import static com.seaofnodes.simple.Utils.TODO; -/** - * 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. - */ +/** Start supplies control, whole memory, and the argument, in that order. */ public class StartNode extends LoopNode implements MultiNode { final Type _arg; @@ -32,7 +24,7 @@ public class StartNode extends LoopNode implements MultiNode { @Override public CFGNode cfg0() { return this; } @Override public TypeTuple compute() { - return TypeTuple.make(Type.CONTROL,TypeMem.TOP,_arg); + return TypeTuple.make(Type.CONTROL,TypeMem.BOT,_arg); } @Override public Node idealize() { return null; } diff --git a/chapter17/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter17/src/main/java/com/seaofnodes/simple/node/StoreNode.java index a5618960..4ec106ac 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter17/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; @@ -26,6 +27,10 @@ public class StoreNode extends MemOpNode { } // Debugger label + @Override boolean canDrop(MemOpNode other, Node dep) { + return super.canDrop(other,dep) && _init==((StoreNode)other)._init; + } + @Override public String label() { return "st_"+mlabel(); } @Override public boolean isMem() { return true; } @@ -39,27 +44,25 @@ public class StoreNode extends MemOpNode { @Override public Type compute() { Type val = val()._type; - TypeMem mem = (TypeMem)mem()._type; // Invariant - Type t = Type.BOTTOM; // No idea on field contents - // Same alias, lift val to the declared type and then meet into other fields - if( mem._alias == _alias ) { - // Update declared forward ref to the actual - if( _declaredType.isFRef() && val instanceof TypeMemPtr tmp && !tmp.isFRef() ) - _declaredType = tmp; - val = val.join(_declaredType); - t = val.meet(mem._t); - } + if( _declaredType.isFRef() && val instanceof TypeMemPtr tmp && !tmp.isFRef() ) + _declaredType = tmp; + Type t = val.join(_declaredType).meet(MemMergeNode.contents(mem(),_alias,this)); return TypeMem.make(_alias,t); } @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 - off()==st.off() && // And same offset (could be "same alias" but this handles arrays to same index) + ptr()==st.ptr() && _alias==st._alias && // Must check same object + off()==st.off() && // And same offset 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/chapter17/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter17/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index a88987d5..55961293 100644 --- a/chapter17/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/chapter17/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -54,7 +54,7 @@ public class SimpleGraphAdapter extends GraphAdapter<Node> { return n instanceof CFGNode ? 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 || n instanceof ReturnNode || n instanceof NewNode) ) return Role.MEM; Node def = n.in(i); if( def != null && isMem(def) ) return Role.MEM; return Role.DATA; diff --git a/chapter17/src/test/java/com/seaofnodes/simple/Chapter04Test.java b/chapter17/src/test/java/com/seaofnodes/simple/Chapter04Test.java index dfd3569f..36727a8f 100644 --- a/chapter17/src/test/java/com/seaofnodes/simple/Chapter04Test.java +++ b/chapter17/src/test/java/com/seaofnodes/simple/Chapter04Test.java @@ -84,7 +84,10 @@ public class Chapter04Test { StopNode stop = parser.parse(); ReturnNode ret = stop.ret(); assertTrue(ret.in(0) instanceof CProjNode); - assertTrue(ret.in(1) instanceof ProjNode); + assertTrue(ret.in(1) instanceof ProjNode); + assertEquals(1, ((ProjNode)ret.in(1))._idx); + assertTrue(ret.in(2) instanceof ProjNode); + assertEquals(2, ((ProjNode)ret.in(2))._idx); } @Test diff --git a/chapter17/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter17/src/test/java/com/seaofnodes/simple/Chapter10Test.java index e3d8b177..86a9937f 100644 --- a/chapter17/src/test/java/com/seaofnodes/simple/Chapter10Test.java +++ b/chapter17/src/test/java/com/seaofnodes/simple/Chapter10Test.java @@ -1,7 +1,8 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.evaluator.Evaluator; -import com.seaofnodes.simple.node.StopNode; +import com.seaofnodes.simple.node.*; +import java.util.BitSet; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -452,4 +453,120 @@ 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)); + } + + 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()); + } + } + + @Test public void testKeepLoadsAtMerge() { + StopNode stop = new Parser(""" + struct S { int x; }; + S !a = new S; S !b = new S; + a.x = arg; b.x = arg+1; + S !p = a; S !q = b; + if (arg<0) { p=b; q=a; } + int v; + if (arg>1) v=p.x; else v=q.x; + return v; + """).parse().iterate(); + // The one-step safety check stops at the memory aggregate. + assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet())); + assertEquals(-1L,Evaluator.evaluate(stop,-1)); + assertEquals(1L,Evaluator.evaluate(stop,0)); + assertEquals(3L,Evaluator.evaluate(stop,3)); + } + + @Test public void testDropStores() { + StopNode stop = new Parser(""" + struct S { int x; }; + S !s = new S; + if (arg) s.x=arg+1; else s.x=arg+2; + return s; + """).parse().iterate(); + assertEquals(1,countMemoryNodes(stop,StoreNode.class,new BitSet())); + assertEquals(2L,((Evaluator.Obj)Evaluator.evaluate(stop,0)).fields()[0]); + assertEquals(4L,((Evaluator.Obj)Evaluator.evaluate(stop,3)).fields()[0]); + } + + @Test public void testKeepReadsBeforeWrites() { + StopNode stop = new Parser(""" + struct S { int x; int y; }; + S !a = new S; S !b = new S; + a.x=arg; b.x=arg+1; + S !p=a; S !q=b; + if (arg<0) { p=b; q=a; } + int v; + if (arg>1) { v=p.x; p.x=41; p.y=5; } + else { v=q.x; q.x=42; } + return v; + """).parse().iterate(); + assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet())); + assertEquals(-1L,Evaluator.evaluate(stop,-1)); + assertEquals(1L,Evaluator.evaluate(stop,0)); + assertEquals(3L,Evaluator.evaluate(stop,3)); + } + + private static int countMemoryNodes(Node n, Class<?> kind, BitSet seen) { + if (n==null || seen.get(n._nid)) return 0; + seen.set(n._nid); + int cnt=kind.isInstance(n) ? 1 : 0; + for (Node def : n._inputs) cnt+=countMemoryNodes(def,kind,seen); + return cnt; + } } diff --git a/chapter17/src/test/java/com/seaofnodes/simple/Chapter10bTest.java b/chapter17/src/test/java/com/seaofnodes/simple/Chapter10bTest.java new file mode 100644 index 00000000..fd6fdf36 --- /dev/null +++ b/chapter17/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/chapter17/src/test/java/com/seaofnodes/simple/Chapter13Test.java b/chapter17/src/test/java/com/seaofnodes/simple/Chapter13Test.java index 862b75e3..a1fdeb3a 100644 --- a/chapter17/src/test/java/com/seaofnodes/simple/Chapter13Test.java +++ b/chapter17/src/test/java/com/seaofnodes/simple/Chapter13Test.java @@ -183,7 +183,7 @@ return new S1.s=new S2; struct I {int i;}; struct P { I? pi; }; P !p1 = new P; -P p2 = new P; +P !p2 = new P; p2.pi = new I; p2.pi.i = 2; if (arg) p1 = new P; diff --git a/chapter17/src/test/java/com/seaofnodes/simple/Chapter14Test.java b/chapter17/src/test/java/com/seaofnodes/simple/Chapter14Test.java index 23c82358..6846e7af 100644 --- a/chapter17/src/test/java/com/seaofnodes/simple/Chapter14Test.java +++ b/chapter17/src/test/java/com/seaofnodes/simple/Chapter14Test.java @@ -175,7 +175,7 @@ while(v1) break; return v1; """); StopNode stop = parser.parse().iterate(); - assertEquals("return (Phi(Loop,((1<<arg)&1),0)&Phi(Loop,Shl,4294967295));", stop.toString()); + assertEquals("return Phi(Loop,(((1<<arg)&1)&Shl),0);", stop.toString()); assertEquals(1L, Evaluator.evaluate(stop, 0)); } diff --git a/chapter17/src/test/java/com/seaofnodes/simple/Chapter15Test.java b/chapter17/src/test/java/com/seaofnodes/simple/Chapter15Test.java index 4e2682dc..e66a009b 100644 --- a/chapter17/src/test/java/com/seaofnodes/simple/Chapter15Test.java +++ b/chapter17/src/test/java/com/seaofnodes/simple/Chapter15Test.java @@ -130,7 +130,7 @@ else { return rez; """); StopNode stop = parser.parse().iterate(); - assertEquals("return Phi(Region,1.2,Phi(Region,2.3,3.14));", stop.toString()); + assertEquals("return Phi(Region,1.2,Phi(Region,2.3,.y));", stop.toString()); assertEquals(3.14, Evaluator.evaluate(stop, 0)); assertEquals(1.2 , Evaluator.evaluate(stop, 1)); } @@ -156,7 +156,7 @@ if( iss[arg] ) return rez; """); try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Might be null accessing 'y'",e.getMessage()); } + catch( Exception e ) { assertEquals("Might be null accessing '[]'",e.getMessage()); } } @Test diff --git a/chapter17/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/chapter17/src/test/java/com/seaofnodes/simple/Chapter16Test.java index 36510737..d9edf0e6 100644 --- a/chapter17/src/test/java/com/seaofnodes/simple/Chapter16Test.java +++ b/chapter17/src/test/java/com/seaofnodes/simple/Chapter16Test.java @@ -254,4 +254,27 @@ return new Square; assertEquals("Obj<Square>{side=3.0,diag=1.7320508075688772}", Evaluator.evaluate(stop, 3).toString()); assertEquals("Obj<Square>{side=4.0,diag=2.0}", Evaluator.evaluate(stop, 4).toString()); } + static final String CONSTRUCTOR_MEMORY = """ + struct S { int x; int y; }; + struct T { int z=arg+40; }; + T !t = new T; + S !a = new S { x=11; y=7; }; + S !b = new S { x=22; y=9; }; + S !p=a; + if (arg) p=b; + int before=p.x; + S !c = new S { + x=p.x+1; + { int i=0; while (i<2) { p.y=p.y+1; i=i+1; } } + y=p.y; + }; + p.x=33; + return before*10000+c.x*100+c.y+t.z; + """; + + @Test public void testConstructorMemory() { + StopNode stop = new Parser(CONSTRUCTOR_MEMORY).parse().iterate(); + assertEquals(111249L,Evaluator.evaluate(stop,0)); + assertEquals(222352L,Evaluator.evaluate(stop,1)); + } } diff --git a/chapter17/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/chapter17/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java index 72f84fa2..30af9895 100644 --- a/chapter17/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java +++ b/chapter17/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java @@ -187,7 +187,7 @@ public class Evaluator { Object[] body=null; int num; if( type.isAry() ) { - long sz = (Long)val(alloc.in(1)); + long sz = (Long)val(alloc.size()); long n = offToIdx(sz, type); if( n < 0 ) throw new NegativeArraySizeException(""+n); @@ -201,17 +201,13 @@ public class Evaluator { assert elem instanceof TypeMemPtr; } // Length value - body[0] = vall(alloc.in(2+2)); + body[0] = vall(alloc.in(3)); } else { body = new Object[num = type._fields.length]; for (int i=0; i<num; i++) - body[i] = val(alloc.in(2+i+num)); + body[i] = val(alloc.in(3+i)); } - Object[] mems = new Object[type._fields.length+2]; - // mems[0] is control - mems[1] = new Obj(type,body); // the ref - // mems[2+...] are memory aliases - return mems; + return new Object[]{new Obj(type,body),MEMORY}; } private Object load(LoadNode load) { @@ -304,10 +300,10 @@ public class Evaluator { case ToFloatNode cast -> (double)vall(cast.in(1)); case LoadNode load -> load(load); case StoreNode store -> store(store); + case MemMergeNode merge -> MEMORY; case NewNode alloc -> alloc(alloc); case CProjNode cproj -> ((Object[])val(cproj.ctrl()))[cproj._idx]; case ProjNode proj -> ((Object[])val( proj.in(0) ))[ proj._idx]; - case ScopeMinNode mem -> null; case ReadOnlyNode ro -> val(ro.in(1)); default -> throw new AssertionError("Unexpected node " + node); }; @@ -331,7 +327,7 @@ public class Evaluator { case null: return Status.FALLTHROUGH; case ReturnNode ret: - return val(ret.in(1)); + return val(ret.expr()); case IfNode ifn: block = block.next()[isTrue(val(ifn.in(1))) ? 0 : 1]; if (block == null) return Status.FALLTHROUGH; diff --git a/chapter17/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java b/chapter17/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java index 06169e2c..afcc700f 100644 --- a/chapter17/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java +++ b/chapter17/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java @@ -31,6 +31,8 @@ import java.util.*; */ public class Scheduler { + private final HashMap<Node,ArrayList<NodeData>> readers = new HashMap<>(); + /** * A basic block with a schedule of the containing nodes. * @@ -252,7 +254,7 @@ public class Scheduler { // but they did not refine the placement // so do that now. var mem = l.in(1); - for(var out : mem._outputs) { + for(var out : l.antiDeps()) { if (out instanceof PhiNode p) { var r = p.in(0); for (int i = 1; i < p.nIns(); i++) { @@ -270,13 +272,9 @@ public class Scheduler { 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) od(out).ifPresent(this::decUsers); - } - } + var loads = readers.get(data.node); + if (loads != null) + for (var load : loads) decUsers(load); } // Now all nodes should be placed and have a block assigned @@ -442,7 +440,6 @@ public class Scheduler { 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()) { @@ -467,16 +464,16 @@ public class Scheduler { } else { for (var in : node._inputs) if (in != null && !(in instanceof CFGNode)) 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) od(out).ifPresent(d->d.users++); - } } + // Writes, including New behind an aggregate, schedule before their + // anti-dependent reads in this reverse scheduling pass. + for (var load : data.values()) + if (load.node instanceof LoadNode l) + for (var use : l.antiDeps()) + if ((use instanceof StoreNode || use instanceof NewNode) && data.containsKey(use)) { + load.users++; + readers.computeIfAbsent(use,k->new ArrayList<>()).add(load); + } } /** diff --git a/chapter18/README.md b/chapter18/README.md index 1727c938..42f76227 100644 --- a/chapter18/README.md +++ b/chapter18/README.md @@ -7,6 +7,7 @@ 3. [Functions](#functions) 4. [CodeGen - The Compile Driver](#codegen---the-compile-driver) 5. [Graph Visualizer](#graph-visualizer) +6. [Memory across functions](#memory-across-functions) You can also read [this chapter](https://github.com/SeaOfNodes/Simple/tree/linear-chapter18) 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-chapter17...linear-chapter18) it to the previous chapter. @@ -236,3 +237,32 @@ Nodes are color coded according to type, same as the lattice diagrams. Nodes are shaped according to node function as well. At the bottom are `ScopeNode`s, which only exist for the Parser but are actual Nodes and have `use->def` edges into the IR. + +## Memory across functions + +Each function has one `$mem` parameter and the parser keeps one memory binding. +Branches, loops, and multiple returns merge whole memory with BulkMemPhi. Field +uses discover precise MemPhis, while MemMerge packages a default memory state +and its alias overrides. MemMerge is an ordinary graph node; it no longer doubles +as a mutable parser alias table. + +| Boundary | Memory convention | +|---|---| +| Function entry | Parm 1 carries whole memory; heap contents are unknown | +| Call inputs | `{ctrl, $mem, arguments..., function pointer}` | +| CallEnd outputs | `{ctrl, $mem, value}` | +| Return inputs | `{ctrl, $mem, value, rpc}` | +| New inputs | `{ctrl, $mem, size, field values...}` | +| New outputs | `{ptr, $mem}`, covering only the allocated struct's aliases | + +A function may receive an existing object, so its entry memory cannot be treated +as an empty heap. Calls conservatively affect every alias. The returned memory +becomes the caller's new whole-memory state; there is no per-function alias +summary. Trivial inlining removes this boundary and lets the ordinary memory +rewrites see through the function body. + +Scheduling follows the relevant alias through MemMerge when looking for writes +or calls that must follow a Load. Calls end their basic block, so moving the Load +before that terminator supplies the required order without appending scheduling +edges to the argument list. Phi factoring keeps the simple one-step Load guard; +a direct Call user blocks that rewrite. diff --git a/chapter18/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java b/chapter18/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java index d96bd178..8221ede1 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java @@ -161,14 +161,8 @@ public abstract class GlobalCodeMotion { // Loads need their memory inputs' uses also done if( n instanceof LoadNode ld ) - for( Node memuse : ld.mem()._outputs ) - if( late[memuse._nid]==null && - // New makes new memory, never crushes load memory - !(memuse instanceof NewNode) && - // Load-use directly defines memory - (memuse._type instanceof TypeMem || - // Load-use indirectly defines memory - (memuse._type instanceof TypeTuple tt && tt._types[ld._alias] instanceof TypeMem)) ) + for( Node memuse : ld.antiDeps() ) + if( late[memuse._nid]==null ) continue outer; // All uses done, schedule @@ -180,9 +174,7 @@ public abstract class GlobalCodeMotion { for( Node def : n._inputs ) { if( def==null ) continue; if( late[def._nid]==null ) work.push(def); - for( Node out : def._outputs ) - if( out instanceof LoadNode ld && late[ld._nid]==null ) - work.push(ld); + wakeLoads(def,late,work,new BitSet()); } if( n instanceof LoopNode loop ) for( Node phi : loop._outputs ) @@ -240,13 +232,24 @@ public abstract class GlobalCodeMotion { best instanceof IfNode; } + // An allocation may consume a load's memory through a partial aggregate. + private static void wakeLoads(Node def, CFGNode[] late, WorkList<Node> work, BitSet visit) { + if( visit.get(def._nid) ) return; + visit.set(def._nid); + for( Node out : def._outputs ) + if( out instanceof LoadNode ld && late[ld._nid]==null ) work.push(ld); + if( def instanceof MemMergeNode ) + for( int i=1; i<def.nIns(); i++ ) + if( def.in(i)!=null ) wakeLoads(def.in(i),late,work,visit); + } + private static CFGNode find_anti_dep(CFGNode lca, LoadNode load, CFGNode early, CFGNode[] late, int[] anti) { // We could skip final-field loads here. // Walk LCA->early, flagging Load's block location choices for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() ) anti[cfg._nid] = load._nid; // Walk load->mem uses, looking for Stores causing an anti-dep - for( Node mem : load.mem()._outputs ) { + for( Node mem : load.antiDeps() ) { switch( mem ) { case StoreNode st: assert late[st._nid]!=null; @@ -263,11 +266,10 @@ public abstract class GlobalCodeMotion { if( phi.in(i)==load.mem() ) lca = anti_dep(load,phi.region().cfg(i),load.mem().cfg0(),lca,null,anti); break; - case NewNode st: break; - case LoadNode ld: break; // Loads do not cause anti-deps on other loads - case ReturnNode ret: break; // Load must already be ahead of Return - case MemMergeNode ret: break; // Mem uses now on ScopeMin - case NeverNode never: break; + case NewNode st: + assert late[st._nid]!=null; + lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st,anti); + break; default: throw Utils.TODO(); } } @@ -276,14 +278,12 @@ public abstract class GlobalCodeMotion { // private static CFGNode anti_dep( LoadNode load, CFGNode stblk, CFGNode defblk, CFGNode lca, Node st, int[] anti ) { - // Preserve the full store range for the earlier evaluator scheduler. - // It places nodes independently of GCM and may hoist this store. for( ; stblk != defblk.idom(); stblk = stblk.idom() ) { // Store and Load overlap, need anti-dependence if( anti[stblk._nid]==load._nid ) { lca = stblk.domLCA(lca,null); // Raise Loads LCA - if( lca == stblk && st != null && st._inputs.find(load) == -1 ) // And if something moved, - st.addDef(load); // Add anti-dep as well + if( lca == stblk && st != null && !(st instanceof CallNode) && st._inputs.find(load) == -1 ) // And if something moved, + st.addDef(load); // Add anti-dep as well; Calls already end their block. return lca; // Cap this stores' anti-dep to here } } diff --git a/chapter18/src/main/java/com/seaofnodes/simple/Parser.java b/chapter18/src/main/java/com/seaofnodes/simple/Parser.java index 3e914bbd..1b13a735 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/Parser.java @@ -154,7 +154,7 @@ public class Parser { _scope.define(ScopeNode.ARG0, TypeInteger.BOT, false, null, _lexer); ctrl(XCTRL); - _scope.mem(new MemMergeNode(false)); + _scope.mem(con(TypeMem.BOT)); // Parse whole program, as-if function header "{ int arg -> body }" parseFunctionBody(TypeFunPtr.MAIN,loc(),"arg"); @@ -194,7 +194,7 @@ public class Parser { // Build a multi-exit return point for all function returns RegionNode r = new RegionNode((Node)null,null).init(); assert r.inProgress(); - PhiNode rmem = new PhiNode(ScopeNode.MEM0,TypeMem.BOT,r,null).init(); + PhiNode rmem = new BulkMemPhiNode(ScopeNode.MEM0,r,null).init(); PhiNode rrez = new PhiNode(ScopeNode.ARG0,Type.BOTTOM,r,null).init(); ReturnNode ret = new ReturnNode(r, rmem, rrez, rpc, fun).init(); fun.setRet(ret); @@ -206,11 +206,8 @@ public class Parser { // external calls. _scope.push(ScopeNode.Kind.Function); ctrl(fun); // Scope control from function - // Private mem alias tracking per function - MemMergeNode mem = new MemMergeNode(true); - mem.addDef(null); // Alias#0 - mem.addDef(new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,con(TypeMem.BOT)).peephole()); // All aliases - _scope.mem(mem); + // Unknown callers supply whole memory; field aliases are discovered lazily. + _scope.mem(new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,con(TypeMem.BOT)).peephole()); // All args, "as-if" called externally for( int i=0; i<ids.length; i++ ) { Type t = sig.arg(i); @@ -224,7 +221,7 @@ public class Parser { // Last expression is the return if( ctrl()._type==Type.CONTROL ) - fun.addReturn(ctrl(), _scope.mem().merge(), last); + fun.addReturn(ctrl(), _scope.mem(), last); // Pop off the inProgress node on the multi-exit Region merge assert r.inProgress(); @@ -584,9 +581,8 @@ public class Parser { */ private Node parseReturn() { var expr = require(parseAsgn(), ";"); - // Need default memory, since it can be lazy, need to force - // a non-lazy Phi - _fun.addReturn(ctrl(), _scope.mem().merge(), expr); + // Force the lazy whole-memory Phi before closing this exit. + _fun.addReturn(ctrl(), _scope.mem(), expr); ctrl(XCTRL); // Kill control return expr; } @@ -1200,21 +1196,25 @@ public class Parser { Field[] fs = obj._fields; if( fs==null ) throw error("Unknown struct type '" + obj._name + "'"); - int len = fs.length; - Node[] ns = new Node[2+len+len]; - ns[0] = ctrl(); // Control in slot 0 - // Total allocated length in bytes - ns[1] = size; - // Memory aliases for every field - for( int i = 0; i < len; i++ ) - ns[2 +i] = memAlias(fs[i]._alias); - // Initial values for every field - for( int i = 0; i < len; i++ ) - ns[2+len+i] = init.get(i+idx); - Node nnn = new NewNode(TypeMemPtr.make(obj), ns).peephole(); - for( int i = 0; i < len; i++ ) - memAlias(fs[i]._alias, new ProjNode(nnn,i+2,memName(fs[i]._alias)).peephole()); - return new ProjNode(nnn,1,obj._name).peephole(); + Node prior = mem().keep(); + MemMergeNode input = new MemMergeNode(null); + for( Field f : fs ) input.alias(f._alias,prior); + // Constructor values follow control, memory, and allocation size. + Node[] ns = new Node[3+fs.length]; + ns[0] = ctrl(); + ns[1] = input.peephole(); + ns[2] = size; + for( int i=0; i<fs.length; i++ ) ns[3+i] = init.get(idx+i); + Node nnn = new NewNode(TypeMemPtr.make(obj),ns).peephole().keep(); + Node ptr = new ProjNode(nnn,0,obj._name).peephole().keep(); + Node out = new ProjNode(nnn,1,"$mem").peephole().keep(); + MemMergeNode after = new MemMergeNode(prior); + for( Field f : fs ) after.alias(f._alias,out); + mem(after.peephole()); + out.unkeep(); + nnn.unkeep(); + prior.unkeep(); + return ptr.unkeep(); } private static final Ary<Node> ALTMP = new Ary<>(Node.class); @@ -1226,13 +1226,18 @@ public class Parser { return newStruct(ary,size,0,ALTMP); } - // 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.mem(alias ); } - private void memAlias(int alias, Node st) { _scope.mem(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.mem(); } + private void mem(Node n) { _scope.mem(n); } + + private void store(String name, int alias, Type glb, Node ptr, Node off, Node val, boolean init, Node ctrl) { + Node prior = mem().keep(); + Node st = new StoreNode(loc(),name,alias,glb,prior,ptr,off,val,init); + st.setDef(0,ctrl); + st = st.peephole(); + mem(new MemMergeNode(prior,alias,st).peephole()); + prior.unkeep(); + } /** * Parse postfix expression; this can be a field expression, an array @@ -1274,6 +1279,7 @@ public class Parser { if( tf instanceof TypeMemPtr ftmp && ftmp.isFRef() ) tf = ftmp.makeFrom(((TypeMemPtr)(TYPES.get(ftmp._obj._name)))._obj); + expr.keep(); // Field offset; fixed for structs, computed for arrays Node off = (name.equals("[]") // If field is an array body // Array index math @@ -1286,16 +1292,11 @@ public class Parser { Node val = parseAsgn().keep(); Node lift = liftExpr( val, tf, f._final ); - Node st = new StoreNode(loc(), name, f._alias, tf, memAlias(f._alias), expr, off.unkeep(), lift, false); - // Arrays include control, as a proxy for a safety range check. - // Structs don't need this; they only need a NPE check which is - // done via the type system. - if( base.isAry() ) st.setDef(0,ctrl()); - memAlias(f._alias, st.peephole()); + store(name,f._alias,tf,expr.unkeep(),off.unkeep(),lift,false,base.isAry() ? ctrl() : null); return val.unkeep(); // "obj.a = expr" returns the expression while updating memory } - Node load = new LoadNode(loc(),name, f._alias, tf, memAlias(f._alias), expr.keep(), off); + Node load = new LoadNode(loc(),name, f._alias, tf, mem(), expr, off); // Arrays include control, as a proxy for a safety range check // Structs don't need this; they only need a NPE check which is // done via the type system. @@ -1306,12 +1307,7 @@ public class Parser { char ch = _lexer.matchOperAssign(); if( ch!=0 ) { Node op = opAssign(ch,load,f._final,tf,name); - Node st = new StoreNode(loc(), name, f._alias, tf, memAlias(f._alias), expr.unkeep(), off, op, false); - // Arrays include control, as a proxy for a safety range check. - // Structs don't need this; they only need a NPE check which is - // done via the type system. - if( base.isAry() ) st.setDef(0,ctrl()); - memAlias(f._alias, peep(st)); + store(name,f._alias,tf,expr.unkeep(),off,op,false,base.isAry() ? ctrl() : null); load = postfix(ch) ? load.unkeep() : op; // And use the original loaded value as the result } else expr.unkill(); @@ -1392,7 +1388,7 @@ public class Parser { } // Control & memory after parsing args args.set(0,ctrl().keep()); - args.set(1,_scope.mem().merge().keep()); + args.set(1,_scope.mem().keep()); args.push(expr); // Function pointer // Unkeep them all for( Node arg : args ) @@ -1404,11 +1400,8 @@ public class Parser { CallEndNode cend = (CallEndNode)new CallEndNode(call).peephole(); // Control from CallEnd ctrl(new CProjNode(cend,0,ScopeNode.CTRL).peephole()); - // Memory from CallEnd - MemMergeNode mem = new MemMergeNode(true); - mem.addDef(null); // Alias#0 - mem.addDef(new ProjNode(cend,1,ScopeNode.MEM0).peephole()); - _scope.mem(mem); + // A call may update any alias, so its memory result is the new whole state. + _scope.mem(new ProjNode(cend,1,ScopeNode.MEM0).peephole()); // Call result return new ProjNode(cend,2,null).peephole(); } diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java new file mode 100644 index 00000000..de5f8b4c --- /dev/null +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java @@ -0,0 +1,238 @@ +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 mmm.in(1)!=null && missingAlias(mmm)==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 ParmNode parm -> 0; + case ConstantNode con -> 0; + case MemMergeNode mmm -> missingAlias(mmm); + 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); + case BulkMemPhiNode bulk -> missingAlias(bulk); + case MemOpNode mem -> unsplit(mem._alias); + case MemPhiNode phi -> unsplit(phi._alias); + default -> { + assert use instanceof ReturnNode || use instanceof CallNode || use instanceof ParmNode + : "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; + } + + // Every explicit slot requests that alias, including a partial allocation + // input whose slot still points to this bulk Phi. + private int missingAlias(MemMergeNode mmm) { + for( int alias=2; alias<mmm.nIns(); alias++ ) + if( mmm.in(alias)!=null && + mmm.alias(alias)!=mmm.in(1) && + !_aliases.get(alias) ) + 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,Type.BOTTOM)); + 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/chapter18/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/LoadNode.java index 54acc12c..0ec7c826 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -2,8 +2,10 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.type.*; import java.util.BitSet; +import java.util.ArrayList; /** * Load represents extracting a value from inside a memory object, @@ -23,6 +25,25 @@ public class LoadNode extends MemOpNode { super(loc, name, alias, glb, mem, ptr, off); } + @Override boolean canDrop(MemOpNode other, Node dep) { + return super.canDrop(other,dep) && !clobbered(dep); + } + + // Check only immediate memory users. Writers clobber memory; Phis and + // aggregates might lead to a clobber, so stop rather than search further. + private boolean clobbered(Node dep) { + Node mem = mem(); + mem.addDepForwards(dep); + for( Node use : mem._outputs ) { + if( use==null ) continue; + use.addDepForwards(dep); + if( use instanceof StoreNode || use instanceof PhiNode || + use instanceof MemMergeNode || + use instanceof NewNode || use instanceof CallNode ) return true; + } + return false; + } + // Debugger label @Override public String label() { return "ld_"+mlabel(); } @Override @@ -30,31 +51,31 @@ public class LoadNode extends MemOpNode { @Override public Type compute() { - if( mem()._type instanceof TypeMem mem ) { - // Update declared forward ref to the actual - if( _declaredType.isFRef() && mem._t instanceof TypeMemPtr tmp && !tmp.isFRef() ) - _declaredType = tmp; - // No lifting if ptr might null-check - if( err()==null ) - return _declaredType.join(mem._t); - } - return _declaredType; + Type t = MemMergeNode.contents(mem(),_alias,this); + // Update declared forward ref to the actual. + if( _declaredType.isFRef() && t instanceof TypeMemPtr tmp && !tmp.isFRef() ) + _declaredType = tmp; + return err()==null ? _declaredType.join(t) : _declaredType; } @Override public Node idealize() { + if( mem() instanceof MemMergeNode merge ) { + setDef(1,IterPeeps.add(merge.alias(_alias))); + return this; + } Node ptr = ptr(); // Simple Load-after-Store on same address. if( mem() instanceof StoreNode st && - ptr == st.ptr() && off() == st.off() ) { // Must check same object + _alias==st._alias && ptr == st.ptr() && off() == st.off() ) { // Must check same object assert _name.equals(st._name); // Equiv class aliasing is perfect return st.val(); } // Simple Load-after-New on same address. if( mem() instanceof ProjNode p && p.in(0) instanceof NewNode nnn && - ptr == nnn.proj(1) ) // Must check same object + ptr == nnn.proj(0) ) // Must check same object return nnn.in(nnn.findAlias(_alias)); // Load from New init // Load-after-Store on same address, but bypassing provably unrelated @@ -65,12 +86,16 @@ public class LoadNode extends MemOpNode { Node mem = mem(); outer: while( true ) { + mem.addDep(this); switch( mem ) { + case MemMergeNode merge: + mem = merge.alias(_alias); + break; case StoreNode st: - if( ptr == st.ptr().addDep(this) && off() == st.off() ) + if( _alias==st._alias && ptr == st.ptr().addDep(this) && off() == st.off() ) return castRO(st.val()); // Proved equal // Can we prove unequal? Offsets do not overlap? - if( !off()._type.join(st.off()._type).isHigh() && // Offsets overlap + if( _alias==st._alias && !off()._type.join(st.off()._type).isHigh() && // Offsets overlap !neverAlias(ptr,st.ptr()) ) // And might alias break outer; // Cannot tell, stop trying // Pointers cannot overlap @@ -88,17 +113,16 @@ public class LoadNode extends MemOpNode { return castRO(nnn1.in(nnn1.findAlias(_alias))); // Load from New init if( !(ptr instanceof ProjNode pproj && pproj.in(0) instanceof NewNode nnn2) ) break outer; // Cannot tell, ptr not related to New - mem = nnn1.in(_alias);// Bypass unrelated New + mem = nnn1.mem();// Bypass unrelated New break; case StartNode start: break outer; case CallEndNode cend: break outer; // TODO: Bypass no-alias call default: throw Utils.TODO(); } break; - case MemMergeNode merge: mem = merge.alias(_alias); break; default: - throw Utils.TODO(); + break outer; } } @@ -109,7 +133,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 && // Offset can be hoisted off() instanceof ConstantNode && // Pointer can be hoisted @@ -130,7 +154,9 @@ public class LoadNode extends MemOpNode { private Node ld( int idx ) { Node mem = mem(), ptr = ptr(); - return new LoadNode(_loc,_name,_alias,_declaredType,mem.in(idx),ptr instanceof PhiNode && ptr.in(0)==mem.in(0) ? ptr.in(idx) : ptr, off()).peephole(); + LoadNode ld = new LoadNode(_loc,_name,_alias,_declaredType,mem.in(idx),ptr instanceof PhiNode && ptr.in(0)==mem.in(0) ? ptr.in(idx) : ptr,off()); + ld.setDef(0,in(0)); + return ld.peephole(); } private static boolean neverAlias( Node ptr1, Node ptr2 ) { @@ -163,9 +189,9 @@ public class LoadNode extends MemOpNode { private boolean profit(PhiNode phi, int idx) { Node px = phi.in(idx); if( px==null ) return false; - if( px._type instanceof TypeMem mem && mem._t.isHighOrConst() ) return true; - if( px instanceof StoreNode st1 && ptr()==st1.ptr() && off()==st1.off() ) return true; px.addDep(this); + if( px._type instanceof TypeMem mem && mem._t.isHighOrConst() ) return true; + if( px instanceof StoreNode st1 && _alias==st1._alias && ptr()==st1.ptr() && off()==st1.off() ) return true; return false; } @@ -175,4 +201,30 @@ public class LoadNode extends MemOpNode { return new ReadOnlyNode(rez).peephole(); return rez; } + + // Memory writers which must follow this read. A packaging node is not a + // write, but can hide New's input, so follow this alias through aggregates. + public ArrayList<Node> antiDeps() { + ArrayList<Node> deps = new ArrayList<>(); + antiDeps(mem(),deps,new BitSet()); + return deps; + } + + private void antiDeps(Node mem, ArrayList<Node> deps, BitSet visit) { + if( visit.get(mem._nid) ) return; + visit.set(mem._nid); + for( Node use : mem._outputs ) { + if( use instanceof MemMergeNode merge ) { + Node slice = _alias<merge.nIns() ? merge.in(_alias) : null; + if( (slice==mem) || (slice==null && merge.in(1)==mem) ) + antiDeps(merge,deps,visit); + } else if( use instanceof CallNode || + (use instanceof StoreNode st && st._alias==_alias) || + (use instanceof NewNode nnn && nnn.field(_alias)!=null) || + (use instanceof MemPhiNode phi && phi._alias==_alias) || + (use instanceof BulkMemPhiNode phi && !phi.isSplit(_alias)) ) { + if( !deps.contains(use) ) deps.add(use); + } + } + } } diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java index 033f1331..07d703f6 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java @@ -1,156 +1,88 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.IterPeeps; -import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.Var; -import com.seaofnodes.simple.type.*; -import java.util.*; -import static com.seaofnodes.simple.Utils.TODO; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeMem; +import java.util.BitSet; -/** - * Memory Merge - a merge of many aliases into a "fat memory". All aliases - * are here, but most will be lazy - take the default fat memory. +/** A default slice and explicit alias overrides. A null default means partial + * memory: absent aliases are not covered, rather than having unknown contents. */ public class MemMergeNode extends Node { - - /* - * In-Progress means this is being used by the Parser to track memory - * aliases. No optimizations are allowed. When no longer "in progress" - * normal peeps work. - */ - public final boolean _inProgress; - - public MemMergeNode( boolean inProgress) { _type = TypeMem.BOT; _inProgress = inProgress; } - - - // If being used by a Scope, this is "in progress" from the Parser. - // Otherwise, it's a memory merge - boolean inProgress() { return _inProgress; } - - @Override public String label() { return "ALLMEM"; } - @Override public boolean isMem() { return true; } - - @Override - StringBuilder _print1(StringBuilder sb, BitSet visited) { - sb.append(_inProgress ? "Merge[" : "MEM[ "); - for( int j=2; j<nIns(); j++ ) { - sb.append(j); - sb.append(":"); - Node n = in(j); - while( n instanceof ScopeNode loop ) { - sb.append("Lazy_"); - n = loop.mem().alias(j); - } - if( n==null ) sb.append("___ "); - else n._print0(sb, visited).append(" "); - } - sb.setLength(sb.length()-1); - return sb.append("]"); + public MemMergeNode(Node bulk) { super(null,bulk); _type = TypeMem.BOT; } + public MemMergeNode(Node bulk, int alias, Node precise) { + this(bulk); + alias(alias,precise); } - // Make a memory merge: no longer a Scope really, tracking memory state but - // not related to the parser in any way. - public Node merge() { - // Force default memory to not be lazy - MemMergeNode merge = new MemMergeNode(false); - for( Node n : _inputs ) - merge.addDef(n); - merge._mem(1,null); - return merge.peephole(); + public Node alias(int alias) { + Node n = alias < nIns() ? in(alias) : null; + assert n!=null || !(in(1) instanceof BulkMemPhiNode bulk) || !bulk.isSplit(alias); + n = n==null ? in(1) : n; + assert n!=null : "Alias not covered: "+alias; + return n; } - - @Override public Type compute() { return TypeMem.BOT; } - - @Override public Node idealize() { - if( inProgress() ) return null; - - // If not merging any memory (all memory is just the default) - if( nIns()==2 ) - return in(1); // Become default memory - - return null; + public void alias(int alias, Node mem) { + assert alias > 1; + while( nIns()<=alias ) addDef(null); + setDef(alias,mem); } - public Node in( Var v ) { return in(v._idx); } - - public Node alias( int alias ) { - return in(alias<nIns() && in(alias)!=null ? alias : 1); - } - - Node alias( int alias, Node st ) { - while( alias >= nIns() ) addDef(null); - return setDef(alias,st); - } - - // Read or update from memory. - // A shared implementation allows us to create lazy phis both during - // lookups and updates; the lazy phi creation is part of chapter 8. - Node _mem( int alias, Node st ) { - // Memory projections are made lazily; if one does not exist - // then it must be START.proj(1) - Node old = alias(alias); - if( old instanceof ScopeNode loop ) { - MemMergeNode loopmem = loop.mem(); - Node memdef = loopmem.alias(alias); - // Lazy phi! - old = memdef instanceof PhiNode phi && loop.ctrl()==phi.region() - // Loop already has a real Phi, use it - ? memdef - // 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. - : loopmem.alias(alias, new PhiNode(Parser.memName(alias), TypeMem.BOT,loop.ctrl(),loopmem._mem(alias,null),null).peephole() ); - alias(alias,old); + // Scalar contents of one alias. New's field inputs supply the initialized type; + // join it with the incoming contents for all previously allocated objects. + // Phis use their cached types, so this query does not recurse around loops. + static Type contents(Node mem, int alias, Node dep) { + mem.addDep(dep); + if( mem instanceof MemMergeNode merge ) + return contents(merge.alias(alias),alias,dep); + if( mem instanceof ProjNode proj && proj.in(0) instanceof NewNode nnn ) { + assert proj._idx==1 && nnn.field(alias)!=null; + nnn.addDep(dep); + Node init = nnn.in(nnn.findAlias(alias)).addDep(dep); + return contents(nnn.mem(),alias,dep).meet(init._type); } - // Memory projections are made lazily; expand as needed - return st==null ? old : alias(alias,st); // Not lazy, so this is the answer - } - - - void _merge( MemMergeNode that, RegionNode r) { - int len = Math.max(nIns(),that.nIns()); - for( int i = 2; i < len; i++) - if( alias(i) != that.alias(i) ) { // No need for redundant Phis - // If we are in lazy phi mode we need to a lookup - // by alias as it will trigger a phi creation - Node lhs = this._mem(i,null); - Node rhs = that._mem(i,null); - alias(i, new PhiNode(Parser.memName(i), lhs._type.glb().meet(rhs._type.glb()), r, lhs, rhs).peephole()); - } + // Function parameters and call results may contain arbitrary heap values. + // Their bulk memory types do not imply empty or zero-filled storage. + if( mem._type==Type.TOP || mem._type==TypeMem.TOP ) return Type.TOP; + if( mem._type instanceof TypeMem mt && mt._alias==alias ) return mt._t; + return Type.BOTTOM; } - // Fill in the backedge of any inserted Phis - void _endLoopMem( ScopeNode scope, MemMergeNode back, MemMergeNode exit ) { - Node exit_def = exit.alias(1); - for( int i=1; i<nIns(); i++ ) { - if( in(i) instanceof PhiNode phi && phi.region()==scope.ctrl() ) { - assert phi.in(2)==null; - phi.setDef(2,back.in(i)==scope ? phi : back.in(i)); // Fill backedge + @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(" "); } - if( exit_def == scope ) // Replace a lazy-phi on the exit path also - exit.alias(i,in(i)); - } + return sb.append("]"); } - // Now one-time do a useless-phi removal - void _useless( ) { + @Override public Node idealize() { + boolean progress=false, allDefault=true; for( int i=2; 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 - } + if( in(i)!=null && in(i)==in(1) ) { setDef(i,null); progress=true; } + if( in(i) instanceof MemMergeNode mem ) { + setDef(i,IterPeeps.add(mem.alias(i))); + progress=true; } + if( in(i)!=null ) allDefault=false; } - } - - @Override boolean eq( Node n ) { - return this==n || !_inProgress; + if( allDefault && in(1)!=null ) 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/chapter18/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/MemOpNode.java index f9df2a40..c09f2725 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/MemOpNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/MemOpNode.java @@ -38,6 +38,13 @@ public abstract class MemOpNode extends Node { public Node ptr() { return in(2); } public Node off() { return in(3); } + // Extra conditions for factoring matching memory operations through a Phi. + // The caller has already checked the opcode, input shape and control. + boolean canDrop(MemOpNode other, Node dep) { + ptr().addDep(dep); + return _alias==other._alias && _declaredType==other._declaredType && err()==null; + } + @Override boolean eq(Node n) { MemOpNode mem = (MemOpNode)n; // Invariant diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java new file mode 100644 index 00000000..6aae00f9 --- /dev/null +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java @@ -0,0 +1,42 @@ +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,Type.BOTTOM),inputs); + assert alias > 1; + _alias = alias; + } + + @Override public String label() { return "MemPhi_"+_alias; } + + @Override public Type compute() { + assert BulkMemPhiNode.checkMem(this,_alias); + if( !(region() instanceof RegionNode r) ) + return region()._type==Type.XCONTROL ? TypeMem.TOP : _type; + if( r.inProgress() ) return _declaredType; + Type t = Type.TOP; + for( int i=1; i<nIns(); i++ ) + if( r.in(i).addDep(this)._type!=Type.XCONTROL ) + t = t.meet(MemMergeNode.contents(in(i),_alias,this)); + return TypeMem.make(_alias,t); + } + + @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/chapter18/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/NewNode.java index 241a3609..9f413e7d 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/NewNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/NewNode.java @@ -1,35 +1,34 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.Utils; import java.util.BitSet; -/** - * Allocation! Allocate a chunk of memory, and pre-zero it. - * The inputs include control and size, and ALL aliases being set. - * The output is large tuple, one for every alias plus the created pointer. - * New is expected to be followed by projections for every alias. +/** Allocate and initialize an object. Inputs {ctrl, $mem, size, fields...}; + * results {ptr, $mem}. + * The memory input and result cover only the aliases in the allocated struct. */ public class NewNode extends Node implements MultiNode { public final TypeMemPtr _ptr; - public final int _len; public NewNode(TypeMemPtr ptr, Node... nodes) { super(nodes); assert !ptr.nullable(); _ptr = ptr; - _len = ptr._obj._fields.length; - // Control in slot 0 - assert nodes[0]._type==Type.CONTROL || nodes[0]._type == Type.XCONTROL; - // Malloc-length in slot 1 - assert nodes[1]._type instanceof TypeInteger || nodes[1]._type==Type.NIL; - for( int i=0; i<_len; i++ ) { - // Memory slices for all fields. - assert nodes[2+ i]._type.isa(TypeMem.BOT); - // Value slices for all fields. - assert nodes[2 + _len + i]._type != null; - } + assert nodes.length==3+ptr._obj._fields.length; + assert nodes[0]._type==Type.CONTROL || nodes[0]._type==Type.XCONTROL; + assert nodes[1]._type instanceof TypeMem; + assert nodes[2]._type instanceof TypeInteger || nodes[2]._type==Type.NIL; + for( int i=3; i<nodes.length; i++ ) assert nodes[i]._type!=null; + } + + public Node mem() { return in(1); } + public Node size() { return in(2); } + + public Field field(int alias) { + for( Field f : _ptr._obj._fields ) + if( f._alias==alias ) return f; + return null; } @Override public String label() { @@ -43,23 +42,13 @@ public class NewNode extends Node implements MultiNode { // Find matching alias input int findAlias(int alias) { - return 2+_ptr._obj.findAlias(alias)+_len; + return 3+_ptr._obj.findAlias(alias); } @Override public TypeTuple compute() { - Field[] fs = _ptr._obj._fields; - Type[] ts = new Type[fs.length+2]; - ts[0] = Type.CONTROL; - ts[1] = _ptr; - for( int i=0; i<fs.length; i++ ) { - Type mt = in(i+2)._type; - TypeMem mem = mt==Type.TOP ? TypeMem.TOP : (TypeMem)mt; - Type tfld = in(2+_len+i)._type.meet(mem._t); - ts[i+2] = TypeMem.make(fs[i]._alias,tfld); - } - return TypeTuple.make(ts); + return TypeTuple.make(_ptr,TypeMem.BOT); } @Override diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter18/src/main/java/com/seaofnodes/simple/node/Node.java index 0016c711..cdd218b5 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/Node.java @@ -215,6 +215,7 @@ public abstract class Node implements Cloneable { // Error is 'use' does not exist; ok for 'use' to be null. protected boolean delUse( Node use ) { _outputs.del(_outputs.find(use)); + moveDepsToWorklist(); // User-count and anti-dependence queries can now change. return _outputs.isEmpty(); } @@ -275,6 +276,7 @@ public abstract class Node implements Cloneable { n.unlock(); int idx = n._inputs.find(this); n._inputs.set(idx,nnn); + n.moveDepsToWorklist(); // Rewiring can change a dependent query without changing type. nnn.addUse(n); IterPeeps.addAll(n._outputs); } @@ -463,6 +465,14 @@ public abstract class Node implements Cloneable { * 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; @@ -470,8 +480,8 @@ public abstract class Node implements Cloneable { if( obs != null ) obs.dep(this, dep); if( _deps==null ) _deps = new Ary<>(Node.class); if( _deps .find(dep) != -1 ) return this; // Already on list - if( _inputs .find(dep) != -1 ) return this; // No need for deps on immediate neighbors - if( _outputs.find(dep) != -1 ) return this; + if( !forwards && _inputs .find(dep) != -1 ) return this; // No need for deps on immediate neighbors + if( !forwards && _outputs.find(dep) != -1 ) return this; _deps.add(dep); return this; } diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/PhiNode.java index 1f3062e8..3fdcd6e5 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -22,6 +22,10 @@ public class PhiNode extends Node { addDef(sample); } + 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_"+MemOpNode.mlabel(_label); } @Override @@ -71,33 +75,9 @@ public class PhiNode extends Node { if (live != null) return live; - // No bother if region is going to fold dead paths soon - for( int i=1; i<nIns(); i++ ) - if( r.in(i)._type == Type.XCONTROL ) - return null; - - // 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 && same_op() ) { - assert !(op instanceof CFGNode); - 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(); - Node down = op.copy(phi_lhs,phi_rhs); - // Test not running backwards, which can happen for e.g. And's - if( down.compute().isa(compute()) ) - return down; - down.kill(); - } + // Phi(op(a,b,...),op(c,d,...)) -> op(Phi(a,c),Phi(b,d),...). + Node down; + if( same_op() && (down=drop_same_op())!=null ) return down; // If merging Phi(N, cast(N)) - we are losing the cast JOIN effects, so just remove. if( nIns()==3 ) { @@ -129,12 +109,72 @@ public class PhiNode extends Node { } private boolean same_op() { - for( int i=2; i<nIns(); i++ ) - if( in(1).getClass() != in(i).getClass() ) - return false; + Node op = in(1); + if( op instanceof CFGNode || op instanceof ConstantNode || op instanceof PhiNode || + op instanceof ProjNode || op instanceof NewNode || op instanceof ScopeNode || op instanceof MemMergeNode ) return false; + // A bulk Phi must split its aliases before factoring precise Stores. + if( this instanceof BulkMemPhiNode ) return false; + // Bulk splitting currently identifies parallel slices by Region/alias. + // Factoring can introduce another slice at a different memory point; + // wait until that Region's bulk partitioning has finished. + if( op instanceof MemOpNode ) + for( Node use : region()._outputs ) + if( use instanceof BulkMemPhiNode ) { + use.addDepForwards(this); + return false; + } + Node busy = null; + for( int i=1; i<nIns(); i++ ) { + Node n = in(i); + if( region().in(i).addDep(this)._type==Type.XCONTROL || + op.getClass()!=n.getClass() || n.nIns()!=op.nIns() || n.in(0)!=null || !op.eq(n) ) return false; + if( n instanceof MemOpNode mem && !mem.canDrop((MemOpNode)op,this) ) return false; + // Moving a Store must remove the old effect, not duplicate it. + // Allow one shared arm: the other arms disappear into the single + // factored operation, usually reducing the total operation count. + n.addDepForwards(this); + if( n.nOuts()>1 ) { + for( Node use : n._outputs ) + if( use!=null && use!=this ) use.addDepForwards(this); + if( n instanceof StoreNode || busy!=null ) return false; + busy=n; + } + for( int j=1; j<n.nIns(); j++ ) + if( n.in(j) instanceof ScopeNode || ((n.in(j)==null) != (op.in(j)==null)) ) return false; + } return true; } + private Node drop_same_op() { + Node op = in(1), cp = op.copyEmpty(); + cp._type = null; + cp.addDef(null); + for( int j=1; j<op.nIns(); j++ ) { + Node x = op.in(j); + boolean different = false; + for( int i=2; i<nIns(); i++ ) + if( in(i).in(j)!=x ) different=true; + if( different ) { + Node[] ins = new Node[nIns()]; + ins[0] = region(); + Type t = Type.TOP; + for( int i=1; i<nIns(); i++ ) { + ins[i] = in(i).in(j); + t = t.meet(ins[i]._type); + } + PhiNode phi = j==1 && op instanceof MemOpNode mem + ? new MemPhiNode(_label,mem._alias,ins) + : PhiNode.make(_label,t.glb(),ins); + x = phi.peephole(); + } + cp.addDef(x); + } + // Factoring must not widen the result (e.g. correlated And operands). + if( cp.compute().isa(compute()) ) return cp; + cp.kill(); + return null; + } + /** * If only single unique input, return it */ diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/RegionNode.java index b3aa27be..157d9109 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/RegionNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/RegionNode.java @@ -71,7 +71,7 @@ public class RegionNode extends CFGNode { if( !hasPhi() && // No Phi users, just a control user in(1) instanceof CProjNode p1 && in(2) instanceof CProjNode p2 && - p1.in(0).addDep(this)==p2.in(0).addDep(this) && + p1.addDepForwards(this).in(0)==p2.addDepForwards(this).in(0) && p1.in(0) instanceof IfNode iff ) { // Replace with the iff.ctrl directly if( nIns()==3 ) return iff.ctrl(); diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/ReturnNode.java index e516859b..9948d973 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -6,15 +6,7 @@ import com.seaofnodes.simple.Utils; 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 all preceding memory effects and the return value alive: {ctrl, $mem, value, rpc}. */ public class ReturnNode extends CFGNode { public final FunNode _fun; @@ -47,7 +39,7 @@ public class ReturnNode extends CFGNode { public Type compute() { if( inProgress () ) return TypeTuple.RET; // In progress if( _fun.isDead() ) return TypeTuple.RET.dual(); // Dead another way - return TypeTuple.make(ctrl()._type,mem()._type,expr()._type); + return TypeTuple.make(ctrl()._type,TypeMem.BOT,expr()._type); } @Override public Node idealize() { diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/ScopeNode.java index e8b20c37..0b93cee6 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -6,9 +6,9 @@ import java.util.*; /** * The Scope node is purely a parser helper - it tracks names to nodes with a - * stack of variables. + * stack of variable records. */ -public class ScopeNode extends MemMergeNode { +public class ScopeNode extends Node { /** * The control is a name that binds to the currently active control @@ -18,6 +18,11 @@ public class ScopeNode extends MemMergeNode { public static final String ARG0 = "arg"; public static final String MEM0 = "$mem"; + public Node in(Var v) { return in(v._idx); } + + @Override public Type compute() { return Type.BOTTOM; } + @Override public Node idealize() { return null; } + // All active/live variables in all nested scopes, all run together public final Ary<Var> _vars; @@ -33,7 +38,7 @@ public class ScopeNode extends MemMergeNode { // A new ScopeNode public ScopeNode() { - super(true); + _type = Type.BOTTOM; _vars = new Ary<>(Var .class); _lexSize= new Ary<>(Integer.class); _kinds = new Ary<>(Kind .class); @@ -69,7 +74,7 @@ public class ScopeNode extends MemMergeNode { public Node ctrl() { return in(0); } - public MemMergeNode mem() { return (MemMergeNode)in(1); } + public Node mem() { return in(lookup(MEM0)); } /** * The ctrl of a ScopeNode is always bound to the currently active @@ -82,7 +87,7 @@ public class ScopeNode extends MemMergeNode { * @return Node that was bound */ public <N extends Node> N ctrl(N n) { return setDef(0,n); } - public Node mem(Node n) { return setDef(1,n); } + public Node mem(Node n) { update(MEM0,n); return n; } public void push(Kind kind) { assert _lexSize._len==_kinds._len; @@ -165,12 +170,6 @@ public class ScopeNode extends MemMergeNode { return true; } - // Read from memory - public Node mem( int alias ) { return mem()._mem(alias,null); } - // Write to memory - public void mem( int alias, Node st ) { mem()._mem(alias,st); } - - /** * Lookup a name in all scopes starting from most deeply nested. * @@ -206,7 +205,7 @@ public class ScopeNode extends MemMergeNode { // 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(v._idx,new PhiNode(v._name, v.lazyGLB(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).peephole()); + : loop.setDef(v._idx,PhiNode.make(v._name, v.lazyGLB(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).peephole()); setDef(v._idx,old); } assert !v._final || st==null; @@ -245,20 +244,8 @@ public class ScopeNode extends MemMergeNode { n.keep(); dup.addDef(ctrl()); // Control input is just copied - // Memory input is a shallow copy - MemMergeNode memdup = new MemMergeNode(true), mem = mem(); - memdup.addDef(null); - memdup.addDef(loop ? this : mem.in(1)); - for( int i=2; i<mem.nIns(); i++ ) - // For lazy phis on loops we use a sentinel - // that will trigger phi creation on update - memdup.addDef(loop ? this : mem.in(i)); - dup.addDef(memdup); - - // Copy of other inputs - for( int i=2; i<nIns(); i++ ) - // For lazy phis on loops we use a sentinel - // that will trigger phi creation on update + // Memory is one binding, with the same lazy loop Phi as scalar names. + for( int i=1; i<nIns(); i++ ) dup.addDef(loop && !_vars.at(i)._final ? this : in(i)); return dup; } @@ -273,22 +260,21 @@ public class ScopeNode extends MemMergeNode { */ public RegionNode mergeScopes(ScopeNode that, Parser.Lexer loc) { RegionNode r = ctrl(new RegionNode(loc,null,ctrl(), that.ctrl()).keep()); - mem()._merge(that.mem(),r); - this ._merge(that ,r); + _merge(that,r); that.kill(); // Kill merged scope IterPeeps.add(r); return r.unkeep(); } private void _merge(ScopeNode that, RegionNode r) { - for( int i = 2; i < nIns(); i++) + 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 Var v = _vars.at(i); Node lhs = this.in(this.update(v,null)); Node rhs = that.in(that.update(v,null)); - setDef(i, new PhiNode(v._name, v.type(), r, lhs, rhs).peephole()); + setDef(i, PhiNode.make(v._name, v.type(), r, lhs, rhs).peephole()); } } @@ -313,21 +299,15 @@ public class ScopeNode extends MemMergeNode { assert ctrl instanceof LoopNode loop && loop.inProgress(); ctrl.setDef(2,back.ctrl()); - mem()._endLoopMem( this, back.mem(), exit.mem() ); - this ._endLoop ( this, back , exit ); + _endLoop(this,back,exit); back.kill(); // Loop backedge is dead // Now one-time do a useless-phi removal - mem()._useless(); - this ._useless(); - - // The exit mem's lazy default value had been the loop top, - // now it goes back to predating the loop. - exit.mem().setDef(1,mem().in(1)); + _useless(); } // Fill in the backedge of any inserted Phis void _endLoop( ScopeNode scope, Node back, Node exit ) { - for( int i=2; i<nIns(); i++ ) { + for( int i=1; i<nIns(); i++ ) { if( _vars.at(i)._final ) continue; // Final vars did not get modified in the loop if( _vars.at(i).type().isHighOrConst() ) continue; // Cannot lift higher than a constant, so no Phi if( back.in(i) != scope ) { @@ -341,6 +321,23 @@ public class ScopeNode extends MemMergeNode { } + // Now one-time do a useless-phi removal + void _useless( ) { + 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. public void addGuards( Node ctrl, Node pred, boolean invert ) { diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/StartNode.java index 50cba348..e9ab8380 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -1,19 +1,11 @@ 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.HashSet; import static com.seaofnodes.simple.Utils.TODO; -/** - * 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. - */ +/** Start supplies control, whole memory, and the argument, in that order. */ public class StartNode extends LoopNode implements MultiNode { final Type _arg; @@ -44,7 +36,7 @@ public class StartNode extends LoopNode implements MultiNode { @Override public TypeTuple compute() { - return TypeTuple.make(Type.CONTROL,TypeMem.TOP,_arg); + return TypeTuple.make(Type.CONTROL,TypeMem.BOT,_arg); } @Override public Node idealize() { return null; } diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/StoreNode.java index 870b8447..92cd2389 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -3,6 +3,7 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.Utils; +import com.seaofnodes.simple.IterPeeps; import com.seaofnodes.simple.type.*; import java.util.BitSet; @@ -27,6 +28,10 @@ public class StoreNode extends MemOpNode { } // Debugger label + @Override boolean canDrop(MemOpNode other, Node dep) { + return super.canDrop(other,dep) && _init==((StoreNode)other)._init; + } + @Override public String label() { return "st_"+mlabel(); } @Override public boolean isMem() { return true; } @@ -40,28 +45,25 @@ public class StoreNode extends MemOpNode { @Override public Type compute() { Type val = val()._type; - TypeMem mem = (TypeMem)mem()._type; // Invariant - if( mem == TypeMem.TOP ) return TypeMem.TOP; - Type t = Type.BOTTOM; // No idea on field contents - // Same alias, lift val to the declared type and then meet into other fields - if( mem._alias == _alias ) { - // Update declared forward ref to the actual - if( _declaredType.isFRef() && val instanceof TypeMemPtr tmp && !tmp.isFRef() ) - _declaredType = tmp; - val = val.join(_declaredType); - t = val.meet(mem._t); - } + if( _declaredType.isFRef() && val instanceof TypeMemPtr tmp && !tmp.isFRef() ) + _declaredType = tmp; + Type t = val.join(_declaredType).meet(MemMergeNode.contents(mem(),_alias,this)); return TypeMem.make(_alias,t); } @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 - off()==st.off() && // And same offset (could be "same alias" but this handles arrays to same index) + ptr()==st.ptr() && _alias==st._alias && // Must check same object + off()==st.off() && // And same offset 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/chapter18/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter18/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index fc06aeaa..bd9fe000 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -67,7 +67,7 @@ public class SimpleGraphAdapter extends GraphAdapter<Node> { return n instanceof CFGNode ? 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( n instanceof MemMergeNode || i == 1 && (n instanceof MemOpNode || n instanceof ReturnNode || n instanceof CallNode) ) return Role.MEM; + if( n instanceof MemMergeNode || (i == 1 && (n instanceof MemOpNode || n instanceof ReturnNode || n instanceof CallNode || n instanceof NewNode)) ) return Role.MEM; Node def = n.in(i); if( def != null && isMem(def) ) return Role.MEM; return Role.DATA; diff --git a/chapter18/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter18/src/test/java/com/seaofnodes/simple/Chapter10Test.java index daeea36d..d0d33663 100644 --- a/chapter18/src/test/java/com/seaofnodes/simple/Chapter10Test.java +++ b/chapter18/src/test/java/com/seaofnodes/simple/Chapter10Test.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple; import org.junit.Test; +import com.seaofnodes.simple.node.*; +import java.util.BitSet; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -449,4 +451,121 @@ return 0; assertEquals("return 0;", code.print()); } + @Test public void testReadBeforeStores() { + var code = new CodeGen(""" + 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().opto(); + StopNode stop = code._stop; + assertEquals("1133",Eval2.eval(code,0)); + assertEquals("2244",Eval2.eval(code,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 code = new CodeGen(NESTED_MEMORY).parse().opto(); + StopNode stop = code._stop; + for (int arg=0; arg<7; arg++) { + assertEquals("S{x="+(5L+arg)+",y="+(7L+arg*(arg+1)-(arg>=2 ? 2 : 0))+ + ",z="+(11L+2*arg-(arg>=2 ? 2 : 0))+"}",Eval2.eval(code,arg)); + } + } + + @Test public void testMemoryAtEarlyReturns() { + var code = new CodeGen(""" + 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().opto(); + StopNode stop = code._stop; + for (int arg=0; arg<2; arg++) { + assertEquals(arg==0 ? "S{x=3,y=17,z=7}" : "S{x=11,y=5,z=13}",Eval2.eval(code,arg)); + } + } + + @Test public void testKeepLoadsAtMerge() { + var code = new CodeGen(""" + struct S { int x; }; + S !a = new S; S !b = new S; + a.x = arg; b.x = arg+1; + S !p = a; S !q = b; + if (arg<0) { p=b; q=a; } + int v; + if (arg>1) v=p.x; else v=q.x; + return v; + """).parse().opto(); + StopNode stop = code._stop; + // The one-step safety check stops at the memory aggregate. + assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet())); + assertEquals("-1",Eval2.eval(code,-1)); + assertEquals("1",Eval2.eval(code,0)); + assertEquals("3",Eval2.eval(code,3)); + } + + @Test public void testDropStores() { + var code = new CodeGen(""" + struct S { int x; }; + S !s = new S; + if (arg) s.x=arg+1; else s.x=arg+2; + return s; + """).parse().opto(); + StopNode stop = code._stop; + assertEquals(1,countMemoryNodes(stop,StoreNode.class,new BitSet())); + assertEquals("S{x=2}",Eval2.eval(code,0)); + assertEquals("S{x=4}",Eval2.eval(code,3)); + } + + @Test public void testKeepReadsBeforeWrites() { + var code = new CodeGen(""" + struct S { int x; int y; }; + S !a = new S; S !b = new S; + a.x=arg; b.x=arg+1; + S !p=a; S !q=b; + if (arg<0) { p=b; q=a; } + int v; + if (arg>1) { v=p.x; p.x=41; p.y=5; } + else { v=q.x; q.x=42; } + return v; + """).parse().opto(); + StopNode stop = code._stop; + assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet())); + assertEquals("-1",Eval2.eval(code,-1)); + assertEquals("1",Eval2.eval(code,0)); + assertEquals("3",Eval2.eval(code,3)); + } + + private static int countMemoryNodes(Node n, Class<?> kind, BitSet seen) { + if (n==null || seen.get(n._nid)) return 0; + seen.set(n._nid); + int cnt=kind.isInstance(n) ? 1 : 0; + for (Node def : n._inputs) cnt+=countMemoryNodes(def,kind,seen); + return cnt; + } } diff --git a/chapter18/src/test/java/com/seaofnodes/simple/Chapter10bTest.java b/chapter18/src/test/java/com/seaofnodes/simple/Chapter10bTest.java new file mode 100644 index 00000000..01bce801 --- /dev/null +++ b/chapter18/src/test/java/com/seaofnodes/simple/Chapter10bTest.java @@ -0,0 +1,65 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.node.*; +import org.junit.Test; +import static org.junit.Assert.*; + +public class Chapter10bTest { + @Test public void testIndependentField() { + var code = new CodeGen(""" + 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().opto(); + var stop = code._stop; + assertEquals("return 42;",stop.toString()); + assertEquals("42",Eval2.eval(code,5)); + assertNull(stop.walk(n -> n instanceof LoadNode ? n : null)); + } + + @Test public void testParallelMemoryPhis() { + var code = new CodeGen(""" + 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().opto(); + var stop = code._stop; + 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) && !(n instanceof ParmNode)); + return null; + }); + assertTrue(phis[0]>=3); + assertEquals("S{x=1,y=2,z=3}",Eval2.eval(code,4)); + } + + @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 code = new CodeGen(Chapter10Test.NESTED_MEMORY).parse(); + 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); + code.opto(); + assertEquals("S{x=9,y=25,z=17}",Eval2.eval(code,4)); + } + } +} diff --git a/chapter18/src/test/java/com/seaofnodes/simple/Chapter15Test.java b/chapter18/src/test/java/com/seaofnodes/simple/Chapter15Test.java index 86f5007d..1c6e5ae7 100644 --- a/chapter18/src/test/java/com/seaofnodes/simple/Chapter15Test.java +++ b/chapter18/src/test/java/com/seaofnodes/simple/Chapter15Test.java @@ -152,7 +152,7 @@ if( iss[arg] ) return rez; """); try { code.parse().opto().typeCheck(); fail(); } - catch( Exception e ) { assertEquals("Might be null accessing 'y'",e.getMessage()); } + catch( Exception e ) { assertEquals("Might be null accessing '[]'",e.getMessage()); } } @Test diff --git a/chapter18/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/chapter18/src/test/java/com/seaofnodes/simple/Chapter16Test.java index 83d34b2e..3dd37f33 100644 --- a/chapter18/src/test/java/com/seaofnodes/simple/Chapter16Test.java +++ b/chapter18/src/test/java/com/seaofnodes/simple/Chapter16Test.java @@ -254,4 +254,27 @@ return new Square; assertEquals("Square{side=3.0,diag=1.7320508075688772}", Eval2.eval(code, 3)); assertEquals("Square{side=4.0,diag=2.0}", Eval2.eval(code, 4)); } + static final String CONSTRUCTOR_MEMORY = """ + struct S { int x; int y; }; + struct T { int z=arg+40; }; + T !t = new T; + S !a = new S { x=11; y=7; }; + S !b = new S { x=22; y=9; }; + S !p=a; + if (arg) p=b; + int before=p.x; + S !c = new S { + x=p.x+1; + { int i=0; while (i<2) { p.y=p.y+1; i=i+1; } } + y=p.y; + }; + p.x=33; + return before*10000+c.x*100+c.y+t.z; + """; + + @Test public void testConstructorMemory() { + var code = new CodeGen(CONSTRUCTOR_MEMORY).parse().opto(); + assertEquals("111249",Eval2.eval(code,0)); + assertEquals("222352",Eval2.eval(code,1)); + } } diff --git a/chapter18/src/test/java/com/seaofnodes/simple/Chapter18Test.java b/chapter18/src/test/java/com/seaofnodes/simple/Chapter18Test.java index 9582ecce..d13514b5 100644 --- a/chapter18/src/test/java/com/seaofnodes/simple/Chapter18Test.java +++ b/chapter18/src/test/java/com/seaofnodes/simple/Chapter18Test.java @@ -421,4 +421,70 @@ return p.coffee_count; assertEquals("1", Eval2.eval(code, 2)); } + static final String CALL_MEMORY = """ + struct S { int x; int y; }; + val bump = { S s, int d -> int old=s.x; s.x=old+d; return old; }; + S !a = new S { x=10; y=7; }; + S !b = new S { x=20; y=9; }; + S !p=a; if (arg) p=b; + int before=p.x; + int old=bump(p,3); + int after=p.x; + bump(a,5); + return before*1000000+old*10000+after*100+a.x+a.y; + """; + + @Test public void testCallMemory() { + var code = new CodeGen(CALL_MEMORY).parse().opto(); + int[] calls={0}; + code._stop.walk(n -> { if(n instanceof CallNode) calls[0]++; return null; }); + assertEquals(2,calls[0]); // Keep real calls so this checks scheduling across them. + assertEquals("10101325",Eval2.eval(code,0)); + assertEquals("20202322",Eval2.eval(code,1)); + code._stop.walk(n -> { + if(n instanceof CallNode call) { + assertEquals(5,call.nIns()); // No scheduling edges may become extra arguments. + assertTrue(call.fptr()._type instanceof TypeFunPtr); + } + return null; + }); + } + + static final String RECURSIVE_MEMORY = """ + struct S { int x; }; + val rec = { S s, int n -> + if (n==0) return s.x; + int before=s.x; + s.x=before+1; + return before+rec(s,n-1); + }; + S !s=new S { x=10; }; + int value=rec(s,arg); + return value*100+s.x; + """; + + @Test public void testRecursiveMemory() { + var code = new CodeGen(RECURSIVE_MEMORY).parse().opto(); + for(int n=0; n<7; n++) + assertEquals(Long.toString((10L*(n+1)+n*(n+1)/2)*100+10+n),Eval2.eval(code,n)); + } + + static final String INLINE_MEMORY = """ + struct S { int x; int y; }; + val make = { S s -> + S t = new S { x=s.x+1; y=3; }; + s.y=9; + return t; + }; + S !a = new S { x=arg+40; y=7; }; + S t=make(a); + return a.x*10000+a.y*100+t.x+t.y; + """; + + @Test public void testInlineAllocationMemory() { + var code = new CodeGen(INLINE_MEMORY).parse().opto(); + assertNull(code._stop.walk(n -> n instanceof CallNode ? n : null)); + assertEquals("400944",Eval2.eval(code,0)); + assertEquals("420946",Eval2.eval(code,2)); + } } diff --git a/chapter18/src/test/java/com/seaofnodes/simple/Eval2.java b/chapter18/src/test/java/com/seaofnodes/simple/Eval2.java index 50207d8c..c517ce6e 100644 --- a/chapter18/src/test/java/com/seaofnodes/simple/Eval2.java +++ b/chapter18/src/test/java/com/seaofnodes/simple/Eval2.java @@ -327,7 +327,7 @@ public abstract class Eval2 { private static Object alloc(NewNode alloc) { TypeStruct type = alloc._ptr._obj; if( type.isAry() ) { - long sz = (Long)val(alloc.in(1)); + long sz = (Long)val(alloc.size()); long x = offToIdx(sz, type); int n = (int)x; if( n!=x || n<0 ) @@ -347,7 +347,7 @@ public abstract class Eval2 { int num = type._fields.length; Object[] ptr = new Object[num]; for( int i=0; i<num; i++ ) - ptr[i] = val(alloc.in(2+i+num)); + ptr[i] = val(alloc.in(3+i)); return ptr; } @@ -438,9 +438,9 @@ public abstract class Eval2 { case TypeRPC rpc -> sb.p(x.toString()); case TypeMem mem -> sb.p("$mem"); case TypeTuple tt -> { - if( tt._types.length>1 && tt._types[1] instanceof TypeMemPtr ) + if( tt._types.length>1 && tt._types[0] instanceof TypeMemPtr ) // Assume a NewNode - yield _print( tt._types[1], x, sb, visit ); + yield _print( tt._types[0], x, sb, visit ); throw Utils.TODO(); } case Type tt -> { diff --git a/chapter18/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/chapter18/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java index 92b424df..1e2a08a7 100644 --- a/chapter18/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java +++ b/chapter18/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java @@ -185,7 +185,7 @@ public class Evaluator { Object[] body=null; int num; if( type.isAry() ) { - long sz = (Long)val(alloc.in(1)); + long sz = (Long)val(alloc.size()); long n = offToIdx(sz, type); if( n < 0 ) throw new NegativeArraySizeException(""+n); @@ -199,17 +199,14 @@ public class Evaluator { assert elem instanceof TypeMemPtr; } // Length value - body[0] = vall(alloc.in(2+2)); + body[0] = vall(alloc.in(3)); } else { body = new Object[num = type._fields.length]; for (int i=0; i<num; i++) - body[i] = val(alloc.in(2+i+num)); + body[i] = val(alloc.in(3+i)); } - Object[] mems = new Object[type._fields.length+2]; - // mems[0] is control - mems[1] = new Obj(type,body); // the ref - // mems[2+...] are memory aliases - return mems; + return new Object[]{new Obj(type,body),null}; + } private Object load(LoadNode load) { diff --git a/chapter19/README.md b/chapter19/README.md index 0d8e481f..63929b34 100644 --- a/chapter19/README.md +++ b/chapter19/README.md @@ -53,6 +53,32 @@ global. In short the compiler gets a lot more functional, and a big step towards concurrent or multi-threaded compilation. +## Memory through instruction selection + +The parser carries one `$mem` binding. BulkMemPhi splits aliases on demand into +parallel MemPhis, while MemMerge packages a default memory and explicit slices. +Both machine selectors preserve these node classes and their alias information; +they do not turn memory Phis into ordinary value Phis. + +This chapter lowers nonzero constructor values into explicit Stores, keeping the +allocation itself a zeroing operation. New consumes `{ctrl, $mem, size}` and +produces `{ptr, $mem}`. Its input is a partial MemMerge covering the allocated +struct's aliases; its single memory result covers those same aliases. The parser +preserves unrelated memory in the surrounding aggregate. The allocation's size +register is input 2, its pointer result is output 0, and memory has no register. + +Selected memory operations retain their field alias even when an x86 arithmetic +or comparison instruction absorbs a Load. Global scheduling therefore finds +anti-dependencies through the common MemOpNode, following the relevant slice +through MemMerge. Memory-result instructions are writers; other memory operations +are readers. Trailing ordering edges on writers impose no register requirement. +The simple one-step Load guard used when factoring Phis is unchanged. + +Calls still consume and produce whole memory. They terminate their blocks, so +scheduling raises a read before a Call without adding inputs to its argument +list. Direct machine calls embed their constant target and retain only control, +memory, and argument inputs. + ## Registers and Register Masks diff --git a/chapter19/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java b/chapter19/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java index 955f20df..95ada83d 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/GlobalCodeMotion.java @@ -48,7 +48,7 @@ public abstract class GlobalCodeMotion { var cons = new ArrayList<Node>(); for( Node con : start._outputs ) if( con!=null && !(con instanceof CFGNode) && - (con.isConst() || con instanceof MachNode mach && mach.outregmap()!=null) ) { + (con.isConst() || (con instanceof MachNode mach && mach.outregmap()!=null)) ) { globals.put(con,true); cons.add(con.keep()); // Preserve original inputs while rewiring users. } @@ -169,15 +169,9 @@ public abstract class GlobalCodeMotion { continue outer; // Nope, await all uses done // Loads need their memory inputs' uses also done - if( n instanceof LoadNode ld ) - for( Node memuse : ld.mem()._outputs ) - if( late[memuse._nid]==null && - // New makes new memory, never crushes load memory - !(memuse instanceof NewNode) && - // Load-use directly defines memory - (memuse._type instanceof TypeMem || - // Load-use indirectly defines memory - (memuse._type instanceof TypeTuple tt && tt._types[ld._alias] instanceof TypeMem)) ) + if( n instanceof MemOpNode ld && !ld.isMem() ) + for( Node memuse : ld.antiDeps() ) + if( late[memuse._nid]==null ) continue outer; // All uses done, schedule @@ -189,9 +183,7 @@ public abstract class GlobalCodeMotion { for( Node def : n._inputs ) { if( def==null ) continue; if( late[def._nid]==null ) work.push(def); - for( Node out : def._outputs ) - if( out instanceof LoadNode ld && late[ld._nid]==null ) - work.push(ld); + wakeLoads(def,late,work,new BitSet()); } if( n instanceof LoopNode loop ) for( Node phi : loop._outputs ) @@ -210,7 +202,7 @@ public abstract class GlobalCodeMotion { lca = use_block(n,use, late).domLCA(lca,null); // Loads may need anti-dependencies, raising their LCA - if( n instanceof LoadNode load ) + if( n instanceof MemOpNode load && !load.isMem() ) lca = find_anti_dep(lca,load,early,late,anti); // Walk up from the LCA to the early, looking for best place. This is @@ -249,15 +241,26 @@ public abstract class GlobalCodeMotion { best instanceof IfNode; } - private static CFGNode find_anti_dep(CFGNode lca, LoadNode load, CFGNode early, CFGNode[] late, int[] anti) { + // An allocation may consume a load's memory through a partial aggregate. + private static void wakeLoads(Node def, CFGNode[] late, WorkList<Node> work, BitSet visit) { + if( visit.get(def._nid) ) return; + visit.set(def._nid); + for( Node out : def._outputs ) + if( out instanceof MemOpNode ld && !ld.isMem() && late[ld._nid]==null ) work.push(ld); + if( def instanceof MemMergeNode ) + for( int i=1; i<def.nIns(); i++ ) + if( def.in(i)!=null ) wakeLoads(def.in(i),late,work,visit); + } + + private static CFGNode find_anti_dep(CFGNode lca, MemOpNode load, CFGNode early, CFGNode[] late, int[] anti) { // We could skip final-field loads here. // Walk LCA->early, flagging Load's block location choices for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() ) anti[cfg._nid] = load._nid; // Walk load->mem uses, looking for Stores causing an anti-dep - for( Node mem : load.mem()._outputs ) { + for( Node mem : load.antiDeps() ) { switch( mem ) { - case StoreNode st: + case MemOpNode st: assert late[st._nid]!=null; lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st,anti); break; @@ -272,11 +275,10 @@ public abstract class GlobalCodeMotion { if( phi.in(i)==load.mem() ) lca = anti_dep(load,phi.region().cfg(i),load.mem().cfg0(),lca,null,anti); break; - case NewNode st: break; - case LoadNode ld: break; // Loads do not cause anti-deps on other loads - case ReturnNode ret: break; // Load must already be ahead of Return - case MemMergeNode ret: break; // Mem uses now on ScopeMin - case NeverNode never: break; + case NewNode st: + assert late[st._nid]!=null; + lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st,anti); + break; default: throw Utils.TODO(); } } @@ -284,15 +286,13 @@ public abstract class GlobalCodeMotion { } // - private static CFGNode anti_dep( LoadNode load, CFGNode stblk, CFGNode defblk, CFGNode lca, Node st, int[] anti ) { - // Preserve the full store range for the earlier evaluator scheduler. - // It places nodes independently of GCM and may hoist this store. + private static CFGNode anti_dep( MemOpNode load, CFGNode stblk, CFGNode defblk, CFGNode lca, Node st, int[] anti ) { for( ; stblk != defblk.idom(); stblk = stblk.idom() ) { // Store and Load overlap, need anti-dependence if( anti[stblk._nid]==load._nid ) { lca = stblk.domLCA(lca,null); // Raise Loads LCA - if( lca == stblk && st != null && st._inputs.find(load) == -1 ) // And if something moved, - st.addDef(load); // Add anti-dep as well + if( lca == stblk && st != null && !(st instanceof CallNode) && st._inputs.find(load) == -1 ) // And if something moved, + st.addDef(load); // Add anti-dep as well; Calls already end their block. return lca; // Cap this stores' anti-dep to here } } diff --git a/chapter19/src/main/java/com/seaofnodes/simple/Parser.java b/chapter19/src/main/java/com/seaofnodes/simple/Parser.java index 1a775050..8caa7d69 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/Parser.java @@ -137,7 +137,7 @@ public class Parser { _scope.define(ScopeNode.ARG0, TypeInteger.BOT, false, null, _lexer); ctrl(XCTRL); - _scope.mem(new MemMergeNode(false)); + _scope.mem(con(TypeMem.BOT)); // Parse whole program, as-if function header "{ int arg -> body }" parseFunctionBody(_code._main,loc(),"arg"); @@ -177,7 +177,7 @@ public class Parser { // Build a multi-exit return point for all function returns RegionNode r = new RegionNode((Lexer)null,null,null).init(); assert r.inProgress(); - PhiNode rmem = new PhiNode(ScopeNode.MEM0,TypeMem.BOT,r,null).init(); + PhiNode rmem = new BulkMemPhiNode(ScopeNode.MEM0,r,null).init(); PhiNode rrez = new PhiNode(ScopeNode.ARG0,Type.BOTTOM,r,null).init(); ReturnNode ret = new ReturnNode(r, rmem, rrez, rpc, fun).init(); fun.setRet(ret); @@ -189,11 +189,8 @@ public class Parser { // external calls. _scope.push(ScopeNode.Kind.Function); ctrl(fun); // Scope control from function - // Private mem alias tracking per function - MemMergeNode mem = new MemMergeNode(true); - mem.addDef(null); // Alias#0 - mem.addDef(new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,con(TypeMem.BOT)).peephole()); // All aliases - _scope.mem(mem); + // Unknown callers supply whole memory; field aliases are discovered lazily. + _scope.mem(new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,con(TypeMem.BOT)).peephole()); // All args, "as-if" called externally for( int i=0; i<ids.length; i++ ) { Type t = sig.arg(i); @@ -207,7 +204,7 @@ public class Parser { // Last expression is the return if( ctrl()._type==Type.CONTROL ) - fun.addReturn(ctrl(), _scope.mem().merge(), last); + fun.addReturn(ctrl(), _scope.mem(), last); // Pop off the inProgress node on the multi-exit Region merge assert r.inProgress(); @@ -567,9 +564,8 @@ public class Parser { */ private Node parseReturn() { var expr = require(parseAsgn(), ";"); - // Need default memory, since it can be lazy, need to force - // a non-lazy Phi - _fun.addReturn(ctrl(), _scope.mem().merge(), expr); + // Force the lazy whole-memory Phi before closing this exit. + _fun.addReturn(ctrl(), _scope.mem(), expr); ctrl(XCTRL); // Kill control return expr; } @@ -1182,29 +1178,31 @@ public class Parser { Field[] fs = obj._fields; if( fs==null ) throw error("Unknown struct type '" + obj._name + "'"); - int len = fs.length; - Node[] ns = new Node[2+len]; - ns[0] = ctrl(); // Control in slot 0 - // Total allocated length in bytes - ns[1] = size; - // Memory aliases for every field - for( int i = 0; i < len; i++ ) - ns[2+i] = memAlias(fs[i]._alias); - Node nnn = new NewNode(TypeMemPtr.make(obj), ns).peephole().keep(); - for( int i = 0; i < len; i++ ) - memAlias(fs[i]._alias, new ProjNode(nnn,i+2,memName(fs[i]._alias)).peephole()); - Node ptr = new ProjNode(nnn.unkeep(),1,obj._name).peephole().keep(); - - // Initial nonzero values for every field - for( int i = 0; i < len; i++ ) { - Node val = init.get( i + idx ); + Node prior = mem().keep(); + MemMergeNode input = new MemMergeNode((Node)null); + for( Field f : fs ) input.alias(f._alias,prior); + // Allocation zeroes its fields; nonzero initializers become explicit Stores. + Node[] ns = new Node[3]; + ns[0] = ctrl(); + ns[1] = input.peephole(); + ns[2] = size; + Node nnn = new NewNode(TypeMemPtr.make(obj),ns).peephole().keep(); + Node ptr = new ProjNode(nnn,0,obj._name).peephole().keep(); + Node out = new ProjNode(nnn,1,"$mem").peephole().keep(); + MemMergeNode after = new MemMergeNode(prior); + for( Field f : fs ) after.alias(f._alias,out); + mem(after.peephole()); + out.unkeep(); + nnn.unkeep(); + prior.unkeep(); + for( int i=0; i<fs.length; i++ ) { + Node val = init.get(idx+i); if( val._type != val._type.makeZero() ) { - Node mem = memAlias(fs[i]._alias); - Node st = new StoreNode(loc(),fs[i]._fname,fs[i]._alias,fs[i]._type,mem,ptr,con(obj.offset(i)),val,true).peephole(); - memAlias(fs[i]._alias,st); + Field f = fs[i]; + Node st = new StoreNode(loc(),f._fname,f._alias,f._type,mem(),ptr,con(obj.offset(i)),val,true).peephole(); + mem(new MemMergeNode(mem(),f._alias,st).peephole()); } } - return ptr.unkeep(); } @@ -1217,13 +1215,18 @@ public class Parser { return newStruct(ary,size,0,ALTMP); } - // 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.mem(alias ); } - private void memAlias(int alias, Node st) { _scope.mem(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.mem(); } + private void mem(Node n) { _scope.mem(n); } + + private void store(String name, int alias, Type glb, Node ptr, Node off, Node val, boolean init, Node ctrl) { + Node prior = mem().keep(); + Node st = new StoreNode(loc(),name,alias,glb,prior,ptr,off,val,init); + st.setDef(0,ctrl); + st = st.peephole(); + mem(new MemMergeNode(prior,alias,st).peephole()); + prior.unkeep(); + } /** * Parse postfix expression; this can be a field expression, an array @@ -1266,6 +1269,7 @@ public class Parser { if( tf instanceof TypeMemPtr ftmp && ftmp.isFRef() ) tf = ftmp.makeFrom(((TypeMemPtr)(TYPES.get(ftmp._obj._name)))._obj); + expr.keep(); // Field offset; fixed for structs, computed for arrays Node off = (name.equals("[]") // If field is an array body // Array index math @@ -1278,16 +1282,11 @@ public class Parser { Node val = parseAsgn().keep(); Node lift = liftExpr( val, tf, f._final ); - Node st = new StoreNode(loc(), name, f._alias, tf, memAlias(f._alias), expr, off.unkeep(), lift, false); - // Arrays include control, as a proxy for a safety range check. - // Structs don't need this; they only need a NPE check which is - // done via the type system. - if( base.isAry() ) st.setDef(0,ctrl()); - memAlias(f._alias, st.peephole()); + store(name,f._alias,tf,expr.unkeep(),off.unkeep(),lift,false,base.isAry() ? ctrl() : null); return val.unkeep(); // "obj.a = expr" returns the expression while updating memory } - Node load = new LoadNode(loc(),name, f._alias, tf, memAlias(f._alias), expr.keep(), off); + Node load = new LoadNode(loc(),name, f._alias, tf, mem(), expr, off); // Arrays include control, as a proxy for a safety range check // Structs don't need this; they only need a NPE check which is // done via the type system. @@ -1298,12 +1297,7 @@ public class Parser { char ch = _lexer.matchOperAssign(); if( ch!=0 ) { Node op = opAssign(ch,load,f._final,tf,name); - Node st = new StoreNode(loc(), name, f._alias, tf, memAlias(f._alias), expr.unkeep(), off, op, false); - // Arrays include control, as a proxy for a safety range check. - // Structs don't need this; they only need a NPE check which is - // done via the type system. - if( base.isAry() ) st.setDef(0,ctrl()); - memAlias(f._alias, peep(st)); + store(name,f._alias,tf,expr.unkeep(),off,op,false,base.isAry() ? ctrl() : null); load = postfix(ch) ? load.unkeep() : op; // And use the original loaded value as the result } else expr.unkill(); @@ -1384,7 +1378,7 @@ public class Parser { } // Control & memory after parsing args args.set(0,ctrl().keep()); - args.set(1,_scope.mem().merge().keep()); + args.set(1,_scope.mem().keep()); args.push(expr); // Function pointer // Unkeep them all for( Node arg : args ) @@ -1404,11 +1398,8 @@ public class Parser { CallEndNode cend = (CallEndNode)new CallEndNode(call).peephole(); // Control from CallEnd ctrl(new CProjNode(cend,0,ScopeNode.CTRL).peephole()); - // Memory from CallEnd - MemMergeNode mem = new MemMergeNode(true); - mem.addDef(null); // Alias#0 - mem.addDef(new ProjNode(cend,1,ScopeNode.MEM0).peephole()); - _scope.mem(mem); + // A call may update any alias, so its memory result is the new whole state. + _scope.mem(new ProjNode(cend,1,ScopeNode.MEM0).peephole()); // Call result return new ProjNode(cend,2,null).peephole(); } diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java new file mode 100644 index 00000000..5d8a4aba --- /dev/null +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java @@ -0,0 +1,240 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.CodeGen; +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); } + + public BulkMemPhiNode(BulkMemPhiNode phi) { super(phi); _aliases = phi._aliases; } + + @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,CodeGen.CODE.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 mmm.in(1)!=null && missingAlias(mmm)==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 ParmNode parm -> 0; + case ConstantNode con -> 0; + case MemMergeNode mmm -> missingAlias(mmm); + 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); + case BulkMemPhiNode bulk -> missingAlias(bulk); + case MemOpNode mem -> unsplit(mem._alias); + case MemPhiNode phi -> unsplit(phi._alias); + default -> { + assert use instanceof ReturnNode || use instanceof CallNode || use instanceof ParmNode + : "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; + } + + // Every explicit slot requests that alias, including a partial allocation + // input whose slot still points to this bulk Phi. + private int missingAlias(MemMergeNode mmm) { + for( int alias=2; alias<mmm.nIns(); alias++ ) + if( mmm.in(alias)!=null && + mmm.alias(alias)!=mmm.in(1) && + !_aliases.get(alias) ) + 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++ ) + CodeGen.CODE.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; + CodeGen.CODE.add(bulk); + CodeGen.CODE.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,Type.BOTTOM)); + for( int i=1; i<nIns(); i++ ) + mphi.addDef(CodeGen.CODE.add(preciseInput(in(i),alias))); + return CodeGen.CODE.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/chapter19/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/LoadNode.java index 15c5664d..5a04e12d 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.simple.CodeGen; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.type.*; @@ -23,6 +24,25 @@ public class LoadNode extends MemOpNode { super(loc, name, alias, glb, mem, ptr, off); } + @Override boolean canDrop(MemOpNode other, Node dep) { + return super.canDrop(other,dep) && !clobbered(dep); + } + + // Check only immediate memory users. Writers clobber memory; Phis and + // aggregates might lead to a clobber, so stop rather than search further. + private boolean clobbered(Node dep) { + Node mem = mem(); + mem.addDepForwards(dep); + for( Node use : mem._outputs ) { + if( use==null ) continue; + use.addDepForwards(dep); + if( use instanceof StoreNode || use instanceof PhiNode || + use instanceof MemMergeNode || + use instanceof NewNode || use instanceof CallNode ) return true; + } + return false; + } + // Debugger label @Override public String label() { return "ld_"+mlabel(); } @Override @@ -30,32 +50,32 @@ public class LoadNode extends MemOpNode { @Override public Type compute() { - if( mem()._type instanceof TypeMem mem ) { - // Update declared forward ref to the actual - if( _declaredType.isFRef() && mem._t instanceof TypeMemPtr tmp && !tmp.isFRef() ) - _declaredType = tmp; - // No lifting if ptr might null-check - if( err()==null ) - return _declaredType.join(mem._t); - } - return _declaredType; + Type t = MemMergeNode.contents(mem(),_alias,this); + // Update declared forward ref to the actual. + if( _declaredType.isFRef() && t instanceof TypeMemPtr tmp && !tmp.isFRef() ) + _declaredType = tmp; + return err()==null ? _declaredType.join(t) : _declaredType; } @Override public Node idealize() { + if( mem() instanceof MemMergeNode merge ) { + setDef(1,CodeGen.CODE.add(merge.alias(_alias))); + return this; + } Node ptr = ptr(); Node mem = mem(); // Simple Load-after-Store on same address. - if( mem instanceof StoreNode st && - ptr == st.ptr() && off() == st.off() ) { // Must check same object + if( mem() instanceof StoreNode st && + _alias==st._alias && ptr == st.ptr() && off() == st.off() ) { // Must check same object assert _name.equals(st._name); // Equiv class aliasing is perfect return extend(st.val()); } // Simple Load-after-New on same address. if( mem instanceof ProjNode p && p.in(0) instanceof NewNode nnn && - ptr == nnn.proj(1) ) // Must check same object + ptr == nnn.proj(0) ) // Must check same object return zero(nnn); // Load zero from new // Uplift control to a prior dominating load. @@ -72,12 +92,16 @@ public class LoadNode extends MemOpNode { ptr = ro.in(1); outer: while( true ) { + mem.addDep(this); switch( mem ) { + case MemMergeNode merge: + mem = merge.alias(_alias); + break; case StoreNode st: - if( ptr == st.ptr().addDep(this) && off() == st.off() ) + if( _alias==st._alias && ptr == st.ptr().addDep(this) && off() == st.off() ) return extend(castRO(st.val())); // Proved equal // Can we prove unequal? Offsets do not overlap? - if( !off()._type.join(st.off()._type).isHigh() && // Offsets overlap + if( _alias==st._alias && !off()._type.join(st.off()._type).isHigh() && // Offsets overlap !neverAlias(ptr,st.ptr()) ) // And might alias break outer; // Cannot tell, stop trying // Pointers cannot overlap @@ -95,17 +119,16 @@ public class LoadNode extends MemOpNode { return zero(nnn1); if( !(ptr instanceof ProjNode pproj && pproj.in(0) instanceof NewNode) ) break outer; // Cannot tell, ptr not related to New - mem = nnn1.in(nnn1.findAlias(_alias));// Bypass unrelated New + mem = nnn1.mem();// Bypass unrelated New break; case StartNode start: break outer; case CallEndNode cend: break outer; // TODO: Bypass no-alias call default: throw Utils.TODO(); } break; - case MemMergeNode merge: mem = merge.alias(_alias); break; default: - throw Utils.TODO(); + break outer; } } @@ -116,7 +139,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 && // Offset can be hoisted off() instanceof ConstantNode && // Pointer can be hoisted @@ -145,7 +168,9 @@ public class LoadNode extends MemOpNode { private Node ld( int idx ) { Node mem = mem(), ptr = ptr(); - return new LoadNode(_loc,_name,_alias,_declaredType,mem.in(idx),ptr instanceof PhiNode && ptr.in(0)==mem.in(0) ? ptr.in(idx) : ptr, off()).peephole(); + LoadNode ld = new LoadNode(_loc,_name,_alias,_declaredType,mem.in(idx),ptr instanceof PhiNode && ptr.in(0)==mem.in(0) ? ptr.in(idx) : ptr,off()); + ld.setDef(0,in(0)); + return ld.peephole(); } private static boolean neverAlias( Node ptr1, Node ptr2 ) { @@ -178,9 +203,9 @@ public class LoadNode extends MemOpNode { private boolean profit(PhiNode phi, int idx) { Node px = phi.in(idx); if( px==null ) return false; - if( px._type instanceof TypeMem mem && mem._t.isHighOrConst() ) return true; - if( px instanceof StoreNode st1 && ptr()==st1.ptr() && off()==st1.off() ) return true; px.addDep(this); + if( px._type instanceof TypeMem mem && mem._t.isHighOrConst() ) return true; + if( px instanceof StoreNode st1 && _alias==st1._alias && ptr()==st1.ptr() && off()==st1.off() ) return true; return false; } @@ -205,4 +230,5 @@ public class LoadNode extends MemOpNode { Node shl = new ShlNode(null,val,shf.keep()).peephole(); return new SarNode(null,shl,shf.unkeep()); } + } diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java index a540e7ca..b9b0c8a4 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java @@ -1,154 +1,89 @@ package com.seaofnodes.simple.node; -import com.seaofnodes.simple.*; -import com.seaofnodes.simple.type.*; -import java.util.*; +import com.seaofnodes.simple.CodeGen; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeMem; +import java.util.BitSet; -/** - * Memory Merge - a merge of many aliases into a "fat memory". All aliases - * are here, but most will be lazy - take the default fat memory. +/** A default slice and explicit alias overrides. A null default means partial + * memory: absent aliases are not covered, rather than having unknown contents. */ public class MemMergeNode extends Node { + public MemMergeNode(MemMergeNode mem) { super(mem); } - /* - * In-Progress means this is being used by the Parser to track memory - * aliases. No optimizations are allowed. When no longer "in progress" - * normal peeps work. - */ - public final boolean _inProgress; - - public MemMergeNode( boolean inProgress) { _type = TypeMem.BOT; _inProgress = inProgress; } - public MemMergeNode(MemMergeNode mem) { super(mem); _inProgress = false; } - - - // If being used by a Scope, this is "in progress" from the Parser. - // Otherwise, it's a memory merge - boolean inProgress() { return _inProgress; } - - @Override public String label() { return "ALLMEM"; } - @Override public boolean isMem() { return true; } - - @Override - StringBuilder _print1(StringBuilder sb, BitSet visited) { - sb.append(_inProgress ? "Merge[" : "MEM[ "); - for( int j=2; j<nIns(); j++ ) { - sb.append(j); - sb.append(":"); - Node n = in(j); - while( n instanceof ScopeNode loop ) { - sb.append("Lazy_"); - n = loop.mem().alias(j); - } - if( n==null ) sb.append("___ "); - else n._print0(sb, visited).append(" "); - } - sb.setLength(sb.length()-1); - return sb.append("]"); - } - - // Make a memory merge: no longer a Scope really, tracking memory state but - // not related to the parser in any way. - public Node merge() { - // Force default memory to not be lazy - MemMergeNode merge = new MemMergeNode(false); - for( Node n : _inputs ) - merge.addDef(n); - merge._mem(1,null); - return merge.peephole(); - } - - - @Override public Type compute() { return TypeMem.BOT; } - - @Override public Node idealize() { - if( inProgress() ) return null; - - // If not merging any memory (all memory is just the default) - if( nIns()==2 ) - return in(1); // Become default memory - - return null; + 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 in( Var v ) { return in(v._idx); } - - public Node alias( int alias ) { - return in(alias<nIns() && in(alias)!=null ? alias : 1); + public Node alias(int alias) { + Node n = alias < nIns() ? in(alias) : null; + assert n!=null || !(in(1) instanceof BulkMemPhiNode bulk) || !bulk.isSplit(alias); + n = n==null ? in(1) : n; + assert n!=null : "Alias not covered: "+alias; + return n; } - Node alias( int alias, Node st ) { - while( alias >= nIns() ) addDef(null); - return setDef(alias,st); + public void alias(int alias, Node mem) { + assert alias > 1; + while( nIns()<=alias ) addDef(null); + setDef(alias,mem); } - // Read or update from memory. - // A shared implementation allows us to create lazy phis both during - // lookups and updates; the lazy phi creation is part of chapter 8. - Node _mem( int alias, Node st ) { - // Memory projections are made lazily; if one does not exist - // then it must be START.proj(1) - Node old = alias(alias); - if( old instanceof ScopeNode loop ) { - MemMergeNode loopmem = loop.mem(); - Node memdef = loopmem.alias(alias); - // Lazy phi! - old = memdef instanceof PhiNode phi && loop.ctrl()==phi.region() - // Loop already has a real Phi, use it - ? memdef - // 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. - : loopmem.alias(alias, new PhiNode(Parser.memName(alias), TypeMem.BOT,loop.ctrl(),loopmem._mem(alias,null),null).peephole() ); - alias(alias,old); + // Scalar contents of one alias. New zeroes the allocated fields; + // join it with the incoming contents for all previously allocated objects. + // Phis use their cached types, so this query does not recurse around loops. + static Type contents(Node mem, int alias, Node dep) { + mem.addDep(dep); + if( mem instanceof MemMergeNode merge ) + return contents(merge.alias(alias),alias,dep); + if( mem instanceof ProjNode proj && proj.in(0) instanceof NewNode nnn ) { + assert proj._idx==1 && nnn.field(alias)!=null; + nnn.addDep(dep); + return contents(nnn.mem(),alias,dep).meet(nnn.field(alias)._type.makeZero()); } - // Memory projections are made lazily; expand as needed - return st==null ? old : alias(alias,st); // Not lazy, so this is the answer - } - - - void _merge( MemMergeNode that, RegionNode r) { - int len = Math.max(nIns(),that.nIns()); - for( int i = 2; i < len; i++) - if( alias(i) != that.alias(i) ) { // No need for redundant Phis - // If we are in lazy phi mode we need to a lookup - // by alias as it will trigger a phi creation - Node lhs = this._mem(i,null); - Node rhs = that._mem(i,null); - alias(i, new PhiNode(Parser.memName(i), lhs._type.glb().meet(rhs._type.glb()), r, lhs, rhs).peephole()); - } + // Function parameters and call results may contain arbitrary heap values. + // Their bulk memory types do not imply empty or zero-filled storage. + if( mem._type==Type.TOP || mem._type==TypeMem.TOP ) return Type.TOP; + if( mem._type instanceof TypeMem mt && mt._alias==alias ) return mt._t; + return Type.BOTTOM; } - // Fill in the backedge of any inserted Phis - void _endLoopMem( ScopeNode scope, MemMergeNode back, MemMergeNode exit ) { - Node exit_def = exit.alias(1); - for( int i=1; i<nIns(); i++ ) { - if( in(i) instanceof PhiNode phi && phi.region()==scope.ctrl() ) { - assert phi.in(2)==null; - phi.setDef(2,back.in(i)==scope ? phi : back.in(i)); // Fill backedge + @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(" "); } - if( exit_def == scope ) // Replace a lazy-phi on the exit path also - exit.alias(i,in(i)); - } + return sb.append("]"); } - // Now one-time do a useless-phi removal - void _useless( ) { + @Override public Node idealize() { + boolean progress=false, allDefault=true; for( int i=2; i<nIns(); i++ ) { - if( in(i) instanceof PhiNode phi ) { - // Do an eager useless-phi removal - Node in = phi.peephole(); - CodeGen.CODE.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 - } + if( in(i)!=null && in(i)==in(1) ) { setDef(i,null); progress=true; } + if( in(i) instanceof MemMergeNode mem ) { + setDef(i,CodeGen.CODE.add(mem.alias(i))); + progress=true; } + if( in(i)!=null ) allDefault=false; } - } - - @Override boolean eq( Node n ) { - return this==n || !_inProgress; + if( allDefault && in(1)!=null ) 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)); + CodeGen.CODE.add(mem.in(1)); + setDef(1,mem.in(1)); + return this; + } + assert BulkMemPhiNode.checkMerge(this); + return progress ? this : null; } } diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/MemOpNode.java index d9ab6b89..e792c152 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/MemOpNode.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/MemOpNode.java @@ -6,6 +6,7 @@ import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeMemPtr; import java.lang.StringBuilder; import java.util.BitSet; +import java.util.ArrayList; /** * Convenience common base for Load and Store. @@ -51,6 +52,13 @@ public abstract class MemOpNode extends Node { public StringBuilder _printMach( StringBuilder sb, BitSet visited ) { throw Utils.TODO(); } + // Extra conditions for factoring matching memory operations through a Phi. + // The caller has already checked the opcode, input shape and control. + boolean canDrop(MemOpNode other, Node dep) { + ptr().addDep(dep); + return _alias==other._alias && _declaredType==other._declaredType && err()==null; + } + @Override boolean eq(Node n) { MemOpNode mem = (MemOpNode)n; // Invariant @@ -71,4 +79,29 @@ public abstract class MemOpNode extends Node { return null; return Parser.error( "Might be null accessing '" + _name + "'",_loc); } + // Memory writers which must follow this read. A packaging node is not a + // write, but can hide New's input, so follow this alias through aggregates. + public ArrayList<Node> antiDeps() { + ArrayList<Node> deps = new ArrayList<>(); + antiDeps(mem(),deps,new BitSet()); + return deps; + } + + private void antiDeps(Node mem, ArrayList<Node> deps, BitSet visit) { + if( visit.get(mem._nid) ) return; + visit.set(mem._nid); + for( Node use : mem._outputs ) { + if( use instanceof MemMergeNode merge ) { + Node slice = _alias<merge.nIns() ? merge.in(_alias) : null; + if( (slice==mem) || (slice==null && merge.in(1)==mem) ) + antiDeps(merge,deps,visit); + } else if( use instanceof CallNode || + (use instanceof MemOpNode st && st.isMem() && st._alias==_alias) || + (use instanceof NewNode nnn && nnn.field(_alias)!=null) || + (use instanceof MemPhiNode phi && phi._alias==_alias) || + (use instanceof BulkMemPhiNode phi && !phi.isSplit(_alias)) ) { + if( !deps.contains(use) ) deps.add(use); + } + } + } } diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java new file mode 100644 index 00000000..411370e1 --- /dev/null +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java @@ -0,0 +1,44 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.CodeGen; +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,Type.BOTTOM),inputs); + assert alias > 1; + _alias = alias; + } + + public MemPhiNode(MemPhiNode phi) { super(phi); _alias = phi._alias; } + + @Override public String label() { return "MemPhi_"+_alias; } + + @Override public Type compute() { + assert BulkMemPhiNode.checkMem(this,_alias); + if( !(region() instanceof RegionNode r) ) + return region()._type==Type.XCONTROL ? TypeMem.TOP : _type; + if( r.inProgress() ) return _declaredType; + Type t = Type.TOP; + for( int i=1; i<nIns(); i++ ) + if( r.in(i).addDep(this)._type!=Type.XCONTROL ) + t = t.meet(MemMergeNode.contents(in(i),_alias,this)); + return TypeMem.make(_alias,t); + } + + @Override public Node idealize() { + for( int i=1; i<nIns(); i++ ) + if( in(i) instanceof MemMergeNode mem ) { + setDef(i,CodeGen.CODE.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/chapter19/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/NewNode.java index abce1601..b9c9de71 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/NewNode.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/NewNode.java @@ -1,37 +1,37 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.Utils; import java.util.BitSet; -/** - * Allocation! Allocate a chunk of memory, and pre-zero it. - * The inputs include control and size, and ALL aliases being set. - * The output is large tuple, one for every alias plus the created pointer. - * New is expected to be followed by projections for every alias. +/** Allocate a zeroed object. Inputs {ctrl, $mem, size}; + * results {ptr, $mem}. + * The memory input and result cover only the aliases in the allocated struct. */ public class NewNode extends Node implements MultiNode { public final TypeMemPtr _ptr; - public final int _len; public NewNode(TypeMemPtr ptr, Node... nodes) { super(nodes); assert !ptr.nullable(); _ptr = ptr; - _len = ptr._obj._fields.length; - // Control in slot 0 - assert nodes[0]._type==Type.CONTROL || nodes[0]._type == Type.XCONTROL; - // Malloc-length in slot 1 - assert nodes[1]._type instanceof TypeInteger || nodes[1]._type==Type.NIL; - for( int i=0; i<_len; i++ ) - // Memory slices for all fields. - assert nodes[2+i]._type.isa(TypeMem.BOT); + assert nodes.length==3; + assert nodes[0]._type==Type.CONTROL || nodes[0]._type==Type.XCONTROL; + assert nodes[1]._type instanceof TypeMem; + assert nodes[2]._type instanceof TypeInteger || nodes[2]._type==Type.NIL; } - public NewNode(NewNode nnn) { super(nnn); _ptr = nnn._ptr; _len = nnn._len; } + public Node mem() { return in(1); } + public Node size() { return in(2); } + + public Field field(int alias) { + for( Field f : _ptr._obj._fields ) + if( f._alias==alias ) return f; + return null; + } + + public NewNode(NewNode nnn) { super(nnn); _ptr = nnn._ptr; } - public Node mem (int idx) { return in(idx+2); } @Override public String label() { return "new_"+(_ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._type.str() : _ptr._obj.str()); } @Override @@ -40,32 +40,10 @@ public class NewNode extends Node implements MultiNode { return sb.append(_ptr._obj.str()); } - int findAlias(int alias) { - int idx = _ptr._obj.findAlias(alias); - assert idx!= -1; // Error, caller should be calling - return idx+2; // Skip ctrl, size - } - - // 0 - ctrl - // 1 - byte size - // 2-len+2 - aliases, one per field - // len+2 - 2*len+2 - initial values, one per field - public Node size() { return in(1); } @Override public TypeTuple compute() { - Field[] fs = _ptr._obj._fields; - Type[] ts = new Type[fs.length+2]; - ts[0] = Type.CONTROL; - ts[1] = _ptr; - for( int i=0; i<fs.length; i++ ) { - Type mt = in(i+2)._type; - TypeMem mem = mt==Type.TOP ? TypeMem.TOP : (TypeMem)mt; - Type tfld = mem._t.meet(mem._t.makeZero()); - Type tfld2 = tfld.join(fs[i]._type); - ts[i+2] = TypeMem.make(fs[i]._alias,tfld2); - } - return TypeTuple.make(ts); + return TypeTuple.make(_ptr,TypeMem.BOT); } @Override diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter19/src/main/java/com/seaofnodes/simple/node/Node.java index 98e561db..13e5fac8 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/Node.java @@ -1,7 +1,6 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.CodeGen; - import com.seaofnodes.simple.*; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeFloat; @@ -225,6 +224,7 @@ public abstract class Node implements Cloneable { // Error is 'use' does not exist; ok for 'use' to be null. protected boolean delUse( Node use ) { _outputs.del(_outputs.find(use)); + moveDepsToWorklist(); // User-count and anti-dependence queries can now change. return _outputs.isEmpty(); } @@ -283,6 +283,7 @@ public abstract class Node implements Cloneable { n.unlock(); int idx = n._inputs.find(this); n._inputs.set(idx,nnn); + n.moveDepsToWorklist(); // Rewiring can change a dependent query without changing type. nnn.addUse(n); CODE.addAll(n._outputs); } @@ -473,6 +474,14 @@ public abstract class Node implements Cloneable { * 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( CODE._midAssert ) return this; @@ -481,8 +490,8 @@ public abstract class Node implements Cloneable { if( dep == null ) return this; if( _deps==null ) _deps = new Ary<>(Node.class); if( _deps .find(dep) != -1 ) return this; // Already on list - if( _inputs .find(dep) != -1 ) return this; // No need for deps on immediate neighbors - if( _outputs.find(dep) != -1 ) return this; + if( !forwards && _inputs .find(dep) != -1 ) return this; // No need for deps on immediate neighbors + if( !forwards && _outputs.find(dep) != -1 ) return this; _deps.add(dep); return this; } diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/PhiNode.java index add811d6..74fa11ab 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -24,6 +24,10 @@ public class PhiNode extends Node { addDef(sample); } + 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_"+MemOpNode.mlabel(_label); } @Override @@ -73,33 +77,9 @@ public class PhiNode extends Node { if (live != null) return live; - // No bother if region is going to fold dead paths soon - for( int i=1; i<nIns(); i++ ) - if( r.in(i)._type == Type.XCONTROL ) - return null; - - // 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 && same_op() ) { - assert !(op instanceof CFGNode); - 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(); - Node down = op.copy(phi_lhs,phi_rhs); - // Test not running backwards, which can happen for e.g. And's - if( down.compute().isa(compute()) ) - return down; - down.kill(); - } + // Phi(op(a,b,...),op(c,d,...)) -> op(Phi(a,c),Phi(b,d),...). + Node down; + if( same_op() && (down=drop_same_op())!=null ) return down; // If merging Phi(N, cast(N)) - we are losing the cast JOIN effects, so just remove. if( nIns()==3 ) { @@ -131,12 +111,72 @@ public class PhiNode extends Node { } private boolean same_op() { - for( int i=2; i<nIns(); i++ ) - if( in(1).getClass() != in(i).getClass() ) - return false; + Node op = in(1); + if( op instanceof CFGNode || op instanceof ConstantNode || op instanceof PhiNode || + op instanceof ProjNode || op instanceof NewNode || op instanceof ScopeNode || op instanceof MemMergeNode ) return false; + // A bulk Phi must split its aliases before factoring precise Stores. + if( this instanceof BulkMemPhiNode ) return false; + // Bulk splitting currently identifies parallel slices by Region/alias. + // Factoring can introduce another slice at a different memory point; + // wait until that Region's bulk partitioning has finished. + if( op instanceof MemOpNode ) + for( Node use : region()._outputs ) + if( use instanceof BulkMemPhiNode ) { + use.addDepForwards(this); + return false; + } + Node busy = null; + for( int i=1; i<nIns(); i++ ) { + Node n = in(i); + if( region().in(i).addDep(this)._type==Type.XCONTROL || + op.getClass()!=n.getClass() || n.nIns()!=op.nIns() || n.in(0)!=null || !op.eq(n) ) return false; + if( n instanceof MemOpNode mem && !mem.canDrop((MemOpNode)op,this) ) return false; + // Moving a Store must remove the old effect, not duplicate it. + // Allow one shared arm: the other arms disappear into the single + // factored operation, usually reducing the total operation count. + n.addDepForwards(this); + if( n.nOuts()>1 ) { + for( Node use : n._outputs ) + if( use!=null && use!=this ) use.addDepForwards(this); + if( n instanceof StoreNode || busy!=null ) return false; + busy=n; + } + for( int j=1; j<n.nIns(); j++ ) + if( n.in(j) instanceof ScopeNode || ((n.in(j)==null) != (op.in(j)==null)) ) return false; + } return true; } + private Node drop_same_op() { + Node op = in(1), cp = op.copyEmpty(); + cp._type = null; + cp.addDef(null); + for( int j=1; j<op.nIns(); j++ ) { + Node x = op.in(j); + boolean different = false; + for( int i=2; i<nIns(); i++ ) + if( in(i).in(j)!=x ) different=true; + if( different ) { + Node[] ins = new Node[nIns()]; + ins[0] = region(); + Type t = Type.TOP; + for( int i=1; i<nIns(); i++ ) { + ins[i] = in(i).in(j); + t = t.meet(ins[i]._type); + } + PhiNode phi = j==1 && op instanceof MemOpNode mem + ? new MemPhiNode(_label,mem._alias,ins) + : PhiNode.make(_label,t.glb(),ins); + x = phi.peephole(); + } + cp.addDef(x); + } + // Factoring must not widen the result (e.g. correlated And operands). + if( cp.compute().isa(compute()) ) return cp; + cp.kill(); + return null; + } + /** * If only single unique input, return it */ diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/RegionNode.java index 643b5f65..933bd1af 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/RegionNode.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/RegionNode.java @@ -47,7 +47,7 @@ public class RegionNode extends CFGNode { if( !hasPhi() && // No Phi users, just a control user in(1) instanceof CProjNode p1 && in(2) instanceof CProjNode p2 && - p1.in(0).addDep(this)==p2.in(0).addDep(this) && + p1.addDepForwards(this).in(0)==p2.addDepForwards(this).in(0) && p1.in(0) instanceof IfNode iff ) { // Replace with the iff.ctrl directly if( nIns()==3 ) return iff.ctrl(); diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/ReturnNode.java index 4ca8131a..f9f51b40 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -6,15 +6,7 @@ import com.seaofnodes.simple.Utils; 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 all preceding memory effects and the return value alive: {ctrl, $mem, value, rpc}. */ public class ReturnNode extends CFGNode { public FunNode _fun; @@ -48,7 +40,7 @@ public class ReturnNode extends CFGNode { public Type compute() { if( inProgress () ) return TypeTuple.RET; // In progress if( _fun.isDead() ) return TypeTuple.RET.dual(); // Dead another way - return TypeTuple.make(ctrl()._type,mem()._type,expr()._type); + return TypeTuple.make(ctrl()._type,TypeMem.BOT,expr()._type); } @Override public Node idealize() { diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/ScopeNode.java index e0d97f39..de7ccdf3 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -6,9 +6,9 @@ import java.util.*; /** * The Scope node is purely a parser helper - it tracks names to nodes with a - * stack of variables. + * stack of variable records. */ -public class ScopeNode extends MemMergeNode { +public class ScopeNode extends Node { /** * The control is a name that binds to the currently active control @@ -18,6 +18,11 @@ public class ScopeNode extends MemMergeNode { public static final String ARG0 = "arg"; public static final String MEM0 = "$mem"; + public Node in(Var v) { return in(v._idx); } + + @Override public Type compute() { return Type.BOTTOM; } + @Override public Node idealize() { return null; } + // All active/live variables in all nested scopes, all run together public final Ary<Var> _vars; @@ -33,7 +38,7 @@ public class ScopeNode extends MemMergeNode { // A new ScopeNode public ScopeNode() { - super(true); + _type = Type.BOTTOM; _vars = new Ary<>(Var .class); _lexSize= new Ary<>(Integer.class); _kinds = new Ary<>(Kind .class); @@ -69,7 +74,7 @@ public class ScopeNode extends MemMergeNode { public Node ctrl() { return in(0); } - public MemMergeNode mem() { return (MemMergeNode)in(1); } + public Node mem() { return in(lookup(MEM0)); } /** * The ctrl of a ScopeNode is always bound to the currently active @@ -82,7 +87,7 @@ public class ScopeNode extends MemMergeNode { * @return Node that was bound */ public <N extends Node> N ctrl(N n) { return setDef(0,n); } - public Node mem(Node n) { return setDef(1,n); } + public Node mem(Node n) { update(MEM0,n); return n; } public void push(Kind kind) { assert _lexSize._len==_kinds._len; @@ -165,12 +170,6 @@ public class ScopeNode extends MemMergeNode { return true; } - // Read from memory - public Node mem( int alias ) { return mem()._mem(alias,null); } - // Write to memory - public void mem( int alias, Node st ) { mem()._mem(alias,st); } - - /** * Lookup a name in all scopes starting from most deeply nested. * @@ -206,7 +205,7 @@ public class ScopeNode extends MemMergeNode { // 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(v._idx,new PhiNode(v._name, v.lazyGLB(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).peephole()); + : loop.setDef(v._idx,PhiNode.make(v._name, v.lazyGLB(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).peephole()); setDef(v._idx,old); } assert !v._final || st==null; @@ -245,20 +244,8 @@ public class ScopeNode extends MemMergeNode { n.keep(); dup.addDef(ctrl()); // Control input is just copied - // Memory input is a shallow copy - MemMergeNode memdup = new MemMergeNode(true), mem = mem(); - memdup.addDef(null); - memdup.addDef(loop ? this : mem.in(1)); - for( int i=2; i<mem.nIns(); i++ ) - // For lazy phis on loops we use a sentinel - // that will trigger phi creation on update - memdup.addDef(loop ? this : mem.in(i)); - dup.addDef(memdup); - - // Copy of other inputs - for( int i=2; i<nIns(); i++ ) - // For lazy phis on loops we use a sentinel - // that will trigger phi creation on update + // Memory is one binding, with the same lazy loop Phi as scalar names. + for( int i=1; i<nIns(); i++ ) dup.addDef(loop && !_vars.at(i)._final ? this : in(i)); return dup; } @@ -273,22 +260,21 @@ public class ScopeNode extends MemMergeNode { */ public RegionNode mergeScopes(ScopeNode that, Parser.Lexer loc) { RegionNode r = ctrl(new RegionNode(loc,null,ctrl(), that.ctrl()).keep()); - mem()._merge(that.mem(),r); - this ._merge(that ,r); + _merge(that,r); that.kill(); // Kill merged scope CodeGen.CODE.add(r); return r.unkeep(); } private void _merge(ScopeNode that, RegionNode r) { - for( int i = 2; i < nIns(); i++) + 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 Var v = _vars.at(i); Node lhs = this.in(this.update(v,null)); Node rhs = that.in(that.update(v,null)); - setDef(i, new PhiNode(v._name, v.type(), r, lhs, rhs).peephole()); + setDef(i, PhiNode.make(v._name, v.type(), r, lhs, rhs).peephole()); } } @@ -313,21 +299,15 @@ public class ScopeNode extends MemMergeNode { assert ctrl instanceof LoopNode loop && loop.inProgress(); ctrl.setDef(2,back.ctrl()); - mem()._endLoopMem( this, back.mem(), exit.mem() ); - this ._endLoop ( this, back , exit ); + _endLoop(this,back,exit); back.kill(); // Loop backedge is dead // Now one-time do a useless-phi removal - mem()._useless(); - this ._useless(); - - // The exit mem's lazy default value had been the loop top, - // now it goes back to predating the loop. - exit.mem().setDef(1,mem().in(1)); + _useless(); } // Fill in the backedge of any inserted Phis void _endLoop( ScopeNode scope, Node back, Node exit ) { - for( int i=2; i<nIns(); i++ ) { + for( int i=1; i<nIns(); i++ ) { if( _vars.at(i)._final ) continue; // Final vars did not get modified in the loop if( _vars.at(i).type().isHighOrConst() ) continue; // Cannot lift higher than a constant, so no Phi if( back.in(i) != scope ) { @@ -341,6 +321,23 @@ public class ScopeNode extends MemMergeNode { } + // Now one-time do a useless-phi removal + void _useless( ) { + for( int i=1; i<nIns(); i++ ) { + if( in(i) instanceof PhiNode phi ) { + // Do an eager useless-phi removal + Node in = phi.peephole(); + CodeGen.CODE.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. public void addGuards( Node ctrl, Node pred, boolean invert ) { diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/StartNode.java index 26e08535..630e3bf6 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -3,18 +3,11 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.CodeGen; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.type.*; -import java.util.Arrays; import java.util.BitSet; import java.util.HashSet; import static com.seaofnodes.simple.Utils.TODO; -/** - * 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. - */ +/** Start supplies control, whole memory, and the argument, in that order. */ public class StartNode extends LoopNode implements MultiNode { final Type _arg; @@ -46,7 +39,7 @@ public class StartNode extends LoopNode implements MultiNode { @Override public TypeTuple compute() { - return TypeTuple.make(Type.CONTROL,TypeMem.TOP,_arg); + return TypeTuple.make(Type.CONTROL,TypeMem.BOT,_arg); } @Override public Node idealize() { return null; } diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/StoreNode.java index cb374c58..a909399f 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -25,6 +25,10 @@ public class StoreNode extends MemOpNode { } // Debugger label + @Override boolean canDrop(MemOpNode other, Node dep) { + return super.canDrop(other,dep) && _init==((StoreNode)other)._init; + } + @Override public String label() { return "st_"+mlabel(); } @Override public boolean isMem() { return true; } @@ -38,28 +42,25 @@ public class StoreNode extends MemOpNode { @Override public Type compute() { Type val = val()._type; - TypeMem mem = (TypeMem)mem()._type; // Invariant - if( mem == TypeMem.TOP ) return TypeMem.TOP; - Type t = Type.BOTTOM; // No idea on field contents - // Same alias, lift val to the declared type and then meet into other fields - if( mem._alias == _alias ) { - // Update declared forward ref to the actual - if( _declaredType.isFRef() && val instanceof TypeMemPtr tmp && !tmp.isFRef() ) - _declaredType = tmp; - val = val.join(_declaredType); - t = val.meet(mem._t); - } + if( _declaredType.isFRef() && val instanceof TypeMemPtr tmp && !tmp.isFRef() ) + _declaredType = tmp; + Type t = val.join(_declaredType).meet(MemMergeNode.contents(mem(),_alias,this)); return TypeMem.make(_alias,t); } @Override public Node idealize() { + if( mem() instanceof MemMergeNode merge ) { + setDef(1,CodeGen.CODE.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 - off()==st.off() && // And same offset (could be "same alias" but this handles arrays to same index) + ptr()==st.ptr() && _alias==st._alias && // Must check same object + off()==st.off() && // And same offset 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/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java b/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java index 092143d6..e8134f03 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java @@ -16,6 +16,8 @@ public class MemOpRISC extends MemOpNode implements MachNode{ } @Override public StringBuilder _printMach(StringBuilder sb, BitSet visited) { return sb.append(".").append(_name); } + @Override public boolean isMem() { return _type instanceof TypeMem; } + @Override public String label() { return op(); } @Override public Type compute() { throw Utils.TODO(); } @Override public Node idealize() { throw Utils.TODO(); } diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java b/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java index f050715d..2b8ec4f2 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java @@ -15,8 +15,8 @@ public class NewRISC extends NewNode implements MachNode{ // Register mask allowed on input i, the size @Override public RegMask regmap(int i) { // Size - if( i==1 ) return riscv.RET_MASK; - // All the memory alias edges + if( i==2 ) return riscv.RET_MASK; + // Memory has no register return null; } @@ -25,8 +25,8 @@ public class NewRISC extends NewNode implements MachNode{ // Register mask allowed as a result. Pointer result in standard calling // convention. @Override public RegMask outregmap(int i) { - if( i == 1 ) return riscv.RET_MASK; - // All the memory aliases edges + if( i == 0 ) return riscv.RET_MASK; + // Memory has no register return null; } diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java b/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java index 9fbd7c12..1bd40d0c 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java @@ -20,7 +20,7 @@ public class StoreRISC extends MemOpRISC { } @Override public RegMask regmap(int i) { - return riscv.RMASK; + return i>=2 && i<=4 ? riscv.RMASK : null; } diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java index 399eebca..81b31c6a 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java @@ -187,6 +187,8 @@ public class riscv extends Machine{ case NewNode nnn -> new NewRISC(nnn); case OrNode or -> or(or); case ParmNode parm -> new ParmRISC(parm); + case BulkMemPhiNode phi -> new BulkMemPhiNode(phi); + case MemPhiNode phi -> new MemPhiNode(phi); case PhiNode phi -> new PhiNode(phi); case ProjNode prj -> prj(prj); case ReadOnlyNode read -> new ReadOnlyNode(read); diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java b/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java index b94ea5ae..ab40571f 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java @@ -53,6 +53,8 @@ public abstract class MemOpX86 extends MemOpNode implements MachNode { @Override public StringBuilder _printMach(StringBuilder sb, BitSet visited) { return sb.append(".").append(_name); } + @Override public boolean isMem() { return _type instanceof TypeMem; } + @Override public String label() { return op(); } @Override public Type compute() { throw Utils.TODO(); } @Override public Node idealize() { throw Utils.TODO(); } @@ -63,7 +65,7 @@ public abstract class MemOpX86 extends MemOpNode implements MachNode { if( i==2 ) return x86_64_v2.RMASK; // base if( i==3 ) return x86_64_v2.RMASK; // index if( i==4 ) return x86_64_v2.RMASK; // value - throw Utils.TODO(); + return null; // Trailing anti-dependence edges } // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { throw Utils.TODO(); } diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java b/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java index f9778845..c70e1c54 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java @@ -12,8 +12,8 @@ public class NewX86 extends NewNode implements MachNode { // Register mask allowed on input i, the size @Override public RegMask regmap(int i) { // Size - if( i==1 ) return x86_64_v2.RDI_MASK; - // All the memory alias edges + if( i==2 ) return x86_64_v2.RDI_MASK; + // Memory has no register return null; } @Override public RegMask outregmap() { throw Utils.TODO(); } @@ -21,8 +21,8 @@ public class NewX86 extends NewNode implements MachNode { // Register mask allowed as a result. Pointer result in standard calling // convention. @Override public RegMask outregmap(int i) { - if( i == 1 ) return x86_64_v2.RET_MASK; - // All the memory aliases edges + if( i == 0 ) return x86_64_v2.RET_MASK; + // Memory has no register return null; } diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java index ddb2dcd6..523d0e34 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java @@ -190,6 +190,8 @@ public class x86_64_v2 extends Machine { case NewNode nnn -> new NewX86(nnn); case OrNode or -> or(or); case ParmNode parm -> new ParmX86(parm); + case BulkMemPhiNode phi -> new BulkMemPhiNode(phi); + case MemPhiNode phi -> new MemPhiNode(phi); case PhiNode phi -> new PhiNode(phi); case ProjNode prj -> prj(prj); case ReadOnlyNode read -> new ReadOnlyNode(read); @@ -220,7 +222,7 @@ public class x86_64_v2 extends Machine { return new AddMemX86(add,address(ld),ld.ptr(),idx,off,scale, imm(rhs),val); if( rhs instanceof LoadNode ld && ld.nOuts()==1 ) - throw Utils.TODO(); // Swap load sides + return new AddMemX86(add,address(ld),ld.ptr(),idx,off,scale, imm(lhs),val); // Attempt a full LEA-style break down. // Returns one of AddX86, AddIX86, LeaX86, or LHS diff --git a/chapter19/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter19/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index 41725fcf..fcb1515f 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -67,7 +67,7 @@ public class SimpleGraphAdapter extends GraphAdapter<Node> { return n instanceof CFGNode ? 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( n instanceof MemMergeNode || i == 1 && (n instanceof MemOpNode || n instanceof ReturnNode || n instanceof CallNode) ) return Role.MEM; + if( n instanceof MemMergeNode || (i == 1 && (n instanceof MemOpNode || n instanceof ReturnNode || n instanceof CallNode || n instanceof NewNode)) ) return Role.MEM; Node def = n.in(i); if( def != null && isMem(def) ) return Role.MEM; return Role.DATA; diff --git a/chapter19/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter19/src/test/java/com/seaofnodes/simple/Chapter10Test.java index daeea36d..d0d33663 100644 --- a/chapter19/src/test/java/com/seaofnodes/simple/Chapter10Test.java +++ b/chapter19/src/test/java/com/seaofnodes/simple/Chapter10Test.java @@ -1,6 +1,8 @@ package com.seaofnodes.simple; import org.junit.Test; +import com.seaofnodes.simple.node.*; +import java.util.BitSet; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -449,4 +451,121 @@ return 0; assertEquals("return 0;", code.print()); } + @Test public void testReadBeforeStores() { + var code = new CodeGen(""" + 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().opto(); + StopNode stop = code._stop; + assertEquals("1133",Eval2.eval(code,0)); + assertEquals("2244",Eval2.eval(code,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 code = new CodeGen(NESTED_MEMORY).parse().opto(); + StopNode stop = code._stop; + for (int arg=0; arg<7; arg++) { + assertEquals("S{x="+(5L+arg)+",y="+(7L+arg*(arg+1)-(arg>=2 ? 2 : 0))+ + ",z="+(11L+2*arg-(arg>=2 ? 2 : 0))+"}",Eval2.eval(code,arg)); + } + } + + @Test public void testMemoryAtEarlyReturns() { + var code = new CodeGen(""" + 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().opto(); + StopNode stop = code._stop; + for (int arg=0; arg<2; arg++) { + assertEquals(arg==0 ? "S{x=3,y=17,z=7}" : "S{x=11,y=5,z=13}",Eval2.eval(code,arg)); + } + } + + @Test public void testKeepLoadsAtMerge() { + var code = new CodeGen(""" + struct S { int x; }; + S !a = new S; S !b = new S; + a.x = arg; b.x = arg+1; + S !p = a; S !q = b; + if (arg<0) { p=b; q=a; } + int v; + if (arg>1) v=p.x; else v=q.x; + return v; + """).parse().opto(); + StopNode stop = code._stop; + // The one-step safety check stops at the memory aggregate. + assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet())); + assertEquals("-1",Eval2.eval(code,-1)); + assertEquals("1",Eval2.eval(code,0)); + assertEquals("3",Eval2.eval(code,3)); + } + + @Test public void testDropStores() { + var code = new CodeGen(""" + struct S { int x; }; + S !s = new S; + if (arg) s.x=arg+1; else s.x=arg+2; + return s; + """).parse().opto(); + StopNode stop = code._stop; + assertEquals(1,countMemoryNodes(stop,StoreNode.class,new BitSet())); + assertEquals("S{x=2}",Eval2.eval(code,0)); + assertEquals("S{x=4}",Eval2.eval(code,3)); + } + + @Test public void testKeepReadsBeforeWrites() { + var code = new CodeGen(""" + struct S { int x; int y; }; + S !a = new S; S !b = new S; + a.x=arg; b.x=arg+1; + S !p=a; S !q=b; + if (arg<0) { p=b; q=a; } + int v; + if (arg>1) { v=p.x; p.x=41; p.y=5; } + else { v=q.x; q.x=42; } + return v; + """).parse().opto(); + StopNode stop = code._stop; + assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet())); + assertEquals("-1",Eval2.eval(code,-1)); + assertEquals("1",Eval2.eval(code,0)); + assertEquals("3",Eval2.eval(code,3)); + } + + private static int countMemoryNodes(Node n, Class<?> kind, BitSet seen) { + if (n==null || seen.get(n._nid)) return 0; + seen.set(n._nid); + int cnt=kind.isInstance(n) ? 1 : 0; + for (Node def : n._inputs) cnt+=countMemoryNodes(def,kind,seen); + return cnt; + } } diff --git a/chapter19/src/test/java/com/seaofnodes/simple/Chapter10bTest.java b/chapter19/src/test/java/com/seaofnodes/simple/Chapter10bTest.java new file mode 100644 index 00000000..abf6c3cf --- /dev/null +++ b/chapter19/src/test/java/com/seaofnodes/simple/Chapter10bTest.java @@ -0,0 +1,59 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.node.*; +import org.junit.Test; +import static org.junit.Assert.*; + +public class Chapter10bTest { + @Test public void testIndependentField() { + var code = new CodeGen(""" + 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().opto(); + var stop = code._stop; + assertEquals("return 42;",stop.toString()); + assertEquals("42",Eval2.eval(code,5)); + assertNull(stop.walk(n -> n instanceof LoadNode ? n : null)); + } + + @Test public void testParallelMemoryPhis() { + var code = new CodeGen(""" + 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().opto(); + var stop = code._stop; + 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) && !(n instanceof ParmNode)); + return null; + }); + assertTrue(phis[0]>=3); + assertEquals("S{x=1,y=2,z=3}",Eval2.eval(code,4)); + } + + @Test public void testLateSliceWorklist() { + // 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 code = new CodeGen(Chapter10Test.NESTED_MEMORY,com.seaofnodes.simple.type.TypeInteger.BOT,seed).parse(); + code.opto(); + assertEquals("S{x=9,y=25,z=17}",Eval2.eval(code,4)); + } + } +} diff --git a/chapter19/src/test/java/com/seaofnodes/simple/Chapter14Test.java b/chapter19/src/test/java/com/seaofnodes/simple/Chapter14Test.java index 17708c37..a3025001 100644 --- a/chapter19/src/test/java/com/seaofnodes/simple/Chapter14Test.java +++ b/chapter19/src/test/java/com/seaofnodes/simple/Chapter14Test.java @@ -174,7 +174,7 @@ while(v1) break; return v1; """); code.parse().opto(); - assertEquals("return Phi(Loop,(((1<<arg)&1)&Shl),0);", code.print()); + assertEquals("return (Phi(Loop,((1<<arg)&1),0)&Phi(Loop,Shl,4294967295));", code.print()); assertEquals("1", Eval2.eval(code, 0)); } diff --git a/chapter19/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/chapter19/src/test/java/com/seaofnodes/simple/Chapter16Test.java index 83d34b2e..3dd37f33 100644 --- a/chapter19/src/test/java/com/seaofnodes/simple/Chapter16Test.java +++ b/chapter19/src/test/java/com/seaofnodes/simple/Chapter16Test.java @@ -254,4 +254,27 @@ return new Square; assertEquals("Square{side=3.0,diag=1.7320508075688772}", Eval2.eval(code, 3)); assertEquals("Square{side=4.0,diag=2.0}", Eval2.eval(code, 4)); } + static final String CONSTRUCTOR_MEMORY = """ + struct S { int x; int y; }; + struct T { int z=arg+40; }; + T !t = new T; + S !a = new S { x=11; y=7; }; + S !b = new S { x=22; y=9; }; + S !p=a; + if (arg) p=b; + int before=p.x; + S !c = new S { + x=p.x+1; + { int i=0; while (i<2) { p.y=p.y+1; i=i+1; } } + y=p.y; + }; + p.x=33; + return before*10000+c.x*100+c.y+t.z; + """; + + @Test public void testConstructorMemory() { + var code = new CodeGen(CONSTRUCTOR_MEMORY).parse().opto(); + assertEquals("111249",Eval2.eval(code,0)); + assertEquals("222352",Eval2.eval(code,1)); + } } diff --git a/chapter19/src/test/java/com/seaofnodes/simple/Chapter18Test.java b/chapter19/src/test/java/com/seaofnodes/simple/Chapter18Test.java index a70ffd5f..81be3379 100644 --- a/chapter19/src/test/java/com/seaofnodes/simple/Chapter18Test.java +++ b/chapter19/src/test/java/com/seaofnodes/simple/Chapter18Test.java @@ -445,4 +445,70 @@ return p.coffee_count; assertEquals("1", Eval2.eval(code, 2)); } + static final String CALL_MEMORY = """ + struct S { int x; int y; }; + val bump = { S s, int d -> int old=s.x; s.x=old+d; return old; }; + S !a = new S { x=10; y=7; }; + S !b = new S { x=20; y=9; }; + S !p=a; if (arg) p=b; + int before=p.x; + int old=bump(p,3); + int after=p.x; + bump(a,5); + return before*1000000+old*10000+after*100+a.x+a.y; + """; + + @Test public void testCallMemory() { + var code = new CodeGen(CALL_MEMORY).parse().opto(); + int[] calls={0}; + code._stop.walk(n -> { if(n instanceof CallNode) calls[0]++; return null; }); + assertEquals(2,calls[0]); // Keep real calls so this checks scheduling across them. + assertEquals("10101325",Eval2.eval(code,0)); + assertEquals("20202322",Eval2.eval(code,1)); + code._stop.walk(n -> { + if(n instanceof CallNode call) { + assertEquals(5,call.nIns()); // No scheduling edges may become extra arguments. + assertTrue(call.fptr()._type instanceof TypeFunPtr); + } + return null; + }); + } + + static final String RECURSIVE_MEMORY = """ + struct S { int x; }; + val rec = { S s, int n -> + if (n==0) return s.x; + int before=s.x; + s.x=before+1; + return before+rec(s,n-1); + }; + S !s=new S { x=10; }; + int value=rec(s,arg); + return value*100+s.x; + """; + + @Test public void testRecursiveMemory() { + var code = new CodeGen(RECURSIVE_MEMORY).parse().opto(); + for(int n=0; n<7; n++) + assertEquals(Long.toString((10L*(n+1)+n*(n+1)/2)*100+10+n),Eval2.eval(code,n)); + } + + static final String INLINE_MEMORY = """ + struct S { int x; int y; }; + val make = { S s -> + S t = new S { x=s.x+1; y=3; }; + s.y=9; + return t; + }; + S !a = new S { x=arg+40; y=7; }; + S t=make(a); + return a.x*10000+a.y*100+t.x+t.y; + """; + + @Test public void testInlineAllocationMemory() { + var code = new CodeGen(INLINE_MEMORY).parse().opto(); + assertNull(code._stop.walk(n -> n instanceof CallNode ? n : null)); + assertEquals("400944",Eval2.eval(code,0)); + assertEquals("420946",Eval2.eval(code,2)); + } } diff --git a/chapter19/src/test/java/com/seaofnodes/simple/Chapter19Test.java b/chapter19/src/test/java/com/seaofnodes/simple/Chapter19Test.java index eef9f460..dc8fa309 100644 --- a/chapter19/src/test/java/com/seaofnodes/simple/Chapter19Test.java +++ b/chapter19/src/test/java/com/seaofnodes/simple/Chapter19Test.java @@ -450,4 +450,71 @@ return sq(arg) + sq(3); code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched(); assertEquals("Stop[ return (add,sq(),sq()); return (mul,Parm_x(sq,int,3,arg),x); ]", code.print()); } + + @Test public void testSelectedMemory() { + for( String cpu : new String[]{"x86_64_v2","riscv"} ) + for( String src : new String[]{Chapter10Test.NESTED_MEMORY,Chapter16Test.CONSTRUCTOR_MEMORY, + Chapter18Test.CALL_MEMORY,Chapter18Test.RECURSIVE_MEMORY} ) { + var code = new CodeGen(src).parse().opto().typeCheck(); + var aliases = memoryPhis(code._stop); + code.instSelect(cpu,"SystemV"); + assertEquals(aliases,memoryPhis(code._stop)); + code.GCM().localSched(); + code._stop.walk(n -> { + if( !(n instanceof CFGNode) && !(n instanceof ProjNode) ) + assertTrue(n.in(0) instanceof CFGNode); + if( n instanceof NewNode nn ) { + assertEquals(2,((TypeTuple)nn._type)._types.length); + assertTrue(nn.mem() instanceof MemMergeNode); + assertNull(nn.mem().in(1)); // Partial allocation input. + MachNode mach = (MachNode)nn; + assertNull(mach.regmap(1)); + assertNotNull(mach.regmap(2)); + assertNotNull(mach.outregmap(0)); + assertNull(mach.outregmap(1)); + } + if( n instanceof CallNode call ) { + assertEquals(4,call.nIns()); // ctrl, memory, two args; direct target is embedded. + } + return null; + }); + } + } + + private static java.util.ArrayList<String> memoryPhis(Node stop) { + var phis = new java.util.ArrayList<String>(); + stop.walk(n -> { + if( n instanceof MemPhiNode phi ) phis.add("alias:"+phi._alias); + if( n instanceof BulkMemPhiNode phi ) phis.add("bulk:"+phi._aliases); + assertFalse(n instanceof PhiNode && n.isMem() && + !(n instanceof MemPhiNode) && !(n instanceof BulkMemPhiNode) && !(n instanceof ParmNode)); + return null; + }); + java.util.Collections.sort(phis); + return phis; + } + + @Test public void testFoldedReadBeforeWrites() { + var code = new CodeGen(""" + struct S { int x; }; + S !a = new S { x=11; }; S !b = new S { x=22; }; + S !p = a; if( arg ) p = b; + int before = p.x + arg; + a.x=33; b.x=44; + return before; + """).parse().opto().typeCheck().instSelect("x86_64_v2","SystemV").GCM().localSched(); + var read = (MemOpNode)code._stop.walk(n -> + n instanceof com.seaofnodes.simple.node.cpus.x86_64_v2.AddMemX86 ? n : null); + assertNotNull(read); + int writes = 0; + for( Node use : read.antiDeps() ) + if( use instanceof MemOpNode st && st.isMem() ) { + assertSame(read.cfg0(),st.cfg0()); + assertTrue(st._inputs.find(read)>=0); + assertTrue(read.cfg0()._outputs.find(read)<st.cfg0()._outputs.find(st)); + assertNull(((MachNode)st).regmap(st._inputs.find(read))); + writes++; + } + assertEquals(1,writes); // The next Store follows this first clobber through memory. + } } diff --git a/chapter19/src/test/java/com/seaofnodes/simple/Eval2.java b/chapter19/src/test/java/com/seaofnodes/simple/Eval2.java index 24761945..4837fcda 100644 --- a/chapter19/src/test/java/com/seaofnodes/simple/Eval2.java +++ b/chapter19/src/test/java/com/seaofnodes/simple/Eval2.java @@ -327,7 +327,7 @@ public abstract class Eval2 { private static Object alloc(NewNode alloc) { TypeStruct type = alloc._ptr._obj; if( type.isAry() ) { - long sz = (Long)val(alloc.in(1)); + long sz = (Long)val(alloc.size()); long x = offToIdx(sz, type); int n = (int)x; if( n!=x || n<0 ) @@ -440,9 +440,9 @@ public abstract class Eval2 { case TypeRPC rpc -> sb.p(x.toString()); case TypeMem mem -> sb.p("$mem"); case TypeTuple tt -> { - if( tt._types.length>1 && tt._types[1] instanceof TypeMemPtr ) + if( tt._types.length>1 && tt._types[0] instanceof TypeMemPtr ) // Assume a NewNode - yield _print( tt._types[1], x, sb, visit ); + yield _print( tt._types[0], x, sb, visit ); throw Utils.TODO(); } case Type tt -> { diff --git a/chapter19/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/chapter19/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java index a3b87e90..3f023bb2 100644 --- a/chapter19/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java +++ b/chapter19/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java @@ -185,7 +185,7 @@ public class Evaluator { Object[] body=null; int num; if( type.isAry() ) { - long sz = (Long)val(alloc.in(1)); + long sz = (Long)val(alloc.size()); long n = offToIdx(sz, type); if( n < 0 ) throw new NegativeArraySizeException(""+n); @@ -199,21 +199,17 @@ public class Evaluator { assert elem instanceof TypeMemPtr; } // Length value - body[0] = vall(alloc.in(2+2)); + body[0] = n; } else { body = new Object[num = type._fields.length]; for( int i=0; i<num; i++ ) body[i] = switch( alloc._ptr._obj._fields[i]._type ) { case TypeInteger ti -> 0L; - case TypeFloat tf -> 0; + case TypeFloat tf -> 0D; default -> null; }; } - Object[] mems = new Object[type._fields.length+2]; - // mems[0] is control - mems[1] = new Obj(type,body); // the ref - // mems[2+...] are memory aliases - return mems; + return new Object[]{new Obj(type,body),null}; } private Object load(LoadNode load) { diff --git a/chapter20/README.md b/chapter20/README.md index e463831a..271653c6 100644 --- a/chapter20/README.md +++ b/chapter20/README.md @@ -152,6 +152,11 @@ represent mutable bitsets with the allocator routinely masking off bits when accumulating a set of constraints. To help keep these uses apart, the `RegMask` class includes mutable and immutable variants. +#### Memory + +Memory edges exist to enforce ordering, but do not get any registers. +The allocator basically ignores them. + ### Callee/Caller Save Registers @@ -505,21 +510,41 @@ checks, not additional program benchmarks. | Program-test cohort | Allocator chapter | Compilations | Split moves | Loop-weighted moves | |---|---:|---:|---:|---:| -| Chapter 20 | 20 | 39 | 238 | 357 | +| Chapter 20 | 20 | 39 | 234 | 360 | `_spills` counts surviving split moves, including register-to-register copies; it does not count only stack stores. `_spillScaled` weights each move by `8^loopDepth`, a rough cost estimate rather than measured execution frequency. -The weighted subtotals are 154 for x86-64, 101 for RISC-V, and 102 for ARM. +The weighted subtotals are 161 for x86-64, 100 for RISC-V, and 99 for ARM. ### Comments on the measurements Before the correctness fixes, this cohort had 235 moves / 354 weighted moves. -The final corrected baseline costs three more of each. The final narrow x86 +The corrected baseline before lazy memory cost three more of each. The final narrow x86 store-mask backport accounts for two of those moves: an integer byte/short store cannot consume an XMM register. This is a correctness cost, not a quality gain. The fixed-neighbor color-bias correction changes no counts in this cohort. +With lazy memory and selected-read ordering, the same 39 compilations have +234 moves / 360 weighted moves, versus 238 / 357 before the port. Register +legality passes on every target; allocator heuristics are unchanged. The changed +programs are: + +| Program | Target | Moves before / after | Weighted before / after | +|---|---|---:|---:| +| Array access (`testAlloc2`) | x86-64 | 1 / 2 | 1 / 2 | +| Array prefix sum (`testArray1`) | x86-64 | 5 / 6 | 5 / 13 | +| String hash | x86-64 | 7 / 5 | 21 / 19 | +| String hash | RISC-V | 4 / 3 | 4 / 3 | +| String hash | ARM | 5 / 3 | 5 / 3 | +| Merge sort | ARM | 37 / 36 | 37 / 36 | + +The prefix sum's extra loop move follows from scheduling selected reads before +clobbering writes. A diagnostic build omitting that ordering returns to 5 / 5; +it is not a valid alternative schedule. These are changed frontend and scheduling +graphs, not a comparison of allocator heuristics. The new memory-allocation +regressions check legality but do not add entries to this measured cohort. + Later chapters will add one quality technique at a time and end with this kind of table: Chapter 21 will measure both the Chapter 20 and Chapter 21 program cohorts using its allocator, and Chapter 25 will have rows for cohorts 20-25. diff --git a/chapter20/src/main/java/com/seaofnodes/simple/Parser.java b/chapter20/src/main/java/com/seaofnodes/simple/Parser.java index af047850..16018fba 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/Parser.java @@ -138,7 +138,7 @@ public class Parser { _scope.define(ScopeNode.ARG0, TypeInteger.BOT, false, null, _lexer); ctrl(XCTRL); - _scope.mem(new MemMergeNode(false)); + _scope.mem(con(TypeMem.BOT)); // Parse whole program, as-if function header "{ int arg -> body }" parseFunctionBody(_code._main,loc(),"arg"); @@ -178,7 +178,7 @@ public class Parser { // Build a multi-exit return point for all function returns RegionNode r = new RegionNode((Lexer)null,null,null).init(); assert r.inProgress(); - PhiNode rmem = new PhiNode(ScopeNode.MEM0,TypeMem.BOT,r,null).init(); + PhiNode rmem = new BulkMemPhiNode(ScopeNode.MEM0,r,null).init(); PhiNode rrez = new PhiNode(ScopeNode.ARG0,Type.BOTTOM,r,null).init(); ReturnNode ret = new ReturnNode(r, rmem, rrez, rpc, fun).init(); fun.setRet(ret); @@ -190,11 +190,8 @@ public class Parser { // external calls. _scope.push(ScopeNode.Kind.Function); ctrl(fun); // Scope control from function - // Private mem alias tracking per function - MemMergeNode mem = new MemMergeNode(true); - mem.addDef(null); // Alias#0 - mem.addDef(new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,con(TypeMem.BOT)).peephole()); // All aliases - _scope.mem(mem); + // Unknown callers supply whole memory; field aliases are discovered lazily. + _scope.mem(new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,con(TypeMem.BOT)).peephole()); // All args, "as-if" called externally for( int i=0; i<ids.length; i++ ) { Type t = sig.arg(i); @@ -208,7 +205,7 @@ public class Parser { // Last expression is the return if( ctrl()._type==Type.CONTROL ) - fun.addReturn(ctrl(), _scope.mem().merge(), last); + fun.addReturn(ctrl(), _scope.mem(), last); // Pop off the inProgress node on the multi-exit Region merge assert r.inProgress(); @@ -568,9 +565,8 @@ public class Parser { */ private Node parseReturn() { var expr = require(parseAsgn(), ";"); - // Need default memory, since it can be lazy, need to force - // a non-lazy Phi - _fun.addReturn(ctrl(), _scope.mem().merge(), expr); + // Force the lazy whole-memory Phi before closing this exit. + _fun.addReturn(ctrl(), _scope.mem(), expr); ctrl(XCTRL); // Kill control return expr; } @@ -1183,29 +1179,31 @@ public class Parser { Field[] fs = obj._fields; if( fs==null ) throw error("Unknown struct type '" + obj._name + "'"); - int len = fs.length; - Node[] ns = new Node[2+len]; - ns[0] = ctrl(); // Control in slot 0 - // Total allocated length in bytes - ns[1] = size; - // Memory aliases for every field - for( int i = 0; i < len; i++ ) - ns[2+i] = memAlias(fs[i]._alias); - Node nnn = new NewNode(TypeMemPtr.make(obj), ns).peephole().keep(); - for( int i = 0; i < len; i++ ) - memAlias(fs[i]._alias, new ProjNode(nnn,i+2,memName(fs[i]._alias)).peephole()); - Node ptr = new ProjNode(nnn.unkeep(),1,obj._name).peephole().keep(); - - // Initial nonzero values for every field - for( int i = 0; i < len; i++ ) { - Node val = init.get( i + idx ); + Node prior = mem().keep(); + MemMergeNode input = new MemMergeNode((Node)null); + for( Field f : fs ) input.alias(f._alias,prior); + // Allocation zeroes its fields; nonzero initializers become explicit Stores. + Node[] ns = new Node[3]; + ns[0] = ctrl(); + ns[1] = input.peephole(); + ns[2] = size; + Node nnn = new NewNode(TypeMemPtr.make(obj),ns).peephole().keep(); + Node ptr = new ProjNode(nnn,0,obj._name).peephole().keep(); + Node out = new ProjNode(nnn,1,"$mem").peephole().keep(); + MemMergeNode after = new MemMergeNode(prior); + for( Field f : fs ) after.alias(f._alias,out); + mem(after.peephole()); + out.unkeep(); + nnn.unkeep(); + prior.unkeep(); + for( int i=0; i<fs.length; i++ ) { + Node val = init.get(idx+i); if( val._type != val._type.makeZero() ) { - Node mem = memAlias(fs[i]._alias); - Node st = new StoreNode(loc(),fs[i]._fname,fs[i]._alias,fs[i]._type,mem,ptr,con(obj.offset(i)),val,true).peephole(); - memAlias(fs[i]._alias,st); + Field f = fs[i]; + Node st = new StoreNode(loc(),f._fname,f._alias,f._type,mem(),ptr,con(obj.offset(i)),val,true).peephole(); + mem(new MemMergeNode(mem(),f._alias,st).peephole()); } } - return ptr.unkeep(); } @@ -1218,13 +1216,18 @@ public class Parser { return newStruct(ary,size,0,ALTMP); } - // 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.mem(alias ); } - private void memAlias(int alias, Node st) { _scope.mem(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.mem(); } + private void mem(Node n) { _scope.mem(n); } + + private void store(String name, int alias, Type glb, Node ptr, Node off, Node val, boolean init, Node ctrl) { + Node prior = mem().keep(); + Node st = new StoreNode(loc(),name,alias,glb,prior,ptr,off,val,init); + st.setDef(0,ctrl); + st = st.peephole(); + mem(new MemMergeNode(prior,alias,st).peephole()); + prior.unkeep(); + } /** * Parse postfix expression; this can be a field expression, an array @@ -1267,6 +1270,7 @@ public class Parser { if( tf instanceof TypeMemPtr ftmp && ftmp.isFRef() ) tf = ftmp.makeFrom(((TypeMemPtr)(TYPES.get(ftmp._obj._name)))._obj); + expr.keep(); // Field offset; fixed for structs, computed for arrays Node off = (name.equals("[]") // If field is an array body // Array index math @@ -1279,16 +1283,11 @@ public class Parser { Node val = parseAsgn().keep(); Node lift = liftExpr( val, tf, f._final ); - Node st = new StoreNode(loc(), name, f._alias, tf, memAlias(f._alias), expr, off.unkeep(), lift, false); - // Arrays include control, as a proxy for a safety range check. - // Structs don't need this; they only need a NPE check which is - // done via the type system. - if( base.isAry() ) st.setDef(0,ctrl()); - memAlias(f._alias, st.peephole()); + store(name,f._alias,tf,expr.unkeep(),off.unkeep(),lift,false,base.isAry() ? ctrl() : null); return val.unkeep(); // "obj.a = expr" returns the expression while updating memory } - Node load = new LoadNode(loc(),name, f._alias, tf, memAlias(f._alias), expr.keep(), off); + Node load = new LoadNode(loc(),name, f._alias, tf, mem(), expr, off); // Arrays include control, as a proxy for a safety range check // Structs don't need this; they only need a NPE check which is // done via the type system. @@ -1299,12 +1298,7 @@ public class Parser { char ch = _lexer.matchOperAssign(); if( ch!=0 ) { Node op = opAssign(ch,load,f._final,tf,name); - Node st = new StoreNode(loc(), name, f._alias, tf, memAlias(f._alias), expr.unkeep(), off, op, false); - // Arrays include control, as a proxy for a safety range check. - // Structs don't need this; they only need a NPE check which is - // done via the type system. - if( base.isAry() ) st.setDef(0,ctrl()); - memAlias(f._alias, peep(st)); + store(name,f._alias,tf,expr.unkeep(),off,op,false,base.isAry() ? ctrl() : null); load = postfix(ch) ? load.unkeep() : op; // And use the original loaded value as the result } else expr.unkill(); @@ -1385,7 +1379,7 @@ public class Parser { } // Control & memory after parsing args args.set(0,ctrl().keep()); - args.set(1,_scope.mem().merge().keep()); + args.set(1,_scope.mem().keep()); args.push(expr); // Function pointer // Unkeep them all for( Node arg : args ) @@ -1405,11 +1399,8 @@ public class Parser { CallEndNode cend = (CallEndNode)new CallEndNode(call).peephole(); // Control from CallEnd ctrl(new CProjNode(cend,0,ScopeNode.CTRL).peephole()); - // Memory from CallEnd - MemMergeNode mem = new MemMergeNode(true); - mem.addDef(null); // Alias#0 - mem.addDef(new ProjNode(cend,1,ScopeNode.MEM0).peephole()); - _scope.mem(mem); + // A call may update any alias, so its memory result is the new whole state. + _scope.mem(new ProjNode(cend,1,ScopeNode.MEM0).peephole()); // Call result return new ProjNode(cend,2,"#2").peephole(); } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java b/chapter20/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java index 4ebd86c0..52906afb 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java @@ -51,7 +51,7 @@ public abstract class GlobalCodeMotion { var cons = new ArrayList<Node>(); for( Node con : start._outputs ) if( con!=null && !(con instanceof CFGNode) && - (con.isConst() || con instanceof MachNode mach && mach.outregmap()!=null) ) { + (con.isConst() || (con instanceof MachNode mach && mach.outregmap()!=null)) ) { globals.put(con,true); cons.add(con.keep()); // Preserve original inputs while rewiring users. } @@ -172,15 +172,9 @@ public abstract class GlobalCodeMotion { continue outer; // Nope, await all uses done // Loads need their memory inputs' uses also done - if( n instanceof LoadNode ld ) - for( Node memuse : ld.mem()._outputs ) - if( late[memuse._nid]==null && - // New makes new memory, never crushes load memory - !(memuse instanceof NewNode) && - // Load-use directly defines memory - (memuse._type instanceof TypeMem || - // Load-use indirectly defines memory - (memuse._type instanceof TypeTuple tt && tt._types[ld._alias] instanceof TypeMem)) ) + if( n instanceof MemOpNode ld && !ld.isMem() ) + for( Node memuse : ld.antiDeps() ) + if( late[memuse._nid]==null ) continue outer; // All uses done, schedule @@ -192,9 +186,7 @@ public abstract class GlobalCodeMotion { for( Node def : n._inputs ) { if( def==null ) continue; if( late[def._nid]==null ) work.push(def); - for( Node out : def._outputs ) - if( out instanceof LoadNode ld && late[ld._nid]==null ) - work.push(ld); + wakeLoads(def,late,work,new BitSet()); } if( n instanceof LoopNode loop ) for( Node phi : loop._outputs ) @@ -213,7 +205,7 @@ public abstract class GlobalCodeMotion { lca = use_block(n,use, late).domLCA(lca,null); // Loads may need anti-dependencies, raising their LCA - if( n instanceof LoadNode load ) + if( n instanceof MemOpNode load && !load.isMem() ) lca = find_anti_dep(lca,load,early,late,anti); // Walk up from the LCA to the early, looking for best place. This is @@ -253,15 +245,26 @@ public abstract class GlobalCodeMotion { best instanceof IfNode; } - private static CFGNode find_anti_dep(CFGNode lca, LoadNode load, CFGNode early, CFGNode[] late, int[] anti) { + // An allocation may consume a load's memory through a partial aggregate. + private static void wakeLoads(Node def, CFGNode[] late, WorkList<Node> work, BitSet visit) { + if( visit.get(def._nid) ) return; + visit.set(def._nid); + for( Node out : def._outputs ) + if( out instanceof MemOpNode ld && !ld.isMem() && late[ld._nid]==null ) work.push(ld); + if( def instanceof MemMergeNode ) + for( int i=1; i<def.nIns(); i++ ) + if( def.in(i)!=null ) wakeLoads(def.in(i),late,work,visit); + } + + private static CFGNode find_anti_dep(CFGNode lca, MemOpNode load, CFGNode early, CFGNode[] late, int[] anti) { // We could skip final-field loads here. // Walk LCA->early, flagging Load's block location choices for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() ) anti[cfg._nid] = load._nid; // Walk load->mem uses, looking for Stores causing an anti-dep - for( Node mem : load.mem()._outputs ) { + for( Node mem : load.antiDeps() ) { switch( mem ) { - case StoreNode st: + case MemOpNode st: assert late[st._nid]!=null; lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st,anti); break; @@ -276,11 +279,10 @@ public abstract class GlobalCodeMotion { if( phi.in(i)==load.mem() ) lca = anti_dep(load,phi.region().cfg(i),load.mem().cfg0(),lca,null,anti); break; - case NewNode st: break; - case LoadNode ld: break; // Loads do not cause anti-deps on other loads - case ReturnNode ret: break; // Load must already be ahead of Return - case MemMergeNode ret: break; // Mem uses now on ScopeMin - case NeverNode never: break; + case NewNode st: + assert late[st._nid]!=null; + lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st,anti); + break; default: throw Utils.TODO(); } } @@ -288,15 +290,13 @@ public abstract class GlobalCodeMotion { } // - private static CFGNode anti_dep( LoadNode load, CFGNode stblk, CFGNode defblk, CFGNode lca, Node st, int[] anti ) { - // Preserve the full store range for the earlier evaluator scheduler. - // It places nodes independently of GCM and may hoist this store. + private static CFGNode anti_dep( MemOpNode load, CFGNode stblk, CFGNode defblk, CFGNode lca, Node st, int[] anti ) { for( ; stblk != defblk.idom(); stblk = stblk.idom() ) { // Store and Load overlap, need anti-dependence if( anti[stblk._nid]==load._nid ) { lca = stblk.domLCA(lca,null); // Raise Loads LCA - if( lca == stblk && st != null && st._inputs.find(load) == -1 ) // And if something moved, - st.addDef(load); // Add anti-dep as well + if( lca == stblk && st != null && !(st instanceof CallNode) && st._inputs.find(load) == -1 ) // And if something moved, + st.addDef(load); // Add anti-dep as well; Calls already end their block. return lca; // Cap this stores' anti-dep to here } } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java new file mode 100644 index 00000000..e9d4b897 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java @@ -0,0 +1,240 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.codegen.CodeGen; +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); } + + public BulkMemPhiNode(BulkMemPhiNode phi) { super(phi); _aliases = phi._aliases; } + + @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,CodeGen.CODE.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 mmm.in(1)!=null && missingAlias(mmm)==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 ParmNode parm -> 0; + case ConstantNode con -> 0; + case MemMergeNode mmm -> missingAlias(mmm); + 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); + case BulkMemPhiNode bulk -> missingAlias(bulk); + case MemOpNode mem -> unsplit(mem._alias); + case MemPhiNode phi -> unsplit(phi._alias); + default -> { + assert use instanceof ReturnNode || use instanceof CallNode || use instanceof ParmNode + : "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; + } + + // Every explicit slot requests that alias, including a partial allocation + // input whose slot still points to this bulk Phi. + private int missingAlias(MemMergeNode mmm) { + for( int alias=2; alias<mmm.nIns(); alias++ ) + if( mmm.in(alias)!=null && + mmm.alias(alias)!=mmm.in(1) && + !_aliases.get(alias) ) + 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++ ) + CodeGen.CODE.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; + CodeGen.CODE.add(bulk); + CodeGen.CODE.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,Type.BOTTOM)); + for( int i=1; i<nIns(); i++ ) + mphi.addDef(CodeGen.CODE.add(preciseInput(in(i),alias))); + return CodeGen.CODE.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/chapter20/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/LoadNode.java index 94c3e337..76125e39 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.type.*; @@ -23,6 +24,25 @@ public class LoadNode extends MemOpNode { super(loc, name, alias, glb, mem, ptr, off); } + @Override boolean canDrop(MemOpNode other, Node dep) { + return super.canDrop(other,dep) && !clobbered(dep); + } + + // Check only immediate memory users. Writers clobber memory; Phis and + // aggregates might lead to a clobber, so stop rather than search further. + private boolean clobbered(Node dep) { + Node mem = mem(); + mem.addDepForwards(dep); + for( Node use : mem._outputs ) { + if( use==null ) continue; + use.addDepForwards(dep); + if( use instanceof StoreNode || use instanceof PhiNode || + use instanceof MemMergeNode || + use instanceof NewNode || use instanceof CallNode ) return true; + } + return false; + } + // Debugger label @Override public String label() { return "ld_"+mlabel(); } @Override @@ -30,32 +50,32 @@ public class LoadNode extends MemOpNode { @Override public Type compute() { - if( mem()._type instanceof TypeMem mem ) { - // Update declared forward ref to the actual - if( _declaredType.isFRef() && mem._t instanceof TypeMemPtr tmp && !tmp.isFRef() ) - _declaredType = tmp; - // No lifting if ptr might null-check - if( err()==null ) - return _declaredType.join(mem._t); - } - return _declaredType; + Type t = MemMergeNode.contents(mem(),_alias,this); + // Update declared forward ref to the actual. + if( _declaredType.isFRef() && t instanceof TypeMemPtr tmp && !tmp.isFRef() ) + _declaredType = tmp; + return err()==null ? _declaredType.join(t) : _declaredType; } @Override public Node idealize() { + if( mem() instanceof MemMergeNode merge ) { + setDef(1,CodeGen.CODE.add(merge.alias(_alias))); + return this; + } Node ptr = ptr(); Node mem = mem(); // Simple Load-after-Store on same address. - if( mem instanceof StoreNode st && - ptr == st.ptr() && off() == st.off() ) { // Must check same object + if( mem() instanceof StoreNode st && + _alias==st._alias && ptr == st.ptr() && off() == st.off() ) { // Must check same object assert _name.equals(st._name); // Equiv class aliasing is perfect return extend(st.val()); } // Simple Load-after-New on same address. if( mem instanceof ProjNode p && p.in(0) instanceof NewNode nnn && - ptr == nnn.proj(1) ) // Must check same object + ptr == nnn.proj(0) ) // Must check same object return zero(nnn); // Load zero from new // Uplift control to a prior dominating load. @@ -72,12 +92,16 @@ public class LoadNode extends MemOpNode { ptr = ro.in(1); outer: while( true ) { + mem.addDep(this); switch( mem ) { + case MemMergeNode merge: + mem = merge.alias(_alias); + break; case StoreNode st: - if( ptr == st.ptr().addDep(this) && off() == st.off() ) + if( _alias==st._alias && ptr == st.ptr().addDep(this) && off() == st.off() ) return extend(castRO(st.val())); // Proved equal // Can we prove unequal? Offsets do not overlap? - if( !off()._type.join(st.off()._type).isHigh() && // Offsets overlap + if( _alias==st._alias && !off()._type.join(st.off()._type).isHigh() && // Offsets overlap !neverAlias(ptr,st.ptr()) ) // And might alias break outer; // Cannot tell, stop trying // Pointers cannot overlap @@ -95,17 +119,16 @@ public class LoadNode extends MemOpNode { return zero(nnn1); if( !(ptr instanceof ProjNode pproj && pproj.in(0) instanceof NewNode) ) break outer; // Cannot tell, ptr not related to New - mem = nnn1.in(nnn1.findAlias(_alias));// Bypass unrelated New + mem = nnn1.mem();// Bypass unrelated New break; case StartNode start: break outer; case CallEndNode cend: break outer; // TODO: Bypass no-alias call default: throw Utils.TODO(); } break; - case MemMergeNode merge: mem = merge.alias(_alias); break; default: - throw Utils.TODO(); + break outer; } } @@ -116,7 +139,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 && // Offset can be hoisted off() instanceof ConstantNode && // Pointer can be hoisted @@ -145,7 +168,9 @@ public class LoadNode extends MemOpNode { private Node ld( int idx ) { Node mem = mem(), ptr = ptr(); - return new LoadNode(_loc,_name,_alias,_declaredType,mem.in(idx),ptr instanceof PhiNode && ptr.in(0)==mem.in(0) ? ptr.in(idx) : ptr, off()).peephole(); + LoadNode ld = new LoadNode(_loc,_name,_alias,_declaredType,mem.in(idx),ptr instanceof PhiNode && ptr.in(0)==mem.in(0) ? ptr.in(idx) : ptr,off()); + ld.setDef(0,in(0)); + return ld.peephole(); } private static boolean neverAlias( Node ptr1, Node ptr2 ) { @@ -178,9 +203,9 @@ public class LoadNode extends MemOpNode { private boolean profit(PhiNode phi, int idx) { Node px = phi.in(idx); if( px==null ) return false; - if( px._type instanceof TypeMem mem && mem._t.isHighOrConst() ) return true; - if( px instanceof StoreNode st1 && ptr()==st1.ptr() && off()==st1.off() ) return true; px.addDep(this); + if( px._type instanceof TypeMem mem && mem._t.isHighOrConst() ) return true; + if( px instanceof StoreNode st1 && _alias==st1._alias && ptr()==st1.ptr() && off()==st1.off() ) return true; return false; } @@ -205,4 +230,5 @@ public class LoadNode extends MemOpNode { Node shl = new ShlNode(null,val,shf.keep()).peephole(); return new SarNode(null,shl,shf.unkeep()); } + } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java index adf83aab..31c76a93 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java @@ -5,151 +5,85 @@ import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.*; import java.util.*; -/** - * Memory Merge - a merge of many aliases into a "fat memory". All aliases - * are here, but most will be lazy - take the default fat memory. +/** A default slice and explicit alias overrides. A null default means partial + * memory: absent aliases are not covered, rather than having unknown contents. */ public class MemMergeNode extends Node { + public MemMergeNode(MemMergeNode mem) { super(mem); } - /* - * In-Progress means this is being used by the Parser to track memory - * aliases. No optimizations are allowed. When no longer "in progress" - * normal peeps work. - */ - public final boolean _inProgress; - - public MemMergeNode( boolean inProgress) { _type = TypeMem.BOT; _inProgress = inProgress; } - public MemMergeNode(MemMergeNode mem) { super(mem); _inProgress = false; } - - - // If being used by a Scope, this is "in progress" from the Parser. - // Otherwise, it's a memory merge - boolean inProgress() { return _inProgress; } - - @Override public String label() { return "ALLMEM"; } - @Override public boolean isMem() { return true; } - - @Override - StringBuilder _print1(StringBuilder sb, BitSet visited) { - sb.append(_inProgress ? "Merge[" : "MEM[ "); - for( int j=2; j<nIns(); j++ ) { - sb.append(j); - sb.append(":"); - Node n = in(j); - while( n instanceof ScopeNode loop ) { - sb.append("Lazy_"); - n = loop.mem().alias(j); - } - if( n==null ) sb.append("___ "); - else n._print0(sb, visited).append(" "); - } - sb.setLength(sb.length()-1); - return sb.append("]"); - } - - // Make a memory merge: no longer a Scope really, tracking memory state but - // not related to the parser in any way. - public Node merge() { - // Force default memory to not be lazy - MemMergeNode merge = new MemMergeNode(false); - for( Node n : _inputs ) - merge.addDef(n); - merge._mem(1,null); - return merge.peephole(); - } - - - @Override public Type compute() { return TypeMem.BOT; } - - @Override public Node idealize() { - if( inProgress() ) return null; - - // If not merging any memory (all memory is just the default) - if( nIns()==2 ) - return in(1); // Become default memory - - return null; + 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 in( Var v ) { return in(v._idx); } - - public Node alias( int alias ) { - return in(alias<nIns() && in(alias)!=null ? alias : 1); + public Node alias(int alias) { + Node n = alias < nIns() ? in(alias) : null; + assert n!=null || !(in(1) instanceof BulkMemPhiNode bulk) || !bulk.isSplit(alias); + n = n==null ? in(1) : n; + assert n!=null : "Alias not covered: "+alias; + return n; } - Node alias( int alias, Node st ) { - while( alias >= nIns() ) addDef(null); - return setDef(alias,st); + public void alias(int alias, Node mem) { + assert alias > 1; + while( nIns()<=alias ) addDef(null); + setDef(alias,mem); } - // Read or update from memory. - // A shared implementation allows us to create lazy phis both during - // lookups and updates; the lazy phi creation is part of chapter 8. - Node _mem( int alias, Node st ) { - // Memory projections are made lazily; if one does not exist - // then it must be START.proj(1) - Node old = alias(alias); - if( old instanceof ScopeNode loop ) { - MemMergeNode loopmem = loop.mem(); - Node memdef = loopmem.alias(alias); - // Lazy phi! - old = memdef instanceof PhiNode phi && loop.ctrl()==phi.region() - // Loop already has a real Phi, use it - ? memdef - // 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. - : loopmem.alias(alias, new PhiNode(Parser.memName(alias), TypeMem.BOT,loop.ctrl(),loopmem._mem(alias,null),null).peephole() ); - alias(alias,old); + // Scalar contents of one alias. New zeroes the allocated fields; + // join it with the incoming contents for all previously allocated objects. + // Phis use their cached types, so this query does not recurse around loops. + static Type contents(Node mem, int alias, Node dep) { + mem.addDep(dep); + if( mem instanceof MemMergeNode merge ) + return contents(merge.alias(alias),alias,dep); + if( mem instanceof ProjNode proj && proj.in(0) instanceof NewNode nnn ) { + assert proj._idx==1 && nnn.field(alias)!=null; + nnn.addDep(dep); + return contents(nnn.mem(),alias,dep).meet(nnn.field(alias)._type.makeZero()); } - // Memory projections are made lazily; expand as needed - return st==null ? old : alias(alias,st); // Not lazy, so this is the answer - } - - - void _merge( MemMergeNode that, RegionNode r) { - int len = Math.max(nIns(),that.nIns()); - for( int i = 2; i < len; i++) - if( alias(i) != that.alias(i) ) { // No need for redundant Phis - // If we are in lazy phi mode we need to a lookup - // by alias as it will trigger a phi creation - Node lhs = this._mem(i,null); - Node rhs = that._mem(i,null); - alias(i, new PhiNode(Parser.memName(i), lhs._type.glb().meet(rhs._type.glb()), r, lhs, rhs).peephole()); - } + // Function parameters and call results may contain arbitrary heap values. + // Their bulk memory types do not imply empty or zero-filled storage. + if( mem._type==Type.TOP || mem._type==TypeMem.TOP ) return Type.TOP; + if( mem._type instanceof TypeMem mt && mt._alias==alias ) return mt._t; + return Type.BOTTOM; } - // Fill in the backedge of any inserted Phis - void _endLoopMem( ScopeNode scope, MemMergeNode back, MemMergeNode exit ) { - Node exit_def = exit.alias(1); - for( int i=1; i<nIns(); i++ ) { - if( in(i) instanceof PhiNode phi && phi.region()==scope.ctrl() ) { - assert phi.in(2)==null; - phi.setDef(2,back.in(i)==scope ? phi : back.in(i)); // Fill backedge + @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(" "); } - if( exit_def == scope ) // Replace a lazy-phi on the exit path also - exit.alias(i,in(i)); - } + return sb.append("]"); } - // Now one-time do a useless-phi removal - void _useless( ) { + @Override public Node idealize() { + boolean progress=false, allDefault=true; for( int i=2; i<nIns(); i++ ) { - if( in(i) instanceof PhiNode phi ) { - // Do an eager useless-phi removal - Node in = phi.peephole(); - CodeGen.CODE.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 - } + if( in(i)!=null && in(i)==in(1) ) { setDef(i,null); progress=true; } + if( in(i) instanceof MemMergeNode mem ) { + setDef(i,CodeGen.CODE.add(mem.alias(i))); + progress=true; } + if( in(i)!=null ) allDefault=false; } - } - - @Override boolean eq( Node n ) { - return this==n || !_inProgress; + if( allDefault && in(1)!=null ) 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)); + CodeGen.CODE.add(mem.in(1)); + setDef(1,mem.in(1)); + return this; + } + assert BulkMemPhiNode.checkMerge(this); + return progress ? this : null; } } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/MemOpNode.java index cba4dc1b..41760124 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/MemOpNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/MemOpNode.java @@ -6,6 +6,7 @@ import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeMemPtr; import java.lang.StringBuilder; import java.util.BitSet; +import java.util.ArrayList; /** * Convenience common base for Load and Store. @@ -51,6 +52,13 @@ public abstract class MemOpNode extends Node { public StringBuilder _printMach( StringBuilder sb, BitSet visited ) { throw Utils.TODO(); } + // Extra conditions for factoring matching memory operations through a Phi. + // The caller has already checked the opcode, input shape and control. + boolean canDrop(MemOpNode other, Node dep) { + ptr().addDep(dep); + return _alias==other._alias && _declaredType==other._declaredType && err()==null; + } + @Override boolean eq(Node n) { MemOpNode mem = (MemOpNode)n; // Invariant @@ -71,4 +79,29 @@ public abstract class MemOpNode extends Node { return null; return Parser.error( "Might be null accessing '" + _name + "'",_loc); } + // Memory writers which must follow this read. A packaging node is not a + // write, but can hide New's input, so follow this alias through aggregates. + public ArrayList<Node> antiDeps() { + ArrayList<Node> deps = new ArrayList<>(); + antiDeps(mem(),deps,new BitSet()); + return deps; + } + + private void antiDeps(Node mem, ArrayList<Node> deps, BitSet visit) { + if( visit.get(mem._nid) ) return; + visit.set(mem._nid); + for( Node use : mem._outputs ) { + if( use instanceof MemMergeNode merge ) { + Node slice = _alias<merge.nIns() ? merge.in(_alias) : null; + if( (slice==mem) || (slice==null && merge.in(1)==mem) ) + antiDeps(merge,deps,visit); + } else if( use instanceof CallNode || + (use instanceof MemOpNode st && st.isMem() && st._alias==_alias) || + (use instanceof NewNode nnn && nnn.field(_alias)!=null) || + (use instanceof MemPhiNode phi && phi._alias==_alias) || + (use instanceof BulkMemPhiNode phi && !phi.isSplit(_alias)) ) { + if( !deps.contains(use) ) deps.add(use); + } + } + } } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java new file mode 100644 index 00000000..26572ce1 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java @@ -0,0 +1,44 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.codegen.CodeGen; +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,Type.BOTTOM),inputs); + assert alias > 1; + _alias = alias; + } + + public MemPhiNode(MemPhiNode phi) { super(phi); _alias = phi._alias; } + + @Override public String label() { return "MemPhi_"+_alias; } + + @Override public Type compute() { + assert BulkMemPhiNode.checkMem(this,_alias); + if( !(region() instanceof RegionNode r) ) + return region()._type==Type.XCONTROL ? TypeMem.TOP : _type; + if( r.inProgress() ) return _declaredType; + Type t = Type.TOP; + for( int i=1; i<nIns(); i++ ) + if( r.in(i).addDep(this)._type!=Type.XCONTROL ) + t = t.meet(MemMergeNode.contents(in(i),_alias,this)); + return TypeMem.make(_alias,t); + } + + @Override public Node idealize() { + for( int i=1; i<nIns(); i++ ) + if( in(i) instanceof MemMergeNode mem ) { + setDef(i,CodeGen.CODE.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/chapter20/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/NewNode.java index abce1601..b9c9de71 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/NewNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/NewNode.java @@ -1,37 +1,37 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.type.*; -import com.seaofnodes.simple.Utils; import java.util.BitSet; -/** - * Allocation! Allocate a chunk of memory, and pre-zero it. - * The inputs include control and size, and ALL aliases being set. - * The output is large tuple, one for every alias plus the created pointer. - * New is expected to be followed by projections for every alias. +/** Allocate a zeroed object. Inputs {ctrl, $mem, size}; + * results {ptr, $mem}. + * The memory input and result cover only the aliases in the allocated struct. */ public class NewNode extends Node implements MultiNode { public final TypeMemPtr _ptr; - public final int _len; public NewNode(TypeMemPtr ptr, Node... nodes) { super(nodes); assert !ptr.nullable(); _ptr = ptr; - _len = ptr._obj._fields.length; - // Control in slot 0 - assert nodes[0]._type==Type.CONTROL || nodes[0]._type == Type.XCONTROL; - // Malloc-length in slot 1 - assert nodes[1]._type instanceof TypeInteger || nodes[1]._type==Type.NIL; - for( int i=0; i<_len; i++ ) - // Memory slices for all fields. - assert nodes[2+i]._type.isa(TypeMem.BOT); + assert nodes.length==3; + assert nodes[0]._type==Type.CONTROL || nodes[0]._type==Type.XCONTROL; + assert nodes[1]._type instanceof TypeMem; + assert nodes[2]._type instanceof TypeInteger || nodes[2]._type==Type.NIL; } - public NewNode(NewNode nnn) { super(nnn); _ptr = nnn._ptr; _len = nnn._len; } + public Node mem() { return in(1); } + public Node size() { return in(2); } + + public Field field(int alias) { + for( Field f : _ptr._obj._fields ) + if( f._alias==alias ) return f; + return null; + } + + public NewNode(NewNode nnn) { super(nnn); _ptr = nnn._ptr; } - public Node mem (int idx) { return in(idx+2); } @Override public String label() { return "new_"+(_ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._type.str() : _ptr._obj.str()); } @Override @@ -40,32 +40,10 @@ public class NewNode extends Node implements MultiNode { return sb.append(_ptr._obj.str()); } - int findAlias(int alias) { - int idx = _ptr._obj.findAlias(alias); - assert idx!= -1; // Error, caller should be calling - return idx+2; // Skip ctrl, size - } - - // 0 - ctrl - // 1 - byte size - // 2-len+2 - aliases, one per field - // len+2 - 2*len+2 - initial values, one per field - public Node size() { return in(1); } @Override public TypeTuple compute() { - Field[] fs = _ptr._obj._fields; - Type[] ts = new Type[fs.length+2]; - ts[0] = Type.CONTROL; - ts[1] = _ptr; - for( int i=0; i<fs.length; i++ ) { - Type mt = in(i+2)._type; - TypeMem mem = mt==Type.TOP ? TypeMem.TOP : (TypeMem)mt; - Type tfld = mem._t.meet(mem._t.makeZero()); - Type tfld2 = tfld.join(fs[i]._type); - ts[i+2] = TypeMem.make(fs[i]._alias,tfld2); - } - return TypeTuple.make(ts); + return TypeTuple.make(_ptr,TypeMem.BOT); } @Override diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter20/src/main/java/com/seaofnodes/simple/node/Node.java index 65dbbd76..aecac75c 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/Node.java @@ -230,6 +230,7 @@ public abstract class Node implements Cloneable { // Error is 'use' does not exist; ok for 'use' to be null. protected boolean delUse( Node use ) { _outputs.del(_outputs.find(use)); + moveDepsToWorklist(); // User-count and anti-dependence queries can now change. return _outputs.isEmpty(); } @@ -288,6 +289,7 @@ public abstract class Node implements Cloneable { n.unlock(); int idx = n._inputs.find(this); n._inputs.set(idx,nnn); + n.moveDepsToWorklist(); // Rewiring can change a dependent query without changing type. nnn.addUse(n); CODE.addAll(n._outputs); } @@ -539,6 +541,14 @@ public abstract class Node implements Cloneable { * 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( CODE._midAssert ) return this; @@ -547,8 +557,8 @@ public abstract class Node implements Cloneable { if( dep == null ) return this; if( _deps==null ) _deps = new Ary<>(Node.class); if( _deps .find(dep) != -1 ) return this; // Already on list - if( _inputs .find(dep) != -1 ) return this; // No need for deps on immediate neighbors - if( _outputs.find(dep) != -1 ) return this; + if( !forwards && _inputs .find(dep) != -1 ) return this; // No need for deps on immediate neighbors + if( !forwards && _outputs.find(dep) != -1 ) return this; _deps.add(dep); return this; } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/PhiNode.java index 944e7ed6..52298345 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -24,6 +24,10 @@ public class PhiNode extends Node { addDef(sample); } + 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_"+MemOpNode.mlabel(_label); } @Override @@ -77,32 +81,9 @@ public class PhiNode extends Node { if( r.in(i)._type == Type.XCONTROL ) return null; - // 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 && same_op() ) { - assert !(op instanceof CFGNode); - 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(); - Node down = op.copy(phi_lhs,phi_rhs); - // Test not running backwards, which can happen for e.g. And's - if( down.compute().isa(compute()) ) - return down; - in(1).in(1).addDep(this); - in(1).in(2).addDep(this); - in(2).in(1).addDep(this); - in(2).in(2).addDep(this); - down.kill(); - } + // Phi(op(a,b,...),op(c,d,...)) -> op(Phi(a,c),Phi(b,d),...). + Node down; + if( same_op() && (down=drop_same_op())!=null ) return down; // If merging Phi(N, cast(N)) - we are losing the cast JOIN effects, so just remove. if( nIns()==3 ) { @@ -134,12 +115,75 @@ public class PhiNode extends Node { } private boolean same_op() { - for( int i=2; i<nIns(); i++ ) - if( in(1).getClass() != in(i).getClass() ) - return false; + Node op = in(1); + if( op instanceof CFGNode || op instanceof ConstantNode || op instanceof PhiNode || + op instanceof ProjNode || op instanceof NewNode || op instanceof ScopeNode || op instanceof MemMergeNode ) return false; + // A bulk Phi must split its aliases before factoring precise Stores. + if( this instanceof BulkMemPhiNode ) return false; + // Bulk splitting currently identifies parallel slices by Region/alias. + // Factoring can introduce another slice at a different memory point; + // wait until that Region's bulk partitioning has finished. + if( op instanceof MemOpNode ) + for( Node use : region()._outputs ) + if( use instanceof BulkMemPhiNode ) { + use.addDepForwards(this); + return false; + } + Node busy = null; + for( int i=1; i<nIns(); i++ ) { + Node n = in(i); + if( region().in(i).addDep(this)._type==Type.XCONTROL || + op.getClass()!=n.getClass() || n.nIns()!=op.nIns() || n.in(0)!=null || !op.eq(n) ) return false; + if( n instanceof MemOpNode mem && !mem.canDrop((MemOpNode)op,this) ) return false; + // Moving a Store must remove the old effect, not duplicate it. + // Allow one shared arm: the other arms disappear into the single + // factored operation, usually reducing the total operation count. + n.addDepForwards(this); + if( n.nOuts()>1 ) { + for( Node use : n._outputs ) + if( use!=null && use!=this ) use.addDepForwards(this); + if( n instanceof StoreNode || busy!=null ) return false; + busy=n; + } + for( int j=1; j<n.nIns(); j++ ) + if( n.in(j) instanceof ScopeNode || ((n.in(j)==null) != (op.in(j)==null)) ) return false; + } return true; } + private Node drop_same_op() { + Node op = in(1), cp = op.copyEmpty(); + cp._type = null; + cp.addDef(null); + for( int j=1; j<op.nIns(); j++ ) { + Node x = op.in(j); + boolean different = false; + for( int i=2; i<nIns(); i++ ) + if( in(i).in(j)!=x ) different=true; + if( different ) { + Node[] ins = new Node[nIns()]; + ins[0] = region(); + Type t = Type.TOP; + for( int i=1; i<nIns(); i++ ) { + ins[i] = in(i).in(j); + t = t.meet(ins[i]._type); + } + PhiNode phi = j==1 && op instanceof MemOpNode mem + ? new MemPhiNode(_label,mem._alias,ins) + : PhiNode.make(_label,t.glb(),ins); + x = phi.peephole(); + } + cp.addDef(x); + } + // Factoring must not widen the result (e.g. correlated And operands). + if( cp.compute().isa(compute()) ) return cp; + for( int i=1; i<nIns(); i++ ) + for( int j=1; j<in(i).nIns(); j++ ) + if( in(i).in(j)!=null ) in(i).in(j).addDep(this); + cp.kill(); + return null; + } + /** * If only single unique input, return it */ diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/RegionNode.java index 166ef4bf..f28952e0 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/RegionNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/RegionNode.java @@ -47,7 +47,7 @@ public class RegionNode extends CFGNode { if( !hasPhi() && // No Phi users, just a control user in(1) instanceof CProjNode p1 && in(2) instanceof CProjNode p2 && - p1.in(0).addDep(this)==p2.in(0).addDep(this) && + p1.addDepForwards(this).in(0)==p2.addDepForwards(this).in(0) && p1.in(0) instanceof IfNode iff ) { // Replace with the iff.ctrl directly if( nIns()==3 ) return iff.ctrl(); diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/ReturnNode.java index b767b69b..7858671c 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -6,15 +6,7 @@ import com.seaofnodes.simple.Utils; 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 all preceding memory effects and the return value alive: {ctrl, $mem, value, rpc}. */ public class ReturnNode extends CFGNode { public FunNode _fun; @@ -49,7 +41,7 @@ public class ReturnNode extends CFGNode { public Type compute() { if( inProgress () ) return TypeTuple.RET; // In progress // Inlining can delete the entry while callers still use this return. - return TypeTuple.make(ctrl()._type,mem()._type,expr()._type); + return TypeTuple.make(ctrl()._type,TypeMem.BOT,expr()._type); } @Override public Node idealize() { diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/ScopeNode.java index 13ddc53a..406be11f 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -7,9 +7,9 @@ import java.util.*; /** * The Scope node is purely a parser helper - it tracks names to nodes with a - * stack of variables. + * stack of variable records. */ -public class ScopeNode extends MemMergeNode { +public class ScopeNode extends Node { /** * The control is a name that binds to the currently active control @@ -19,6 +19,11 @@ public class ScopeNode extends MemMergeNode { public static final String ARG0 = "arg"; public static final String MEM0 = "$mem"; + public Node in(Var v) { return in(v._idx); } + + @Override public Type compute() { return Type.BOTTOM; } + @Override public Node idealize() { return null; } + // All active/live variables in all nested scopes, all run together public final Ary<Var> _vars; @@ -34,7 +39,7 @@ public class ScopeNode extends MemMergeNode { // A new ScopeNode public ScopeNode() { - super(true); + _type = Type.BOTTOM; _vars = new Ary<>(Var .class); _lexSize= new Ary<>(Integer.class); _kinds = new Ary<>(Kind .class); @@ -70,7 +75,7 @@ public class ScopeNode extends MemMergeNode { public Node ctrl() { return in(0); } - public MemMergeNode mem() { return (MemMergeNode)in(1); } + public Node mem() { return in(lookup(MEM0)); } /** * The ctrl of a ScopeNode is always bound to the currently active @@ -83,7 +88,7 @@ public class ScopeNode extends MemMergeNode { * @return Node that was bound */ public <N extends Node> N ctrl(N n) { return setDef(0,n); } - public Node mem(Node n) { return setDef(1,n); } + public Node mem(Node n) { update(MEM0,n); return n; } public void push(Kind kind) { assert _lexSize._len==_kinds._len; @@ -166,12 +171,6 @@ public class ScopeNode extends MemMergeNode { return true; } - // Read from memory - public Node mem( int alias ) { return mem()._mem(alias,null); } - // Write to memory - public void mem( int alias, Node st ) { mem()._mem(alias,st); } - - /** * Lookup a name in all scopes starting from most deeply nested. * @@ -207,7 +206,7 @@ public class ScopeNode extends MemMergeNode { // 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(v._idx,new PhiNode(v._name, v.lazyGLB(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).peephole()); + : loop.setDef(v._idx,PhiNode.make(v._name, v.lazyGLB(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).peephole()); setDef(v._idx,old); } assert !v._final || st==null; @@ -246,20 +245,8 @@ public class ScopeNode extends MemMergeNode { n.keep(); dup.addDef(ctrl()); // Control input is just copied - // Memory input is a shallow copy - MemMergeNode memdup = new MemMergeNode(true), mem = mem(); - memdup.addDef(null); - memdup.addDef(loop ? this : mem.in(1)); - for( int i=2; i<mem.nIns(); i++ ) - // For lazy phis on loops we use a sentinel - // that will trigger phi creation on update - memdup.addDef(loop ? this : mem.in(i)); - dup.addDef(memdup); - - // Copy of other inputs - for( int i=2; i<nIns(); i++ ) - // For lazy phis on loops we use a sentinel - // that will trigger phi creation on update + // Memory is one binding, with the same lazy loop Phi as scalar names. + for( int i=1; i<nIns(); i++ ) dup.addDef(loop && !_vars.at(i)._final ? this : in(i)); return dup; } @@ -274,22 +261,21 @@ public class ScopeNode extends MemMergeNode { */ public RegionNode mergeScopes(ScopeNode that, Parser.Lexer loc) { RegionNode r = ctrl(new RegionNode(loc,null,ctrl(), that.ctrl()).keep()); - mem()._merge(that.mem(),r); - this ._merge(that ,r); + _merge(that,r); that.kill(); // Kill merged scope CodeGen.CODE.add(r); return r.unkeep(); } private void _merge(ScopeNode that, RegionNode r) { - for( int i = 2; i < nIns(); i++) + 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 Var v = _vars.at(i); Node lhs = this.in(this.update(v,null)); Node rhs = that.in(that.update(v,null)); - setDef(i, new PhiNode(v._name, v.type(), r, lhs, rhs).peephole()); + setDef(i, PhiNode.make(v._name, v.type(), r, lhs, rhs).peephole()); } } @@ -314,21 +300,15 @@ public class ScopeNode extends MemMergeNode { assert ctrl instanceof LoopNode loop && loop.inProgress(); ctrl.setDef(2,back.ctrl()); - mem()._endLoopMem( this, back.mem(), exit.mem() ); - this ._endLoop ( this, back , exit ); + _endLoop(this,back,exit); back.kill(); // Loop backedge is dead // Now one-time do a useless-phi removal - mem()._useless(); - this ._useless(); - - // The exit mem's lazy default value had been the loop top, - // now it goes back to predating the loop. - exit.mem().setDef(1,mem().in(1)); + _useless(); } // Fill in the backedge of any inserted Phis void _endLoop( ScopeNode scope, Node back, Node exit ) { - for( int i=2; i<nIns(); i++ ) { + for( int i=1; i<nIns(); i++ ) { if( _vars.at(i)._final ) continue; // Final vars did not get modified in the loop if( _vars.at(i).type().isHighOrConst() ) continue; // Cannot lift higher than a constant, so no Phi if( back.in(i) != scope ) { @@ -342,6 +322,23 @@ public class ScopeNode extends MemMergeNode { } + // Now one-time do a useless-phi removal + void _useless( ) { + for( int i=1; i<nIns(); i++ ) { + if( in(i) instanceof PhiNode phi ) { + // Do an eager useless-phi removal + Node in = phi.peephole(); + CodeGen.CODE.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. public void addGuards( Node ctrl, Node pred, boolean invert ) { diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/StartNode.java index 22a1750d..2783ab93 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -3,18 +3,11 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.type.*; -import java.util.Arrays; import java.util.BitSet; import java.util.HashSet; import static com.seaofnodes.simple.Utils.TODO; -/** - * 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. - */ +/** Start supplies control, whole memory, and the argument, in that order. */ public class StartNode extends LoopNode implements MultiNode { final Type _arg; @@ -45,7 +38,7 @@ public class StartNode extends LoopNode implements MultiNode { @Override public TypeTuple compute() { - return TypeTuple.make(Type.CONTROL,TypeMem.TOP,_arg); + return TypeTuple.make(Type.CONTROL,TypeMem.BOT,_arg); } @Override public Node idealize() { return null; } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/StoreNode.java index cb374c58..4833df39 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.*; import com.seaofnodes.simple.type.*; import java.util.BitSet; @@ -25,6 +26,10 @@ public class StoreNode extends MemOpNode { } // Debugger label + @Override boolean canDrop(MemOpNode other, Node dep) { + return super.canDrop(other,dep) && _init==((StoreNode)other)._init; + } + @Override public String label() { return "st_"+mlabel(); } @Override public boolean isMem() { return true; } @@ -38,28 +43,25 @@ public class StoreNode extends MemOpNode { @Override public Type compute() { Type val = val()._type; - TypeMem mem = (TypeMem)mem()._type; // Invariant - if( mem == TypeMem.TOP ) return TypeMem.TOP; - Type t = Type.BOTTOM; // No idea on field contents - // Same alias, lift val to the declared type and then meet into other fields - if( mem._alias == _alias ) { - // Update declared forward ref to the actual - if( _declaredType.isFRef() && val instanceof TypeMemPtr tmp && !tmp.isFRef() ) - _declaredType = tmp; - val = val.join(_declaredType); - t = val.meet(mem._t); - } + if( _declaredType.isFRef() && val instanceof TypeMemPtr tmp && !tmp.isFRef() ) + _declaredType = tmp; + Type t = val.join(_declaredType).meet(MemMergeNode.contents(mem(),_alias,this)); return TypeMem.make(_alias,t); } @Override public Node idealize() { + if( mem() instanceof MemMergeNode merge ) { + setDef(1,CodeGen.CODE.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 - off()==st.off() && // And same offset (could be "same alias" but this handles arrays to same index) + ptr()==st.ptr() && _alias==st._alias && // Must check same object + off()==st.off() && // And same offset 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/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java index adffa501..e874ce15 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java @@ -34,6 +34,8 @@ public class MemOpARM extends MemOpNode implements MachNode { @Override public StringBuilder _printMach(StringBuilder sb, BitSet visited) { return sb.append(".").append(_name); } + @Override public boolean isMem() { return _type instanceof TypeMem; } + @Override public String label() { return op(); } @Override public Type compute() { throw Utils.TODO(); } @Override public Node idealize() { throw Utils.TODO(); } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java index bd88fa83..c145f348 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java @@ -12,9 +12,9 @@ public class NewARM extends NewNode implements MachNode { // A pre-zeroed chunk of memory. NewARM(NewNode nnn) { super(nnn); } // Size and pointer result in standard calling convention; null for all the - // memory aliases edges - @Override public RegMask regmap(int i) { return i == 1 ? arm. X0_MASK : null; } - @Override public RegMask outregmap(int i) { return i == 1 ? arm. X0_MASK : null; } + // memory and ordering edges + @Override public RegMask regmap(int i) { return i == 2 ? arm. X0_MASK : null; } + @Override public RegMask outregmap(int i) { return i == 0 ? arm. X0_MASK : null; } @Override public RegMask outregmap() { return null; } // Encoding is appended into the byte array; size is returned diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java index 49045b28..d78fdcad 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java @@ -34,7 +34,7 @@ public class StoreARM extends MemOpARM { if( i==2 ) return arm.RMASK; if( i==3 ) return arm.RMASK; if( i==4 ) return arm.RMASK; - throw Utils.TODO(); + return null; // Trailing anti-dependence edges } // Register mask allowed as a result. 0 for no register. @Override public RegMask outregmap() { return null; } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java index fd207157..009af342 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java @@ -204,6 +204,8 @@ public class arm extends Machine { case NewNode nnn -> new NewARM(nnn); case OrNode or -> or(or); case ParmNode parm -> new ParmARM(parm); + case BulkMemPhiNode phi -> new BulkMemPhiNode(phi); + case MemPhiNode phi -> new MemPhiNode(phi); case PhiNode phi -> new PhiNode(phi); case ProjNode prj -> prj(prj); case ReadOnlyNode read -> new ReadOnlyNode(read); diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java index bcd7924e..01f51b57 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java @@ -33,6 +33,8 @@ public class MemOpRISC extends MemOpNode implements MachNode { @Override public StringBuilder _printMach(StringBuilder sb, BitSet visited) { return sb.append(".").append(_name); } + @Override public boolean isMem() { return _type instanceof TypeMem; } + @Override public String label() { return op(); } @Override public Type compute() { throw Utils.TODO(); } @Override public Node idealize() { throw Utils.TODO(); } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java index 53ad0af2..e8d32570 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java @@ -10,9 +10,9 @@ public class NewRISC extends NewNode implements MachNode { // A pre-zeroed chunk of memory. NewRISC( NewNode nnn ) { super(nnn); } // Size and pointer result in standard calling convention; null for all the - // memory aliases edges - @Override public RegMask regmap(int i) { return i == 1 ? riscv.A0_MASK : null; } - @Override public RegMask outregmap(int i) { return i == 1 ? riscv.A0_MASK : null; } + // memory and ordering edges + @Override public RegMask regmap(int i) { return i == 2 ? riscv.A0_MASK : null; } + @Override public RegMask outregmap(int i) { return i == 0 ? riscv.A0_MASK : null; } @Override public RegMask outregmap() { return null; } // Encoding is appended into the byte array; size is returned diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java index c57cf11d..22e443fd 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java @@ -34,7 +34,7 @@ public class StoreRISC extends MemOpRISC { if( i==2 ) return riscv.RMASK; if( i==3 ) return riscv.RMASK; if( i==4 ) return riscv.RMASK; - throw Utils.TODO(); + return null; // Trailing anti-dependence edges } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java index 83706cbf..daf33f29 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java @@ -206,6 +206,8 @@ public class riscv extends Machine { case NewNode nnn -> new NewRISC(nnn); case OrNode or -> or(or); case ParmNode parm -> new ParmRISC(parm); + case BulkMemPhiNode phi -> new BulkMemPhiNode(phi); + case MemPhiNode phi -> new MemPhiNode(phi); case PhiNode phi -> new PhiNode(phi); case ProjNode prj -> prj(prj); case ReadOnlyNode read -> new ReadOnlyNode(read); diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java index f65fe054..1a771081 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java @@ -57,6 +57,8 @@ public abstract class MemOpX86 extends MemOpNode implements MachNode { return sb.append(".").append(_name); } + @Override public boolean isMem() { return _type instanceof TypeMem; } + @Override public String label() { return op(); } @Override public Type compute() { throw Utils.TODO(); } @Override public Node idealize() { throw Utils.TODO(); } @@ -67,7 +69,7 @@ public abstract class MemOpX86 extends MemOpNode implements MachNode { if( i==2 ) return x86_64_v2.RMASK; // base in GPR if( i==3 ) return x86_64_v2.RMASK; // index in GPR if( i==4 ) return _sz >= '4' ? x86_64_v2.MEM_MASK : x86_64_v2.RMASK; // Narrow stores need GPRs - throw Utils.TODO(); + return null; // Trailing anti-dependence edges } @Override public int encoding(ByteArrayOutputStream bytes) { throw Utils.TODO(); } diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java index cd47c64b..f4803b2e 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java @@ -10,9 +10,9 @@ public class NewX86 extends NewNode implements MachNode { // A pre-zeroed chunk of memory. NewX86( NewNode nnn ) { super(nnn); } // Size and pointer result in standard calling convention; null for all the - // memory aliases edges - @Override public RegMask regmap(int i) { return i == 1 ? x86_64_v2.RDI_MASK : null; } - @Override public RegMask outregmap(int i) { return i == 1 ? x86_64_v2.RAX_MASK : null; } + // memory and ordering edges + @Override public RegMask regmap(int i) { return i == 2 ? x86_64_v2.RDI_MASK : null; } + @Override public RegMask outregmap(int i) { return i == 0 ? x86_64_v2.RAX_MASK : null; } @Override public RegMask outregmap() { return null; } // Encoding is appended into the byte array; size is returned diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java index aacd2352..57771d6a 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java @@ -235,6 +235,8 @@ public class x86_64_v2 extends Machine { case NewNode nnn -> new NewX86(nnn); case OrNode or -> or(or); case ParmNode parm -> new ParmX86(parm); + case BulkMemPhiNode phi -> new BulkMemPhiNode(phi); + case MemPhiNode phi -> new MemPhiNode(phi); case PhiNode phi -> new PhiNode(phi); case ProjNode prj -> prj(prj); case ReadOnlyNode read -> new ReadOnlyNode(read); @@ -261,11 +263,12 @@ public class x86_64_v2 extends Machine { private Node add( AddNode add ) { Node lhs = add.in(1); Node rhs = add.in(2); + // The other addend must occupy the result register, even if constant. if( lhs instanceof LoadNode ld && ld.nOuts()==1 ) - return new AddMemX86(add,address(ld),ld.ptr(),idx,off,scale, imm(rhs),val); + return new AddMemX86(add,address(ld),ld.ptr(),idx,off,scale, 0,rhs); if( rhs instanceof LoadNode ld && ld.nOuts()==1 ) - throw Utils.TODO(); // Swap load sides + return new AddMemX86(add,address(ld),ld.ptr(),idx,off,scale, 0,lhs); // Attempt a full LEA-style break down. // Returns one of AddX86, AddIX86, LeaX86, or LHS diff --git a/chapter20/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter20/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index d0df57fa..7a2025da 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -67,7 +67,7 @@ public class SimpleGraphAdapter extends GraphAdapter<Node> { return n instanceof CFGNode ? 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( n instanceof MemMergeNode || i == 1 && (n instanceof MemOpNode || n instanceof ReturnNode || n instanceof CallNode) ) return Role.MEM; + if( n instanceof MemMergeNode || (i == 1 && (n instanceof MemOpNode || n instanceof ReturnNode || n instanceof CallNode || n instanceof NewNode)) ) return Role.MEM; Node def = n.in(i); if( def != null && isMem(def) ) return Role.MEM; return Role.DATA; diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter10Test.java index 14cf6e01..7da1354f 100644 --- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter10Test.java +++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter10Test.java @@ -2,6 +2,8 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.codegen.CodeGen; import org.junit.Test; +import com.seaofnodes.simple.node.*; +import java.util.BitSet; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -450,4 +452,121 @@ return 0; assertEquals("return 0;", code.print()); } + @Test public void testReadBeforeStores() { + var code = new CodeGen(""" + 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().opto(); + StopNode stop = code._stop; + assertEquals("1133",Eval2.eval(code,0)); + assertEquals("2244",Eval2.eval(code,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 code = new CodeGen(NESTED_MEMORY).parse().opto(); + StopNode stop = code._stop; + for (int arg=0; arg<7; arg++) { + assertEquals("S{x="+(5L+arg)+",y="+(7L+arg*(arg+1)-(arg>=2 ? 2 : 0))+ + ",z="+(11L+2*arg-(arg>=2 ? 2 : 0))+"}",Eval2.eval(code,arg)); + } + } + + @Test public void testMemoryAtEarlyReturns() { + var code = new CodeGen(""" + 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().opto(); + StopNode stop = code._stop; + for (int arg=0; arg<2; arg++) { + assertEquals(arg==0 ? "S{x=3,y=17,z=7}" : "S{x=11,y=5,z=13}",Eval2.eval(code,arg)); + } + } + + @Test public void testKeepLoadsAtMerge() { + var code = new CodeGen(""" + struct S { int x; }; + S !a = new S; S !b = new S; + a.x = arg; b.x = arg+1; + S !p = a; S !q = b; + if (arg<0) { p=b; q=a; } + int v; + if (arg>1) v=p.x; else v=q.x; + return v; + """).parse().opto(); + StopNode stop = code._stop; + // The one-step safety check stops at the memory aggregate. + assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet())); + assertEquals("-1",Eval2.eval(code,-1)); + assertEquals("1",Eval2.eval(code,0)); + assertEquals("3",Eval2.eval(code,3)); + } + + @Test public void testDropStores() { + var code = new CodeGen(""" + struct S { int x; }; + S !s = new S; + if (arg) s.x=arg+1; else s.x=arg+2; + return s; + """).parse().opto(); + StopNode stop = code._stop; + assertEquals(1,countMemoryNodes(stop,StoreNode.class,new BitSet())); + assertEquals("S{x=2}",Eval2.eval(code,0)); + assertEquals("S{x=4}",Eval2.eval(code,3)); + } + + @Test public void testKeepReadsBeforeWrites() { + var code = new CodeGen(""" + struct S { int x; int y; }; + S !a = new S; S !b = new S; + a.x=arg; b.x=arg+1; + S !p=a; S !q=b; + if (arg<0) { p=b; q=a; } + int v; + if (arg>1) { v=p.x; p.x=41; p.y=5; } + else { v=q.x; q.x=42; } + return v; + """).parse().opto(); + StopNode stop = code._stop; + assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet())); + assertEquals("-1",Eval2.eval(code,-1)); + assertEquals("1",Eval2.eval(code,0)); + assertEquals("3",Eval2.eval(code,3)); + } + + private static int countMemoryNodes(Node n, Class<?> kind, BitSet seen) { + if (n==null || seen.get(n._nid)) return 0; + seen.set(n._nid); + int cnt=kind.isInstance(n) ? 1 : 0; + for (Node def : n._inputs) cnt+=countMemoryNodes(def,kind,seen); + return cnt; + } } diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter10bTest.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter10bTest.java new file mode 100644 index 00000000..5d78ae06 --- /dev/null +++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter10bTest.java @@ -0,0 +1,60 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import org.junit.Test; +import static org.junit.Assert.*; + +public class Chapter10bTest { + @Test public void testIndependentField() { + var code = new CodeGen(""" + 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().opto(); + var stop = code._stop; + assertEquals("return 42;",stop.toString()); + assertEquals("42",Eval2.eval(code,5)); + assertNull(stop.walk(n -> n instanceof LoadNode ? n : null)); + } + + @Test public void testParallelMemoryPhis() { + var code = new CodeGen(""" + 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().opto(); + var stop = code._stop; + 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) && !(n instanceof ParmNode)); + return null; + }); + assertTrue(phis[0]>=3); + assertEquals("S{x=1,y=2,z=3}",Eval2.eval(code,4)); + } + + @Test public void testLateSliceWorklist() { + // 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 code = new CodeGen(Chapter10Test.NESTED_MEMORY,com.seaofnodes.simple.type.TypeInteger.BOT,seed).parse(); + code.opto(); + assertEquals("S{x=9,y=25,z=17}",Eval2.eval(code,4)); + } + } +} diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter16Test.java index 5899763f..571a0d54 100644 --- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter16Test.java +++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter16Test.java @@ -255,4 +255,27 @@ return new Square; assertEquals("Square{side=3.0,diag=1.7320508075688772}", Eval2.eval(code, 3)); assertEquals("Square{side=4.0,diag=2.0}", Eval2.eval(code, 4)); } + static final String CONSTRUCTOR_MEMORY = """ + struct S { int x; int y; }; + struct T { int z=arg+40; }; + T !t = new T; + S !a = new S { x=11; y=7; }; + S !b = new S { x=22; y=9; }; + S !p=a; + if (arg) p=b; + int before=p.x; + S !c = new S { + x=p.x+1; + { int i=0; while (i<2) { p.y=p.y+1; i=i+1; } } + y=p.y; + }; + p.x=33; + return before*10000+c.x*100+c.y+t.z; + """; + + @Test public void testConstructorMemory() { + var code = new CodeGen(CONSTRUCTOR_MEMORY).parse().opto(); + assertEquals("111249",Eval2.eval(code,0)); + assertEquals("222352",Eval2.eval(code,1)); + } } diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter18Test.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter18Test.java index ddf4a263..b9ca9cd3 100644 --- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter18Test.java +++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter18Test.java @@ -446,4 +446,70 @@ return p.coffee_count; assertEquals("1", Eval2.eval(code, 2)); } + static final String CALL_MEMORY = """ + struct S { int x; int y; }; + val bump = { S s, int d -> int old=s.x; s.x=old+d; return old; }; + S !a = new S { x=10; y=7; }; + S !b = new S { x=20; y=9; }; + S !p=a; if (arg) p=b; + int before=p.x; + int old=bump(p,3); + int after=p.x; + bump(a,5); + return before*1000000+old*10000+after*100+a.x+a.y; + """; + + @Test public void testCallMemory() { + var code = new CodeGen(CALL_MEMORY).parse().opto(); + int[] calls={0}; + code._stop.walk(n -> { if(n instanceof CallNode) calls[0]++; return null; }); + assertEquals(2,calls[0]); // Keep real calls so this checks scheduling across them. + assertEquals("10101325",Eval2.eval(code,0)); + assertEquals("20202322",Eval2.eval(code,1)); + code._stop.walk(n -> { + if(n instanceof CallNode call) { + assertEquals(5,call.nIns()); // No scheduling edges may become extra arguments. + assertTrue(call.fptr()._type instanceof TypeFunPtr); + } + return null; + }); + } + + static final String RECURSIVE_MEMORY = """ + struct S { int x; }; + val rec = { S s, int n -> + if (n==0) return s.x; + int before=s.x; + s.x=before+1; + return before+rec(s,n-1); + }; + S !s=new S { x=10; }; + int value=rec(s,arg); + return value*100+s.x; + """; + + @Test public void testRecursiveMemory() { + var code = new CodeGen(RECURSIVE_MEMORY).parse().opto(); + for(int n=0; n<7; n++) + assertEquals(Long.toString((10L*(n+1)+n*(n+1)/2)*100+10+n),Eval2.eval(code,n)); + } + + static final String INLINE_MEMORY = """ + struct S { int x; int y; }; + val make = { S s -> + S t = new S { x=s.x+1; y=3; }; + s.y=9; + return t; + }; + S !a = new S { x=arg+40; y=7; }; + S t=make(a); + return a.x*10000+a.y*100+t.x+t.y; + """; + + @Test public void testInlineAllocationMemory() { + var code = new CodeGen(INLINE_MEMORY).parse().opto(); + assertNull(code._stop.walk(n -> n instanceof CallNode ? n : null)); + assertEquals("400944",Eval2.eval(code,0)); + assertEquals("420946",Eval2.eval(code,2)); + } } diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter19Test.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter19Test.java index 95fbe3f5..a7321c96 100644 --- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter19Test.java +++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter19Test.java @@ -419,4 +419,71 @@ return sq(arg) + sq(3); code.parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2", "SystemV").GCM().localSched(); assertEquals("Stop[ return (add,#2,#2); return (mul,Parm_x(sq,int),x); ]", code.print()); } + + @Test public void testSelectedMemory() { + for( String cpu : new String[]{"x86_64_v2","riscv","arm"} ) + for( String src : new String[]{Chapter10Test.NESTED_MEMORY,Chapter16Test.CONSTRUCTOR_MEMORY, + Chapter18Test.CALL_MEMORY,Chapter18Test.RECURSIVE_MEMORY} ) { + var code = new CodeGen(src).parse().opto().typeCheck(); + var aliases = memoryPhis(code._stop); + code.instSelect(PORTS,cpu,"SystemV"); + assertEquals(aliases,memoryPhis(code._stop)); + code.GCM().localSched(); + code._stop.walk(n -> { + if( !(n instanceof CFGNode) && !(n instanceof ProjNode) ) + assertTrue(n.in(0) instanceof CFGNode); + if( n instanceof NewNode nn ) { + assertEquals(2,((TypeTuple)nn._type)._types.length); + assertTrue(nn.mem() instanceof MemMergeNode); + assertNull(nn.mem().in(1)); // Partial allocation input. + MachNode mach = (MachNode)nn; + assertNull(mach.regmap(1)); + assertNotNull(mach.regmap(2)); + assertNotNull(mach.outregmap(0)); + assertNull(mach.outregmap(1)); + } + if( n instanceof CallNode call ) { + assertEquals(4,call.nIns()); // ctrl, memory, two args; direct target is embedded. + } + return null; + }); + } + } + + private static java.util.ArrayList<String> memoryPhis(Node stop) { + var phis = new java.util.ArrayList<String>(); + stop.walk(n -> { + if( n instanceof MemPhiNode phi ) phis.add("alias:"+phi._alias); + if( n instanceof BulkMemPhiNode phi ) phis.add("bulk:"+phi._aliases); + assertFalse(n instanceof PhiNode && n.isMem() && + !(n instanceof MemPhiNode) && !(n instanceof BulkMemPhiNode) && !(n instanceof ParmNode)); + return null; + }); + java.util.Collections.sort(phis); + return phis; + } + + @Test public void testFoldedReadBeforeWrites() { + var code = new CodeGen(""" + struct S { int x; }; + S !a = new S { x=11; }; S !b = new S { x=22; }; + S !p = a; if( arg ) p = b; + int before = p.x + arg; + a.x=33; b.x=44; + return before; + """).parse().opto().typeCheck().instSelect(PORTS,"x86_64_v2","SystemV").GCM().localSched(); + var read = (MemOpNode)code._stop.walk(n -> + n instanceof com.seaofnodes.simple.node.cpus.x86_64_v2.AddMemX86 ? n : null); + assertNotNull(read); + int writes = 0; + for( Node use : read.antiDeps() ) + if( use instanceof MemOpNode st && st.isMem() ) { + assertSame(read.cfg0(),st.cfg0()); + assertTrue(st._inputs.find(read)>=0); + assertTrue(read.cfg0()._outputs.find(read)<st.cfg0()._outputs.find(st)); + assertNull(((MachNode)st).regmap(st._inputs.find(read))); + writes++; + } + assertEquals(1,writes); // The next Store follows this first clobber through memory. + } } diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter20Test.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter20Test.java index 71a4bc36..7cdcd739 100644 --- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter20Test.java +++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter20Test.java @@ -157,7 +157,7 @@ for( int i=0; i<ary#-1; i++ ) ary[i+1] += ary[i]; return ary[1] * 1000 + ary[3]; // 1 * 1000 + 6 """; - testTarget(src,"x86_64_v2", "SystemV",5,"return .[];"); + testTarget(src,"x86_64_v2", "SystemV",13,"return .[];"); testTarget(src,"riscv" , "SystemV",1,"return (add,.[],(mul,.[],1000));"); testTarget(src,"arm" , "SystemV",1,"return (add,.[],(muli,.[]));"); } @@ -202,7 +202,7 @@ hashCode(s); """; testTarget(src,"x86_64_v2", "SystemV",21,null); testTarget(src,"riscv" , "SystemV", 3,null); - testTarget(src,"arm" , "SystemV", 5,null); + testTarget(src,"arm" , "SystemV", 3,null); } @Test @@ -245,4 +245,25 @@ return arg; testTarget(src,"riscv" , "SystemV",2,null); testTarget(src,"arm" , "SystemV",2,null); } + + @Test public void testMemoryAllocation() { + for( String cpu : new String[]{"x86_64_v2","riscv","arm"} ) + for( String src : new String[]{Chapter10Test.NESTED_MEMORY,Chapter16Test.CONSTRUCTOR_MEMORY, + Chapter18Test.CALL_MEMORY,Chapter18Test.RECURSIVE_MEMORY, + "int[] !a = new int[3]; a[arg]=arg; return a[0]+1;"} ) { + var code = new CodeGen(src).parse().opto().typeCheck() + .instSelect(PORTS,cpu,"SystemV").GCM().localSched().regAlloc(); + com.seaofnodes.simple.codegen.RegAllocTestSupport.checkRegisters(code); + code._stop.walk(n -> { + // Whole memory, precise Phis, and memory projections need no register. + if( n._type instanceof com.seaofnodes.simple.type.TypeMem ) + assertEquals(-1,code._regAlloc.regnum(n)); + if( n instanceof com.seaofnodes.simple.node.NewNode nn ) { + assertTrue(code._regAlloc.regnum(nn.proj(0))>=0); + assertEquals(-1,code._regAlloc.regnum(nn.proj(1))); + } + return null; + }); + } + } } diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Eval2.java b/chapter20/src/test/java/com/seaofnodes/simple/Eval2.java index 3e1954aa..b55607df 100644 --- a/chapter20/src/test/java/com/seaofnodes/simple/Eval2.java +++ b/chapter20/src/test/java/com/seaofnodes/simple/Eval2.java @@ -329,7 +329,7 @@ public abstract class Eval2 { private static Object alloc(NewNode alloc) { TypeStruct type = alloc._ptr._obj; if( type.isAry() ) { - long sz = (Long)val(alloc.in(1)); + long sz = (Long)val(alloc.size()); long x = offToIdx(sz, type); int n = (int)x; if( n!=x || n<0 ) @@ -442,9 +442,9 @@ public abstract class Eval2 { case TypeRPC rpc -> sb.p(x.toString()); case TypeMem mem -> sb.p("$mem"); case TypeTuple tt -> { - if( tt._types.length>1 && tt._types[1] instanceof TypeMemPtr ) + if( tt._types.length>1 && tt._types[0] instanceof TypeMemPtr ) // Assume a NewNode - yield _print( tt._types[1], x, sb, visit ); + yield _print( tt._types[0], x, sb, visit ); throw Utils.TODO(); } case Type tt -> { diff --git a/chapter20/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/chapter20/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java index a3b87e90..3f023bb2 100644 --- a/chapter20/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java +++ b/chapter20/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java @@ -185,7 +185,7 @@ public class Evaluator { Object[] body=null; int num; if( type.isAry() ) { - long sz = (Long)val(alloc.in(1)); + long sz = (Long)val(alloc.size()); long n = offToIdx(sz, type); if( n < 0 ) throw new NegativeArraySizeException(""+n); @@ -199,21 +199,17 @@ public class Evaluator { assert elem instanceof TypeMemPtr; } // Length value - body[0] = vall(alloc.in(2+2)); + body[0] = n; } else { body = new Object[num = type._fields.length]; for( int i=0; i<num; i++ ) body[i] = switch( alloc._ptr._obj._fields[i]._type ) { case TypeInteger ti -> 0L; - case TypeFloat tf -> 0; + case TypeFloat tf -> 0D; default -> null; }; } - Object[] mems = new Object[type._fields.length+2]; - // mems[0] is control - mems[1] = new Obj(type,body); // the ref - // mems[2+...] are memory aliases - return mems; + return new Object[]{new Obj(type,body),null}; } private Object load(LoadNode load) { diff --git a/chapter21/README.md b/chapter21/README.md index 21f2febf..3e082630 100644 --- a/chapter21/README.md +++ b/chapter21/README.md @@ -74,6 +74,23 @@ relocation are two stages of the same obligation to make references correct. ## Execute the result +Chapter 10b's lazy memory partitioning now carries through encoding. Parameters, +calls, and returns carry whole memory; `BulkMemPhi`, `MemPhi`, and `MemMerge` +discover precise aliases inside each function. New consumes `{ctrl, $mem, size}` +and produces `{ptr, $mem}`. Its memory covers the allocated struct's aliases; +the surrounding merge retains unrelated aliases. Nonzero initialization still +uses explicit Stores. Memory nodes and ordering edges require no registers. + +Scheduling follows aliases through aggregates to order reads before matching +Stores, allocations, and calls. Calls retain their argument lists, with no added +ordering operands. Synthetic loop exits collect precise memory by alias. + +The execution regressions cover constructors, non-inlined and recursive calls, +inlining, and array updates, using native x86 and both emulators. A full-width +x86 load uses the allocated register bank when choosing its encoding, since an +integer may reside in XMM. ARM emulation handles MOVZ/MOVK halfword construction +for the large constants in these checks. + `make tests` includes C harnesses that link and run generated x86 code, and RISC-V/AArch64 emulator checks. The tests compare results as well as spill counts. Native process failures retain their full exit status. Emulator checks must read @@ -85,7 +102,7 @@ is recorded without skipping the remaining targets or execution checks; any such mismatch still makes the command fail. Structural allocator regressions are excluded from the spill totals. -## RegAlloc improvements: conservative copy coalescing +## RegAlloc improvements: eliminating copies A split inserts a copy between two live ranges. If both can safely become one range, the copy can disappear before coloring. [Coalesce.java](src/main/java/com/seaofnodes/simple/codegen/Coalesce.java) @@ -99,6 +116,13 @@ to the deleted copy. A rejected merge restores the original adjacency list. Mask compatibility alone is insufficient: two values that are simultaneously live cannot share a register merely because both permit it. +After coloring, a copy with one use in the immediately following instruction +can also disappear when that operand accepts the source register and is not +its two-address operand. For example, `mov rpc=s8; st1 [rpc+4],s1` becomes +`st1 [s8+4],s1`. Adjacency proves that nothing overwrites the source in between. +Phi and control-flow users are excluded. This local cleanup leaves spill +selection unchanged; it complements coalescing without another CFG analysis. + Chapter 20's legality and progress fixes carry forward, including compatible rematerialization and clobber-aware copy reuse. Chapter 21 retains its native frame and ABI support. Stronger color bias and cheap-spill ranking are reserved @@ -110,11 +134,11 @@ These are measured sums with **this chapter's compiler**, default optimizer seed moves; it is not necessarily a memory spill. Weighted counts multiply each split by `8^loopDepth`. -| Test cohort | Allocations | Splits | Loop-weighted splits | +| Program cohort | Compilations | Retained moves | Loop-weighted moves | |---|---:|---:|---:| -| Chapter 20 | 39 | 379 | 484 | -| Chapter 21 | 52 | 461 | 972 | -| Total | 91 | 840 | 1,456 | +| Chapter 20 | 39 | 367 | 472 | +| Chapter 21 | 52 | 454 | 972 | +| **Total** | **91** | **821** | **1,444** | The Chapter 20 row freezes all 13 original inputs, including BrainFuck and MergeSort, on three SystemV targets. They live in `Chapter20Test`. The revised @@ -124,10 +148,47 @@ the Chapter 21 row. That row includes 17 ARM/SystemV, 17 RISC-V/SystemV, eight x86/SystemV, and ten x86/Win64 compilations. Native host ABI changes affect this row; compare the same host and targets. -| Controlled comparison, same 91 compilations | Splits | Loop-weighted splits | +Before adjacent-copy forwarding, the memory port changed the previous total of 840 / 1,456 to 845 / 1,559, +with identical cohort membership and allocator heuristics. These are all changed +rows; each entry is retained / loop-weighted splits: + +| Cohort / program | Target / ABI | Before | Lazy memory | +|---|---|---:|---:| +| 20 / MergeSort | ARM / SystemV | 44 / 44 | 41 / 41 | +| 20 / BrainFuck | RISC-V / SystemV | 35 / 42 | 37 / 58 | +| 20 / Alloc2 | x86 / SystemV | 3 / 3 | 4 / 4 | +| 20 / Alloc2 | ARM / SystemV | 10 / 10 | 9 / 9 | +| 20 / String | x86 / SystemV | 7 / 14 | 8 / 15 | +| 20 / String | RISC-V / SystemV | 14 / 14 | 10 / 10 | +| 20 / String | ARM / SystemV | 13 / 13 | 10 / 10 | +| 21 / Sieve | x86 / Win64 | 24 / 178 | 24 / 185 | +| 21 / Sieve | RISC-V / SystemV | 19 / 89 | 22 / 92 | +| 21 / Sieve | ARM / SystemV | 23 / 93 | 21 / 91 | +| 21 / BrainFuck | RISC-V / SystemV | 35 / 42 | 46 / 130 | + +RISC-V BrainFuck accounts for most of the weighted increase. A diagnostic run +omitting only read-before-New ordering changes its two rows to 35 / 42 and +37 / 58. That explains most of the cost; the compiler retains the ordering +constraint. No heuristic tuning is included in the memory port. + +The subsequent Load-search improvement folds BrainFuck's fixed program length +through the loop's N-way memory merge. Every backedge arm must fold or return +to unchanged memory for the same pointer. It removes 907 executed heap loads +in the RISC-V Hello World run and reduces stack traffic from 1,006 loads / 572 +stores to 15 / 15. Before copy forwarding, that row had 144 / 165 moves / weighted moves, +and the complete 91-entry total was 943 / 1,594. Adjacent-copy forwarding +reduces these to 35 / 42 and 821 / 1,444 respectively. The RISC-V Hello World +run executes 21,787 instructions instead of 21,920, with unchanged heap and +stack traffic. These are emulator instruction counts, not hardware timings. +The current native BrainFuck test and all runtime assertions are enabled. + +The following coalescing comparison predates lazy memory. Both sides use the +same compiler from that audit; it is not a fresh ablation of the current graph: + +| Earlier controlled comparison, same 91 compilations | Splits | Loop-weighted splits | |---|---:|---:| -| Current compiler with coalescing disabled | 1,070 | 1,889 | -| Current compiler with coalescing | 840 | 1,456 | +| Audit compiler with coalescing disabled | 1,070 | 1,889 | +| Audit compiler with coalescing | 840 | 1,456 | The controlled run disables only `Coalesce.coalesce` in `graphColor`. Allocation, register constraints, and runtime checks pass in both runs. The disabled run diff --git a/chapter21/src/main/java/com/seaofnodes/simple/Parser.java b/chapter21/src/main/java/com/seaofnodes/simple/Parser.java index fb108447..3b502c44 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/Parser.java @@ -138,7 +138,7 @@ public class Parser { _scope.define(ScopeNode.ARG0, TypeInteger.BOT, false, null, _lexer); ctrl(XCTRL); - _scope.mem(new MemMergeNode(false)); + _scope.mem(con(TypeMem.BOT)); // Parse whole program, as-if function header "{ int arg -> body }" parseFunctionBody(_code._main,loc(),"arg"); @@ -196,7 +196,7 @@ public class Parser { // Build a multi-exit return point for all function returns RegionNode r = new RegionNode((Lexer)null,null,null).init(); assert r.inProgress(); - PhiNode rmem = new PhiNode(ScopeNode.MEM0,TypeMem.BOT,r,null).init(); + PhiNode rmem = new BulkMemPhiNode(ScopeNode.MEM0,r,null).init(); PhiNode rrez = new PhiNode(ScopeNode.ARG0,Type.BOTTOM,r,null).init(); ReturnNode ret = new ReturnNode(r, rmem, rrez, rpc, fun).init(); fun.setRet(ret); @@ -208,11 +208,8 @@ public class Parser { // external calls. _scope.push(ScopeNode.Kind.Function); ctrl(fun); // Scope control from function - // Private mem alias tracking per function - MemMergeNode mem = new MemMergeNode(true); - mem.addDef(null); // Alias#0 - mem.addDef(new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,con(TypeMem.BOT)).peephole()); // All aliases - _scope.mem(mem); + // Unknown callers supply whole memory; field aliases are discovered lazily. + _scope.mem(new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,con(TypeMem.BOT)).peephole()); // All args, "as-if" called externally for( int i=0; i<ids.length; i++ ) { Type t = sig.arg(i); @@ -226,7 +223,7 @@ public class Parser { // Last expression is the return if( ctrl()._type==Type.CONTROL ) - fun.addReturn(ctrl(), _scope.mem().merge(), last); + fun.addReturn(ctrl(), _scope.mem(), last); // Pop off the inProgress node on the multi-exit Region merge assert r.inProgress(); @@ -586,9 +583,8 @@ public class Parser { */ private Node parseReturn() { var expr = require(parseAsgn(), ";"); - // Need default memory, since it can be lazy, need to force - // a non-lazy Phi - _fun.addReturn(ctrl(), _scope.mem().merge(), expr); + // Force the lazy whole-memory Phi before closing this exit. + _fun.addReturn(ctrl(), _scope.mem(), expr); ctrl(XCTRL); // Kill control return expr; } @@ -1201,29 +1197,31 @@ public class Parser { Field[] fs = obj._fields; if( fs==null ) throw error("Unknown struct type '" + obj._name + "'"); - int len = fs.length; - Node[] ns = new Node[2+len]; - ns[0] = ctrl(); // Control in slot 0 - // Total allocated length in bytes - ns[1] = size; - // Memory aliases for every field - for( int i = 0; i < len; i++ ) - ns[2+i] = memAlias(fs[i]._alias); - Node nnn = new NewNode(TypeMemPtr.make(obj), ns).peephole().keep(); - for( int i = 0; i < len; i++ ) - memAlias(fs[i]._alias, new ProjNode(nnn,i+2,memName(fs[i]._alias)).peephole()); - Node ptr = new ProjNode(nnn.unkeep(),1,obj._name).peephole().keep(); - - // Initial nonzero values for every field - for( int i = 0; i < len; i++ ) { - Node val = init.get( i + idx ); + Node prior = mem().keep(); + MemMergeNode input = new MemMergeNode((Node)null); + for( Field f : fs ) input.alias(f._alias,prior); + // Allocation zeroes its fields; nonzero initializers become explicit Stores. + Node[] ns = new Node[3]; + ns[0] = ctrl(); + ns[1] = input.peephole(); + ns[2] = size; + Node nnn = new NewNode(TypeMemPtr.make(obj),ns).peephole().keep(); + Node ptr = new ProjNode(nnn,0,obj._name).peephole().keep(); + Node out = new ProjNode(nnn,1,"$mem").peephole().keep(); + MemMergeNode after = new MemMergeNode(prior); + for( Field f : fs ) after.alias(f._alias,out); + mem(after.peephole()); + out.unkeep(); + nnn.unkeep(); + prior.unkeep(); + for( int i=0; i<fs.length; i++ ) { + Node val = init.get(idx+i); if( val._type != val._type.makeZero() ) { - Node mem = memAlias(fs[i]._alias); - Node st = new StoreNode(loc(),fs[i]._fname,fs[i]._alias,fs[i]._type,mem,ptr,con(obj.offset(i)),val,true).peephole(); - memAlias(fs[i]._alias,st); + Field f = fs[i]; + Node st = new StoreNode(loc(),f._fname,f._alias,f._type,mem(),ptr,con(obj.offset(i)),val,true).peephole(); + mem(new MemMergeNode(mem(),f._alias,st).peephole()); } } - return ptr.unkeep(); } @@ -1236,13 +1234,18 @@ public class Parser { return newStruct(ary,size,0,ALTMP); } - // 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.mem(alias ); } - private void memAlias(int alias, Node st) { _scope.mem(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.mem(); } + private void mem(Node n) { _scope.mem(n); } + + private void store(String name, int alias, Type glb, Node ptr, Node off, Node val, boolean init, Node ctrl) { + Node prior = mem().keep(); + Node st = new StoreNode(loc(),name,alias,glb,prior,ptr,off,val,init); + st.setDef(0,ctrl); + st = st.peephole(); + mem(new MemMergeNode(prior,alias,st).peephole()); + prior.unkeep(); + } /** * Parse postfix expression; this can be a field expression, an array @@ -1285,6 +1288,7 @@ public class Parser { if( tf instanceof TypeMemPtr ftmp && ftmp.isFRef() ) tf = ftmp.makeFrom(((TypeMemPtr)(TYPES.get(ftmp._obj._name)))._obj); + expr.keep(); // Field offset; fixed for structs, computed for arrays Node off = (name.equals("[]") // If field is an array body // Array index math @@ -1297,16 +1301,11 @@ public class Parser { Node val = parseAsgn().keep(); Node lift = liftExpr( val, tf, f._final ); - Node st = new StoreNode(loc(), name, f._alias, tf, memAlias(f._alias), expr, off.unkeep(), lift, false); - // Arrays include control, as a proxy for a safety range check. - // Structs don't need this; they only need a NPE check which is - // done via the type system. - if( base.isAry() ) st.setDef(0,ctrl()); - memAlias(f._alias, st.peephole()); + store(name,f._alias,tf,expr.unkeep(),off.unkeep(),lift,false,base.isAry() ? ctrl() : null); return val.unkeep(); // "obj.a = expr" returns the expression while updating memory } - Node load = new LoadNode(loc(),name, f._alias, tf, memAlias(f._alias), expr.keep(), off); + Node load = new LoadNode(loc(),name, f._alias, tf, mem(), expr, off); // Arrays include control, as a proxy for a safety range check // Structs don't need this; they only need a NPE check which is // done via the type system. @@ -1317,12 +1316,7 @@ public class Parser { char ch = _lexer.matchOperAssign(); if( ch!=0 ) { Node op = opAssign(ch,load,f._final,tf,name); - Node st = new StoreNode(loc(), name, f._alias, tf, memAlias(f._alias), expr.unkeep(), off, op, false); - // Arrays include control, as a proxy for a safety range check. - // Structs don't need this; they only need a NPE check which is - // done via the type system. - if( base.isAry() ) st.setDef(0,ctrl()); - memAlias(f._alias, peep(st)); + store(name,f._alias,tf,expr.unkeep(),off,op,false,base.isAry() ? ctrl() : null); load = postfix(ch) ? load.unkeep() : op; // And use the original loaded value as the result } else expr.unkill(); @@ -1403,7 +1397,7 @@ public class Parser { } // Control & memory after parsing args args.set(0,ctrl().keep()); - args.set(1,_scope.mem().merge().keep()); + args.set(1,_scope.mem().keep()); args.push(expr); // Function pointer // Unkeep them all for( Node arg : args ) @@ -1424,11 +1418,8 @@ public class Parser { call.peephole(); // Rerun peeps after CallEnd, allows early inlining // Control from CallEnd ctrl(new CProjNode(cend,0,ScopeNode.CTRL).peephole()); - // Memory from CallEnd - MemMergeNode mem = new MemMergeNode(true); - mem.addDef(null); // Alias#0 - mem.addDef(new ProjNode(cend,1,ScopeNode.MEM0).peephole()); - _scope.mem(mem); + // A call may update any alias, so its memory result is the new whole state. + _scope.mem(new ProjNode(cend,1,ScopeNode.MEM0).peephole()); // Call result return new ProjNode(cend,2,"#2").peephole(); } diff --git a/chapter21/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java b/chapter21/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java index 064d5820..2c44cb05 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java @@ -429,7 +429,7 @@ public class CodeGen { @Override public String toString() { return _phase.ordinal() > Phase.Schedule.ordinal() ? IRPrinter._prettyPrint( this ) - : _stop.p(9999); + : IRPrinter.prettyPrint(this); } // Debugging helper diff --git a/chapter21/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java b/chapter21/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java index 5c119251..9945374a 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java @@ -51,7 +51,7 @@ public abstract class GlobalCodeMotion { var cons = new ArrayList<Node>(); for( Node con : start._outputs ) if( con!=null && !(con instanceof CFGNode) && - (con.isConst() || con instanceof MachNode mach && mach.outregmap()!=null) ) { + (con.isConst() || (con instanceof MachNode mach && mach.outregmap()!=null)) ) { globals.put(con,true); cons.add(con.keep()); // Preserve original inputs while rewiring users. } @@ -173,16 +173,8 @@ public abstract class GlobalCodeMotion { // Loads need their memory inputs' uses also done if( n instanceof MemOpNode ld && ld._isLoad ) - for( Node memuse : ld.mem()._outputs ) - if( late[memuse._nid]==null && - // New makes new memory, never crushes load memory - !(memuse instanceof NewNode) && - // Load-use directly defines memory - (memuse._type instanceof TypeMem || - // Load-use directly defines memory - memuse instanceof CallNode || - // Load-use indirectly defines memory - (memuse._type instanceof TypeTuple tt && tt._types[ld._alias] instanceof TypeMem)) ) + for( Node memuse : ld.antiDeps() ) + if( late[memuse._nid]==null ) continue outer; // All uses done, schedule @@ -194,9 +186,7 @@ public abstract class GlobalCodeMotion { for( Node def : n._inputs ) { if( def==null ) continue; if( late[def._nid]==null ) work.push(def); - for( Node out : def._outputs ) - if( out instanceof MemOpNode ld && ld._isLoad && late[ld._nid]==null ) - work.push(ld); + wakeLoads(def,late,work,new BitSet()); } if( n instanceof LoopNode loop ) for( Node phi : loop._outputs ) @@ -256,23 +246,32 @@ public abstract class GlobalCodeMotion { best instanceof IfNode; } + // An allocation may consume a load's memory through a partial aggregate. + private static void wakeLoads(Node def, CFGNode[] late, WorkList<Node> work, BitSet visit) { + if( visit.get(def._nid) ) return; + visit.set(def._nid); + for( Node out : def._outputs ) + if( out instanceof MemOpNode ld && ld._isLoad && late[ld._nid]==null ) work.push(ld); + if( def instanceof MemMergeNode ) + for( int i=1; i<def.nIns(); i++ ) + if( def.in(i)!=null ) wakeLoads(def.in(i),late,work,visit); + } + private static CFGNode find_anti_dep(CFGNode lca, MemOpNode load, CFGNode early, CFGNode[] late, int[] anti) { // We could skip final-field loads here. // Walk LCA->early, flagging Load's block location choices for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() ) anti[cfg._nid] = load._nid; // Walk load->mem uses, looking for Stores causing an anti-dep - for( Node mem : load.mem()._outputs ) { + for( Node mem : load.antiDeps() ) { switch( mem ) { case MemOpNode st: - if( !st._isLoad ) { - assert late[mem._nid] != null; - lca = anti_dep(load,late[mem._nid],lca,st,anti); - } - break; // Loads do not cause anti-deps on other loads - case CallNode call: - assert late[call._nid]!=null; - lca = anti_dep(load,late[call._nid],lca,call,anti); + assert late[st._nid]!=null; + lca = anti_dep(load,late[st._nid],lca,st,anti); + break; + case CallNode st: + assert late[st._nid]!=null; + lca = anti_dep(load,late[st._nid],lca,st,anti); break; case PhiNode phi: // Repeat anti-dep for matching Phi inputs. @@ -281,10 +280,10 @@ public abstract class GlobalCodeMotion { if( phi.in(i)==load.mem() ) lca = anti_dep(load,phi.region().cfg(i),lca,null,anti); break; - case NewNode st: break; - case ReturnNode ret: break; // Load must already be ahead of Return - case MemMergeNode ret: break; // Mem uses now on ScopeMin - case NeverNode never: break; + case NewNode st: + assert late[st._nid]!=null; + lca = anti_dep(load,late[st._nid],lca,st,anti); + break; default: throw Utils.TODO(); } } @@ -296,7 +295,7 @@ public abstract class GlobalCodeMotion { // Stores are already placed. Constrain the load at that block only. if( anti[stblk._nid]==load._nid ) { lca = stblk.domLCA(lca,null); - if( lca==stblk && st!=null && st._inputs.find(load) == -1 ) + if( lca==stblk && st!=null && !(st instanceof CallNode) && st._inputs.find(load) == -1 ) st.addDef(load); // Same-block load must precede the store. } return lca; diff --git a/chapter21/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java b/chapter21/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java index 5d6ba060..fc60a385 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java @@ -535,8 +535,8 @@ public class RegAlloc { if( defreg != usereg && splitBypass(bb,j,n,defreg) ) usereg = lrg(n.in(1))._reg; - // Split has same reg? Useless! Can remove it! - if( defreg == usereg ) { + // Same register, or the next instruction can use the source directly. + if( defreg == usereg || splitForward(bb,j,n,usereg) ) { n.removeSplit(); j--; continue; @@ -549,6 +549,18 @@ public class RegAlloc { } } + // A sole, adjacent use can read the source register directly. Nothing can + // clobber it in between; only the operand mask and two-address tie remain. + private boolean splitForward( CFGNode bb, int j, Node split, int reg ) { + if( split.nOuts()!=1 || j+1==bb.nOuts() ) return false; + Node use = split.out(0); + if( use!=bb.out(j+1) || use instanceof PhiNode || use instanceof CFGNode || + !(use instanceof MachNode mach) ) return false; + int i = use._inputs.find(split); + RegMask mask = mach.regmap(i); + return mask!=null && mask.test(reg) && mach.twoAddress()!=i; + } + private boolean splitBypass( CFGNode bb, int j, Node lo, int defreg ) { // Attempt to bypass split Node hi = lo.in(1); diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java new file mode 100644 index 00000000..7d4f1fb8 --- /dev/null +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java @@ -0,0 +1,240 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.codegen.CodeGen; +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); } + + public BulkMemPhiNode(BulkMemPhiNode phi) { super(phi); _aliases = phi._aliases; } + + @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,CodeGen.CODE.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 mmm.in(1)!=null && missingAlias(mmm)==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 ParmNode parm -> 0; + case ConstantNode con -> 0; + case MemMergeNode mmm -> missingAlias(mmm); + 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. + addDepForwards(use); + return switch(use) { + case ScopeNode scope -> 0; + case MemMergeNode mmm -> missingAlias(mmm); + case BulkMemPhiNode bulk -> missingAlias(bulk); + case MemOpNode mem -> unsplit(mem._alias); + case MemPhiNode phi -> unsplit(phi._alias); + default -> { + assert use instanceof ReturnNode || use instanceof CallNode || use instanceof ParmNode + : "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; + } + + // Every explicit slot requests that alias, including a partial allocation + // input whose slot still points to this bulk Phi. + private int missingAlias(MemMergeNode mmm) { + for( int alias=2; alias<mmm.nIns(); alias++ ) + if( mmm.in(alias)!=null && + mmm.alias(alias)!=mmm.in(1) && + !_aliases.get(alias) ) + 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++ ) + CodeGen.CODE.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; + CodeGen.CODE.add(bulk); + CodeGen.CODE.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,Type.BOTTOM)); + for( int i=1; i<nIns(); i++ ) + mphi.addDef(CodeGen.CODE.add(preciseInput(in(i),alias))); + return CodeGen.CODE.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/chapter21/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/LoadNode.java index c8cca355..0432bcbc 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -1,7 +1,7 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.type.*; import java.util.BitSet; @@ -23,6 +23,25 @@ public class LoadNode extends MemOpNode { super(loc, name, alias, true, glb, mem, ptr, off); } + @Override boolean canDrop(MemOpNode other, Node dep) { + return super.canDrop(other,dep) && !clobbered(dep); + } + + // Check only immediate memory users. Writers clobber memory; Phis and + // aggregates might lead to a clobber, so stop rather than search further. + private boolean clobbered(Node dep) { + Node mem = mem(); + dep.addDepForwards(mem); + for( Node use : mem._outputs ) { + if( use==null ) continue; + dep.addDepForwards(use); + if( use instanceof StoreNode || use instanceof PhiNode || + use instanceof MemMergeNode || + use instanceof NewNode || use instanceof CallNode ) return true; + } + return false; + } + // Debugger label @Override public String label() { return "ld_"+mlabel(); } @Override @@ -30,33 +49,24 @@ public class LoadNode extends MemOpNode { @Override public Type compute() { - if( mem()._type instanceof TypeMem mem ) { - // Update declared forward ref to the actual - if( _declaredType.isFRef() && mem._t instanceof TypeMemPtr tmp && !tmp.isFRef() ) - _declaredType = tmp; - // No lifting if ptr might null-check - if( err()==null ) - return _declaredType.join(mem._t); - } - return _declaredType; + Type t = MemMergeNode.contents(mem(),_alias,this); + // Update declared forward ref to the actual. + if( _declaredType.isFRef() && t instanceof TypeMemPtr tmp && !tmp.isFRef() ) + _declaredType = tmp; + return err()==null ? _declaredType.join(t) : _declaredType; } @Override public Node idealize() { + if( mem() instanceof MemMergeNode merge ) { + setDef(1,CodeGen.CODE.add(merge.alias(_alias))); + return this; + } Node ptr = ptr(); Node mem = mem(); - // Simple Load-after-Store on same address. - if( mem instanceof StoreNode st && - ptr == st.ptr() && off() == st.off() ) { // Must check same object - assert _name.equals(st._name); // Equiv class aliasing is perfect - return extend(st.val()); - } - - // Simple Load-after-New on same address. - if( mem instanceof ProjNode p && p.in(0) instanceof NewNode nnn && - ptr == nnn.proj(1) ) // Must check same object - return zero(nnn); // Load zero from new + Node win = find(mem,ptr,null,null); + if( win!=null ) return folded(win); // Uplift control to a prior dominating load. for( Node memuse : mem._outputs ) @@ -65,49 +75,7 @@ public class LoadNode extends MemOpNode { cfg0()!=null && cfg0().domLCA(ld.cfg0(),this) == ld.cfg0() ) // Higher control means load is legal earlier return ld; - // Load-after-Store on same address, but bypassing provably unrelated - // stores. This is a more complex superset of the above two peeps. - // "Provably unrelated" is really weak. - if( ptr instanceof ReadOnlyNode ro ) - ptr = ro.in(1); - outer: - while( true ) { - switch( mem ) { - case StoreNode st: - if( ptr == addDep(st.ptr()) && off() == st.off() ) - return extend(castRO(st.val())); // Proved equal - // Can we prove unequal? Offsets do not overlap? - if( !off()._type.join(st.off()._type).isHigh() && // Offsets overlap - !neverAlias(ptr,st.ptr()) ) // And might alias - break outer; // Cannot tell, stop trying - // Pointers cannot overlap - mem = st.mem(); // Proved never equal - break; - case PhiNode phi: - // Assume related - addDep(phi); - break outer; - case ConstantNode top: break outer; // Assume shortly dead - case ProjNode mproj: // Memory projection - switch( mproj.in(0) ) { - case NewNode nnn1: - if( ptr instanceof ProjNode pproj && pproj.in(0) == mproj.in(0) ) - return zero(nnn1); - if( !(ptr instanceof ProjNode pproj && pproj.in(0) instanceof NewNode) ) - break outer; // Cannot tell, ptr not related to New - mem = nnn1.in(nnn1.findAlias(_alias));// Bypass unrelated New - break; - case StartNode start: break outer; - case CallEndNode cend: break outer; // TODO: Bypass no-alias call - default: throw Utils.TODO(); - } - break; - case MemMergeNode merge: mem = merge.alias(_alias); break; - - default: - throw Utils.TODO(); - } - } + if( ptr instanceof ReadOnlyNode ro ) ptr=ro.in(1); // 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 @@ -116,19 +84,27 @@ 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 && + // Array access control must not be moved across the merge. + in(0)==null && // Offset can be hoisted off() instanceof ConstantNode && // Pointer can be hoisted hoistPtr(ptr,memphi) ) { - // 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 = ld(1); - Node ld2 = ld(2); - return new PhiNode(_name,_declaredType,memphi.region(),ld1,ld2); + // Returning to this memory means no change only if the pointer + // is also loop invariant. A pointer Phi may select another object. + Node stop = memphi.region() instanceof LoopNode && + !(ptr instanceof PhiNode p && p.region()==memphi.region()) ? memphi : null; + if( find(memphi.in(2),ptr,stop,new BitSet())!=null || + (!(memphi.region() instanceof LoopNode) && find(memphi.in(1),ptr,null,new BitSet())!=null) ) { + PhiNode phi = new PhiNode(_name,_declaredType,memphi.region()).keep(); + phi.setType(_type); // Backedge searches may return this value Phi. + for( int i=1; i<memphi.nIns(); i++ ) { + Node p = ptr instanceof PhiNode pp && pp.region()==memphi.region() ? pp.in(i) : ptr; + phi.addDef(load(memphi.in(i),p,stop,phi)); + } + return CodeGen.CODE.add(phi.unkeep()); } } @@ -143,9 +119,66 @@ public class LoadNode extends MemOpNode { return castRO(new ConstantNode(zero).peephole()); } - private Node ld( int idx ) { - Node mem = mem(), ptr = ptr(); - return new LoadNode(_loc,_name,_alias,_declaredType,mem.in(idx),ptr instanceof PhiNode && ptr.in(0)==mem.in(0) ? ptr.in(idx) : ptr, off()).peephole(); + // Search only; no new nodes or rewiring. Return a Store/New that folds, + // the unchanged loop memory, or a merge whose every arm folds. Null fails. + // Register dependencies so a later pointer/offset rewrite retries the query. + private Node find(Node mem, Node ptr, Node stop, BitSet visit) { + if( ptr instanceof ReadOnlyNode ro ) ptr=ro.in(1); + while( mem!=null ) { + addDep(mem); + if( mem==stop ) return mem; + switch( mem ) { + case MemMergeNode merge: mem=merge.alias(_alias); break; + case StoreNode st: + addDep(st.ptr()); + if( _alias==st._alias && ptr==st.ptr() && off()==st.off() ) return st; + if( _alias==st._alias && !neverAlias(ptr,st.ptr()) && + !addDep(off())._type.join(addDep(st.off())._type).isHigh() ) return null; + mem=st.mem(); + break; + case ProjNode proj: + if( !(proj.in(0) instanceof NewNode nnn) ) return null; + if( ptr instanceof ProjNode p && p.in(0)==nnn ) return nnn; + if( !(ptr instanceof ProjNode p && p.in(0) instanceof NewNode) ) return null; + mem=nnn.mem(); // Distinct allocations cannot overlap. + break; + case MemPhiNode phi: + addDep(phi.region()); + // Only the original loop closes a successful cycle. Another + // loop or an unfinished merge needs a separate proof. + if( visit==null || !(phi.region() instanceof RegionNode r) || r instanceof LoopNode || + r.inProgress() || phi.inProgress() || r._type!=Type.CONTROL ) return null; + if( visit.get(phi._nid) ) return phi; // Already proved every arm. + for( int i=1; i<phi.nIns(); i++ ) + if( find(phi.in(i),ptr,stop,visit)==null ) return null; + visit.set(phi._nid); + return phi; + default: return null; + } + } + return null; + } + + private Node folded(Node win) { + return win instanceof StoreNode st ? extend(castRO(st.val())) : zero((NewNode)win); + } + + // Materialize only after the profitability search succeeds. The value Phi + // stands for a load which comes back around the loop with unchanged memory. + private Node load(Node mem, Node ptr, Node stop, PhiNode value) { + Node win=find(mem,ptr,stop,new BitSet()); + if( win!=null ) { + if( win==stop ) return value; + if( win instanceof MemPhiNode mp ) { + PhiNode phi=new PhiNode(_name,_declaredType,mp.region()).keep(); + for( int i=1; i<mp.nIns(); i++ ) phi.addDef(load(mp.in(i),ptr,stop,value)); + return phi.unkeep().peephole(); + } + return folded(win).peephole(); + } + LoadNode ld=new LoadNode(_loc,_name,_alias,_declaredType,mem,ptr,off()); + ld.setDef(0,in(0)); + return ld.peephole(); } private static boolean neverAlias( Node ptr1, Node ptr2 ) { @@ -174,16 +207,6 @@ public class LoadNode extends MemOpNode { return false; } - // Profitable if we find a matching Store on this Phi arm. - private boolean profit(PhiNode phi, int idx) { - Node px = phi.in(idx); - if( px==null ) return false; - if( px._type instanceof TypeMem mem && mem._t.isHighOrConst() ) return true; - if( px instanceof StoreNode st1 && ptr()==st1.ptr() && off()==st1.off() ) return true; - addDep(px); - return false; - } - // Read-Only is a deep property, and cannot be cast-away private Node castRO(Node rez) { if( ptr()._type.isFinal() && !rez._type.isFinal() ) @@ -205,4 +228,5 @@ public class LoadNode extends MemOpNode { Node shl = new ShlNode(null,val,shf.keep()).peephole(); return new SarNode(null,shl,shf.unkeep()); } + } diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/LoopNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/LoopNode.java index 3d69b94d..bb82fe1a 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/LoopNode.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/LoopNode.java @@ -57,11 +57,12 @@ public class LoopNode extends RegionNode { // Now fold control into the exit. Might have 1 valid exit, or an // XCtrl or a bunch of prior NeverNode exits. Node top = new ConstantNode(Type.TOP).peephole(); - Node memout = new MemMergeNode(false); - memout.addDef(f); // placeholder for control + MemMergeNode memout = new MemMergeNode(new ConstantNode(TypeMem.BOT).peephole()); + memout.setDef(0,f); for( Node u : _outputs ) - if( u instanceof PhiNode phi && phi._type.isa(TypeMem.BOT) ) - memout.addDef(phi); + if( u instanceof BulkMemPhiNode ) memout.setDef(1,u); + else if( u instanceof MemPhiNode phi ) memout.alias(phi._alias,phi); + memout.init(); Node ctrl = ret.ctrl(), mem = ret.mem(), expr = ret.expr(); if( ctrl!=null && ctrl._type != Type.XCONTROL ) { @@ -72,7 +73,7 @@ public class LoopNode extends RegionNode { // Nope, insert an aligned exit layer RegionNode r = new RegionNode(_loc,null,ctrl).init(); ctrl = r; r._ltree = stop._ltree; - mem = new PhiNode(r,mem ).init(); + mem = new BulkMemPhiNode(ScopeNode.MEM0,r,mem).init(); expr = new PhiNode(r,expr).init(); } // Append new Never exit diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java index 72e381ca..c1d11f88 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java @@ -5,158 +5,84 @@ import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.*; import java.util.*; -/** - * Memory Merge - a merge of many aliases into a "fat memory". All aliases - * are here, but most will be lazy - take the default fat memory. +/** A default slice and explicit alias overrides. A null default means partial + * memory: absent aliases are not covered, rather than having unknown contents. */ public class MemMergeNode extends Node { + public MemMergeNode(MemMergeNode mem) { super(mem); } - /* - * In-Progress means this is being used by the Parser to track memory - * aliases. No optimizations are allowed. When no longer "in progress" - * normal peeps work. - */ - public final boolean _inProgress; - - public MemMergeNode( boolean inProgress) { _type = TypeMem.BOT; _inProgress = inProgress; } - public MemMergeNode(MemMergeNode mem) { super(mem); _inProgress = false; } - - - // If being used by a Scope, this is "in progress" from the Parser. - // Otherwise, it's a memory merge - boolean inProgress() { return _inProgress; } - - @Override public String label() { return "ALLMEM"; } - @Override public boolean isMem() { return true; } - - @Override - public StringBuilder _print1(StringBuilder sb, BitSet visited) { - sb.append(_inProgress ? "Merge[" : "MEM[ "); - for( int j=2; j<nIns(); j++ ) { - sb.append(j); - sb.append(":"); - Node n = in(j); - while( n instanceof ScopeNode loop ) { - sb.append("Lazy_"); - n = loop.mem().alias(j); - } - if( n==null ) sb.append("___ "); - else n._print0(sb, visited).append(" "); - } - sb.setLength(sb.length()-1); - return sb.append("]"); + public MemMergeNode(Node bulk) { super(null,bulk); _type = TypeMem.BOT; } + public MemMergeNode(Node bulk, int alias, Node precise) { + this(bulk); + alias(alias,precise); } - - // Make a memory merge: no longer a Scope really, tracking memory state but - // not related to the parser in any way. - public Node merge() { - // Force default memory to not be lazy - MemMergeNode merge = new MemMergeNode(false); - for( Node n : _inputs ) - merge.addDef(n); - merge._mem(1,null); - return merge.peephole(); + public Node alias(int alias) { + Node n = alias < nIns() ? in(alias) : null; + assert n!=null || !(in(1) instanceof BulkMemPhiNode bulk) || !bulk.isSplit(alias); + n = n==null ? in(1) : n; + assert n!=null : "Alias not covered: "+alias; + return n; } - - @Override public Type compute() { return TypeMem.BOT; } - - @Override public Node idealize() { - if( inProgress() ) return null; - - // If not merging any memory (all memory is just the default) - if( allDefault() ) - return in(1); // Become default memory - - return null; + public void alias(int alias, Node mem) { + assert alias > 1; + while( nIns()<=alias ) addDef(null); + setDef(alias,mem); } - private boolean allDefault() { - for( int i=2; i<nIns(); i++ ) - if( in(1) != in(i) ) - return false; - return true; - } - - public Node in( Var v ) { return in(v._idx); } - - public Node alias( int alias ) { - return in(alias<nIns() && in(alias)!=null ? alias : 1); - } - - Node alias( int alias, Node st ) { - while( alias >= nIns() ) addDef(null); - return setDef(alias,st); - } - - // Read or update from memory. - // A shared implementation allows us to create lazy phis both during - // lookups and updates; the lazy phi creation is part of chapter 8. - Node _mem( int alias, Node st ) { - // Memory projections are made lazily; if one does not exist - // then it must be START.proj(1) - Node old = alias(alias); - if( old instanceof ScopeNode loop ) { - MemMergeNode loopmem = loop.mem(); - Node memdef = loopmem.alias(alias); - // Lazy phi! - old = memdef instanceof PhiNode phi && loop.ctrl()==phi.region() - // Loop already has a real Phi, use it - ? memdef - // 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. - : loopmem.alias(alias, new PhiNode(Parser.memName(alias), TypeMem.BOT,loop.ctrl(),loopmem._mem(alias,null),null).peephole() ); - alias(alias,old); + // Scalar contents of one alias. New zeroes the allocated fields; + // join it with the incoming contents for all previously allocated objects. + // Phis use their cached types, so this query does not recurse around loops. + static Type contents(Node mem, int alias, Node dep) { + dep.addDep(mem); + if( mem instanceof MemMergeNode merge ) + return contents(merge.alias(alias),alias,dep); + if( mem instanceof ProjNode proj && proj.in(0) instanceof NewNode nnn ) { + assert proj._idx==1 && nnn.field(alias)!=null; + dep.addDep(nnn); + return contents(nnn.mem(),alias,dep).meet(nnn.field(alias)._type.makeZero()); } - // Memory projections are made lazily; expand as needed - return st==null ? old : alias(alias,st); // Not lazy, so this is the answer - } - - - void _merge( MemMergeNode that, RegionNode r) { - int len = Math.max(nIns(),that.nIns()); - for( int i = 2; i < len; i++) - if( alias(i) != that.alias(i) ) { // No need for redundant Phis - // If we are in lazy phi mode we need to a lookup - // by alias as it will trigger a phi creation - Node lhs = this._mem(i,null); - Node rhs = that._mem(i,null); - alias(i, new PhiNode(Parser.memName(i), lhs._type.glb().meet(rhs._type.glb()), r, lhs, rhs).peephole()); - } + // Function parameters and call results may contain arbitrary heap values. + // Their bulk memory types do not imply empty or zero-filled storage. + if( mem._type==Type.TOP || mem._type==TypeMem.TOP ) return Type.TOP; + if( mem._type instanceof TypeMem mt && mt._alias==alias ) return mt._t; + return Type.BOTTOM; } - // Fill in the backedge of any inserted Phis - void _endLoopMem( ScopeNode scope, MemMergeNode back, MemMergeNode exit ) { - Node exit_def = exit.alias(1); - for( int i=1; i<nIns(); i++ ) { - if( in(i) instanceof PhiNode phi && phi.region()==scope.ctrl() ) { - assert phi.in(2)==null; - phi.setDef(2,back.in(i)==scope ? phi : back.in(i)); // Fill backedge + @Override public String label() { return "MemMerge"; } + @Override public boolean isMem() { return true; } + @Override public Type compute() { return TypeMem.BOT; } + @Override public 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(" "); } - if( exit_def == scope ) // Replace a lazy-phi on the exit path also - exit.alias(i,in(i)); - } + return sb.append("]"); } - // Now one-time do a useless-phi removal - void _useless( ) { + @Override public Node idealize() { + boolean progress=false, allDefault=true; for( int i=2; i<nIns(); i++ ) { - if( in(i) instanceof PhiNode phi ) { - // Do an eager useless-phi removal - Node in = phi.peephole(); - CodeGen.CODE.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 - } + if( in(i)!=null && in(i)==in(1) ) { setDef(i,null); progress=true; } + if( in(i) instanceof MemMergeNode mem ) { + setDef(i,CodeGen.CODE.add(mem.alias(i))); + progress=true; } + if( in(i)!=null ) allDefault=false; } - } - - @Override public boolean eq( Node n ) { - return this==n || !_inProgress; + if( allDefault && in(1)!=null ) 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)); + CodeGen.CODE.add(mem.in(1)); + setDef(1,mem.in(1)); + return this; + } + assert BulkMemPhiNode.checkMerge(this); + return progress ? this : null; } } diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/MemOpNode.java index 058c86fa..344e8b2c 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/MemOpNode.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/MemOpNode.java @@ -6,6 +6,7 @@ import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeMemPtr; import java.lang.StringBuilder; import java.util.BitSet; +import java.util.ArrayList; /** * Convenience common base for Load and Store. @@ -79,6 +80,8 @@ public abstract class MemOpNode extends Node { static String mlabel(String name) { return "[]".equals(name) ? "ary" : ("#".equals(name) ? "len" : name); } String mlabel() { return mlabel(_name); } + @Override public boolean isMem() { return !_isLoad; } + public Node mem() { return in(1); } public Node ptr() { return in(2); } public Node off() { return in(3); } @@ -87,6 +90,13 @@ public abstract class MemOpNode extends Node { public StringBuilder _printMach( StringBuilder sb, BitSet visited ) { throw Utils.TODO(); } + // Extra conditions for factoring matching memory operations through a Phi. + // The caller has already checked the opcode, input shape and control. + boolean canDrop(MemOpNode other, Node dep) { + dep.addDep(ptr()); + return _alias==other._alias && _declaredType==other._declaredType && err()==null; + } + @Override public boolean eq(Node n) { MemOpNode mem = (MemOpNode)n; // Invariant @@ -107,4 +117,29 @@ public abstract class MemOpNode extends Node { return null; return Parser.error( "Might be null accessing '" + _name + "'",_loc); } + // Memory writers which must follow this read. A packaging node is not a + // write, but can hide New's input, so follow this alias through aggregates. + public ArrayList<Node> antiDeps() { + ArrayList<Node> deps = new ArrayList<>(); + antiDeps(mem(),deps,new BitSet()); + return deps; + } + + private void antiDeps(Node mem, ArrayList<Node> deps, BitSet visit) { + if( visit.get(mem._nid) ) return; + visit.set(mem._nid); + for( Node use : mem._outputs ) { + if( use instanceof MemMergeNode merge ) { + Node slice = _alias<merge.nIns() ? merge.in(_alias) : null; + if( (slice==mem) || (slice==null && merge.in(1)==mem) ) + antiDeps(merge,deps,visit); + } else if( use instanceof CallNode || + (use instanceof MemOpNode st && !st._isLoad && st._alias==_alias) || + (use instanceof NewNode nnn && nnn.field(_alias)!=null) || + (use instanceof MemPhiNode phi && phi._alias==_alias) || + (use instanceof BulkMemPhiNode phi && !phi.isSplit(_alias)) ) { + if( !deps.contains(use) ) deps.add(use); + } + } + } } diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java new file mode 100644 index 00000000..e5a29329 --- /dev/null +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java @@ -0,0 +1,44 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.codegen.CodeGen; +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,Type.BOTTOM),inputs); + assert alias > 1; + _alias = alias; + } + + public MemPhiNode(MemPhiNode phi) { super(phi); _alias = phi._alias; } + + @Override public String label() { return "MemPhi_"+_alias; } + + @Override public Type compute() { + assert BulkMemPhiNode.checkMem(this,_alias); + if( !(region() instanceof RegionNode r) ) + return region()._type==Type.XCONTROL ? TypeMem.TOP : _type; + if( r.inProgress() ) return _declaredType; + Type t = Type.TOP; + for( int i=1; i<nIns(); i++ ) + if( addDep(r.in(i))._type!=Type.XCONTROL ) + t = t.meet(MemMergeNode.contents(in(i),_alias,this)); + return TypeMem.make(_alias,t); + } + + @Override public Node idealize() { + for( int i=1; i<nIns(); i++ ) + if( in(i) instanceof MemMergeNode mem ) { + setDef(i,CodeGen.CODE.add(mem.alias(_alias))); + return this; + } + return super.idealize(); + } + + @Override public boolean eq(Node n) { return _alias==((MemPhiNode)n)._alias && super.eq(n); } + @Override int hash() { return _alias; } +} diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/NewNode.java index 27f7d634..57d351ef 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/NewNode.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/NewNode.java @@ -6,34 +6,35 @@ import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.type.*; import java.util.BitSet; -/** - * Allocation! Allocate a chunk of memory, and pre-zero it. - * The inputs include control and size, and ALL aliases being set. - * The output is large tuple, one for every alias plus the created pointer. - * New is expected to be followed by projections for every alias. +/** Allocate a zeroed object. Inputs {ctrl, $mem, size}; + * results {ptr, $mem}. + * The memory input and result cover only the aliases in the allocated struct. */ public class NewNode extends Node implements MultiNode { public final TypeMemPtr _ptr; - public final int _len; public NewNode(TypeMemPtr ptr, Node... nodes) { super(nodes); assert !ptr.nullable(); _ptr = ptr; - _len = ptr._obj._fields.length; - // Control in slot 0 - assert nodes[0]._type==Type.CONTROL || nodes[0]._type == Type.XCONTROL; - // Malloc-length in slot 1 - assert nodes[1]._type instanceof TypeInteger || nodes[1]._type==Type.NIL; - for( int i=0; i<_len; i++ ) - // Memory slices for all fields. - assert nodes[2+i]._type.isa(TypeMem.BOT); + assert nodes.length==3; + assert nodes[0]._type==Type.CONTROL || nodes[0]._type==Type.XCONTROL; + assert nodes[1]._type instanceof TypeMem; + assert nodes[2]._type instanceof TypeInteger || nodes[2]._type==Type.NIL; } - public NewNode(NewNode nnn) { super(nnn); _ptr = nnn._ptr; _len = nnn._len; } + public Node mem() { return in(1); } + public Node size() { return in(2); } + + public Field field(int alias) { + for( Field f : _ptr._obj._fields ) + if( f._alias==alias ) return f; + return null; + } + + public NewNode(NewNode nnn) { super(nnn); _ptr = nnn._ptr; } - public Node mem (int idx) { return in(idx+2); } @Override public String label() { return "new_"+(_ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._type.str() : _ptr._obj.str()); } @Override @@ -42,32 +43,10 @@ public class NewNode extends Node implements MultiNode { return sb.append(_ptr._obj.str()); } - int findAlias(int alias) { - int idx = _ptr._obj.findAlias(alias); - assert idx!= -1; // Error, caller should be calling - return idx+2; // Skip ctrl, size - } - - // 0 - ctrl - // 1 - byte size - // 2-len+2 - aliases, one per field - // len+2 - 2*len+2 - initial values, one per field - public Node size() { return in(1); } @Override public TypeTuple compute() { - Field[] fs = _ptr._obj._fields; - Type[] ts = new Type[fs.length+2]; - ts[0] = Type.CONTROL; - ts[1] = _ptr; - for( int i=0; i<fs.length; i++ ) { - Type mt = in(i+2)._type; - TypeMem mem = mt==Type.TOP ? TypeMem.TOP : (TypeMem)mt; - Type tfld = mem._t.meet(mem._t.makeZero()); - Type tfld2 = tfld.join(fs[i]._type); - ts[i+2] = TypeMem.make(fs[i]._alias,tfld2); - } - return TypeTuple.make(ts); + return TypeTuple.make(_ptr,TypeMem.BOT); } @Override @@ -105,9 +84,9 @@ public class NewNode extends Node implements MultiNode { _kills = kills; } public String op() { return "alloc"; } - public RegMask regmap(int i) { return i==1 ? _arg3Mask : null; } + public RegMask regmap(int i) { return i==2 ? _arg3Mask : null; } public RegMask outregmap() { return null; } - public RegMask outregmap(int idx) { return idx==1 ? _retMask : null; } + public RegMask outregmap(int idx) { return idx==0 ? _retMask : null; } public RegMask killmap() { return _kills; } public void asm(CodeGen code, SB sb) { sb.p("#calloc, ").p(code.reg(size())); diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter21/src/main/java/com/seaofnodes/simple/node/Node.java index 1b543c69..12faf706 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/Node.java @@ -230,6 +230,7 @@ public abstract class Node implements Cloneable { // Error is 'use' does not exist; ok for 'use' to be null. protected boolean delUse( Node use ) { _outputs.del(_outputs.find(use)); + moveDepsToWorklist(); // User-count and anti-dependence queries can now change. return _outputs.isEmpty(); } @@ -299,6 +300,7 @@ public abstract class Node implements Cloneable { n.unlock(); int idx = n._inputs.find(this); n._inputs.set(idx,nnn); + n.moveDepsToWorklist(); // Rewiring can change a dependent query without changing type. nnn.addUse(n); CODE.addAll(n._outputs); } @@ -545,7 +547,12 @@ public abstract class Node implements Cloneable { * 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. */ - <N extends Node> N addDep( N dep ) { + <N extends Node> N addDep( N dep ) { return addDep(dep,false); } + + // Keep an explicit dependency when inspecting an immediate user's shape. + <N extends Node> N addDepForwards(N dep) { return addDep(dep,true); } + + private <N extends Node> N addDep(N dep, boolean forwards) { // Running peepholes during the big assert cannot have side effects // like adding dependencies. if( CODE._midAssert ) return dep; @@ -553,8 +560,8 @@ public abstract class Node implements Cloneable { if( obs != null ) obs.dep(this, dep); if( dep._deps==null ) dep._deps = new Ary<>(Node.class); if( dep._deps .find(this) != -1 ) return dep; // Already on list - if( dep._inputs .find(this) != -1 ) return dep; // No need for deps on immediate neighbors - if( dep._outputs.find(this) != -1 ) return dep; + if( !forwards && dep._inputs .find(this) != -1 ) return dep; // No need for deps on immediate neighbors + if( !forwards && dep._outputs.find(this) != -1 ) return dep; dep._deps.add(this); return dep; } diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/PhiNode.java index 8ab40af6..23acd75e 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -24,6 +24,10 @@ public class PhiNode extends Node { addDef(sample); } + 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_"+MemOpNode.mlabel(_label); } @Override @@ -77,10 +81,9 @@ public class PhiNode extends Node { if( r.in(i)._type == Type.XCONTROL ) return null; - // Generic "pull down op" - Node progress; - if( same_op() && (progress = drop_same_op()) != null ) - return progress; + // Phi(op(a,b,...),op(c,d,...)) -> op(Phi(a,c),Phi(b,d),...). + Node down; + if( same_op() && (down=drop_same_op())!=null ) return down; // If merging Phi(N, cast(N)) - we are losing the cast JOIN effects, so just remove. if( nIns()==3 ) { @@ -114,45 +117,71 @@ public class PhiNode extends Node { // Same op on all Phi paths; all ops have only the Phi as a use. // None have a control input. private boolean same_op() { + Node op = in(1); + if( op instanceof CFGNode || op instanceof ConstantNode || op instanceof PhiNode || + op instanceof ProjNode || op instanceof NewNode || op instanceof ScopeNode || op instanceof MemMergeNode ) return false; + // A bulk Phi must split its aliases before factoring precise Stores. + if( this instanceof BulkMemPhiNode ) return false; + // Bulk splitting currently identifies parallel slices by Region/alias. + // Factoring can introduce another slice at a different memory point; + // wait until that Region's bulk partitioning has finished. + if( op instanceof MemOpNode ) + for( Node use : region()._outputs ) + if( use instanceof BulkMemPhiNode ) { + addDepForwards(use); + return false; + } + Node busy = null; for( int i=1; i<nIns(); i++ ) { - Node op = in(i); - if( in(1).getClass() != op.getClass() || op.in(0)!=null || in(1).nIns() != op.nIns() ) - return false; // Wrong class or CFG bound or mismatched inputs - if( op.nOuts() > 1 ) { // Too many users, but addDep in case lose users - for( Node out : op._outputs ) - if( out!=null && out!=this ) - addDep(out); - return false; + Node n = in(i); + if( addDep(region().in(i))._type==Type.XCONTROL || + op.getClass()!=n.getClass() || n.nIns()!=op.nIns() || n.in(0)!=null || !op.eq(n) ) return false; + if( n instanceof MemOpNode mem && !mem.canDrop((MemOpNode)op,this) ) return false; + // Moving a Store must remove the old effect, not duplicate it. + // Allow one shared arm: the other arms disappear into the single + // factored operation, usually reducing the total operation count. + addDepForwards(n); + if( n.nOuts()>1 ) { + for( Node use : n._outputs ) + if( use!=null && use!=this ) addDepForwards(use); + if( n instanceof StoreNode || busy!=null ) return false; + busy=n; } + for( int j=1; j<n.nIns(); j++ ) + if( n.in(j) instanceof ScopeNode || ((n.in(j)==null) != (op.in(j)==null)) ) return false; } return true; } private Node drop_same_op() { - assert !(in(1) instanceof CFGNode); - Node op = in(1); - Node cp = op.copy(); - cp._type = null; // Fresh type - cp.addDef(null); // No control - + Node op = in(1), cp = op.copyEmpty(); + cp._type = null; + cp.addDef(null); for( int j=1; j<op.nIns(); j++ ) { - boolean needsPhi = false; - Node x = op.in(j); // Jth input from sample #1 + Node x = op.in(j); + boolean different = false; for( int i=2; i<nIns(); i++ ) - if( in(i).in(j) != x ) - { needsPhi=true; break; } - if( needsPhi ) { - x = new PhiNode(_label,op.in(j)._type.glb()); - x.addDef(region()); - for( int i=1; i<nIns(); i++ ) - x.addDef(in(i).in(j)); - x = x.peephole(); + if( in(i).in(j)!=x ) different=true; + if( different ) { + Node[] ins = new Node[nIns()]; + ins[0] = region(); + Type t = Type.TOP; + for( int i=1; i<nIns(); i++ ) { + ins[i] = in(i).in(j); + t = t.meet(ins[i]._type); + } + PhiNode phi = j==1 && op instanceof MemOpNode mem + ? new MemPhiNode(_label,mem._alias,ins) + : PhiNode.make(_label,t.glb(),ins); + x = phi.peephole(); } cp.addDef(x); } - // Test not running backwards, which can happen for e.g. And's - if( cp.compute().isa(compute()) ) - return cp; + // Factoring must not widen the result (e.g. correlated And operands). + if( cp.compute().isa(compute()) ) return cp; + for( int i=1; i<nIns(); i++ ) + for( int j=1; j<in(i).nIns(); j++ ) + if( in(i).in(j)!=null ) addDep(in(i).in(j)); cp.kill(); return null; } diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/RegionNode.java index 64747c63..84383845 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/RegionNode.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/RegionNode.java @@ -48,7 +48,7 @@ public class RegionNode extends CFGNode { if( !hasPhi() && // No Phi users, just a control user in(1) instanceof CProjNode p1 && in(2) instanceof CProjNode p2 && - addDep(p1.in(0))==addDep(p2.in(0)) && + addDepForwards(p1).in(0)==addDepForwards(p2).in(0) && p1.in(0) instanceof IfNode iff ) { // Replace with the iff.ctrl directly if( nIns()==3 ) return iff.ctrl(); diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/ReturnNode.java index 5c76116f..05b19682 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -9,15 +9,7 @@ import com.seaofnodes.simple.codegen.Encoding; 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 all preceding memory effects and the return value alive: {ctrl, $mem, value, rpc}. */ public class ReturnNode extends CFGNode { public FunNode _fun; @@ -52,7 +44,7 @@ public class ReturnNode extends CFGNode { public Type compute() { if( inProgress () ) return TypeTuple.RET; // In progress // Inlining can delete the entry while callers still use this return. - return TypeTuple.make(ctrl()._type,mem()._type,expr()._type); + return TypeTuple.make(ctrl()._type,TypeMem.BOT,expr()._type); } @Override public Node idealize() { diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/ScopeNode.java index 298c5a5f..e8c4f0d5 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -7,9 +7,9 @@ import java.util.*; /** * The Scope node is purely a parser helper - it tracks names to nodes with a - * stack of variables. + * stack of variable records. */ -public class ScopeNode extends MemMergeNode { +public class ScopeNode extends Node { /** * The control is a name that binds to the currently active control @@ -19,6 +19,11 @@ public class ScopeNode extends MemMergeNode { public static final String ARG0 = "arg"; public static final String MEM0 = "$mem"; + public Node in(Var v) { return in(v._idx); } + + @Override public Type compute() { return Type.BOTTOM; } + @Override public Node idealize() { return null; } + // All active/live variables in all nested scopes, all run together public final Ary<Var> _vars; @@ -34,7 +39,7 @@ public class ScopeNode extends MemMergeNode { // A new ScopeNode public ScopeNode() { - super(true); + _type = Type.BOTTOM; _vars = new Ary<>(Var .class); _lexSize= new Ary<>(Integer.class); _kinds = new Ary<>(Kind .class); @@ -70,7 +75,7 @@ public class ScopeNode extends MemMergeNode { public Node ctrl() { return in(0); } - public MemMergeNode mem() { return (MemMergeNode)in(1); } + public Node mem() { return in(lookup(MEM0)); } /** * The ctrl of a ScopeNode is always bound to the currently active @@ -83,7 +88,7 @@ public class ScopeNode extends MemMergeNode { * @return Node that was bound */ public <N extends Node> N ctrl(N n) { return setDef(0,n); } - public Node mem(Node n) { return setDef(1,n); } + public Node mem(Node n) { update(MEM0,n); return n; } public void push(Kind kind) { assert _lexSize._len==_kinds._len; @@ -166,12 +171,6 @@ public class ScopeNode extends MemMergeNode { return true; } - // Read from memory - public Node mem( int alias ) { return mem()._mem(alias,null); } - // Write to memory - public void mem( int alias, Node st ) { mem()._mem(alias,st); } - - /** * Lookup a name in all scopes starting from most deeply nested. * @@ -207,7 +206,7 @@ public class ScopeNode extends MemMergeNode { // 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(v._idx,new PhiNode(v._name, v.lazyGLB(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).peephole()); + : loop.setDef(v._idx,PhiNode.make(v._name, v.lazyGLB(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).peephole()); setDef(v._idx,old); } assert !v._final || st==null; @@ -246,20 +245,8 @@ public class ScopeNode extends MemMergeNode { n.keep(); dup.addDef(ctrl()); // Control input is just copied - // Memory input is a shallow copy - MemMergeNode memdup = new MemMergeNode(true), mem = mem(); - memdup.addDef(null); - memdup.addDef(loop ? this : mem.in(1)); - for( int i=2; i<mem.nIns(); i++ ) - // For lazy phis on loops we use a sentinel - // that will trigger phi creation on update - memdup.addDef(loop ? this : mem.in(i)); - dup.addDef(memdup); - - // Copy of other inputs - for( int i=2; i<nIns(); i++ ) - // For lazy phis on loops we use a sentinel - // that will trigger phi creation on update + // Memory is one binding, with the same lazy loop Phi as scalar names. + for( int i=1; i<nIns(); i++ ) dup.addDef(loop && !_vars.at(i)._final ? this : in(i)); return dup; } @@ -274,22 +261,21 @@ public class ScopeNode extends MemMergeNode { */ public RegionNode mergeScopes(ScopeNode that, Parser.Lexer loc) { RegionNode r = ctrl(new RegionNode(loc,null,ctrl(), that.ctrl()).keep()); - mem()._merge(that.mem(),r); - this ._merge(that ,r); + _merge(that,r); that.kill(); // Kill merged scope CodeGen.CODE.add(r); return r.unkeep(); } private void _merge(ScopeNode that, RegionNode r) { - for( int i = 2; i < nIns(); i++) + 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 Var v = _vars.at(i); Node lhs = this.in(this.update(v,null)); Node rhs = that.in(that.update(v,null)); - setDef(i, new PhiNode(v._name, v.type(), r, lhs, rhs).peephole()); + setDef(i, PhiNode.make(v._name, v.type(), r, lhs, rhs).peephole()); } } @@ -314,21 +300,15 @@ public class ScopeNode extends MemMergeNode { assert ctrl instanceof LoopNode loop && loop.inProgress(); ctrl.setDef(2,back.ctrl()); - mem()._endLoopMem( this, back.mem(), exit.mem() ); - this ._endLoop ( this, back , exit ); + _endLoop(this,back,exit); back.kill(); // Loop backedge is dead // Now one-time do a useless-phi removal - mem()._useless(); - this ._useless(); - - // The exit mem's lazy default value had been the loop top, - // now it goes back to predating the loop. - exit.mem().setDef(1,mem().in(1)); + _useless(); } // Fill in the backedge of any inserted Phis void _endLoop( ScopeNode scope, Node back, Node exit ) { - for( int i=2; i<nIns(); i++ ) { + for( int i=1; i<nIns(); i++ ) { if( _vars.at(i)._final ) continue; // Final vars did not get modified in the loop if( _vars.at(i).type().isHighOrConst() ) continue; // Cannot lift higher than a constant, so no Phi if( back.in(i) != scope ) { @@ -342,6 +322,23 @@ public class ScopeNode extends MemMergeNode { } + // Now one-time do a useless-phi removal + void _useless( ) { + for( int i=1; i<nIns(); i++ ) { + if( in(i) instanceof PhiNode phi ) { + // Do an eager useless-phi removal + Node in = phi.peephole(); + CodeGen.CODE.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. public void addGuards( Node ctrl, Node pred, boolean invert ) { diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/StartNode.java index 4bd76f50..91bf85a1 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -3,18 +3,11 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.type.*; -import java.util.Arrays; import java.util.BitSet; import java.util.HashSet; import static com.seaofnodes.simple.Utils.TODO; -/** - * 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. - */ +/** Start supplies control, whole memory, and the argument, in that order. */ public class StartNode extends LoopNode implements MultiNode { final Type _arg; @@ -45,7 +38,7 @@ public class StartNode extends LoopNode implements MultiNode { @Override public TypeTuple compute() { - return TypeTuple.make(Type.CONTROL,TypeMem.TOP,_arg); + return TypeTuple.make(Type.CONTROL,TypeMem.BOT,_arg); } @Override public Node idealize() { return null; } diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/StoreNode.java index f15ff9bb..86dce4fc 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.*; import com.seaofnodes.simple.type.*; import java.util.BitSet; @@ -25,6 +26,10 @@ public class StoreNode extends MemOpNode { } // Debugger label + @Override boolean canDrop(MemOpNode other, Node dep) { + return super.canDrop(other,dep) && _init==((StoreNode)other)._init; + } + @Override public String label() { return "st_"+mlabel(); } @Override public boolean isMem() { return true; } @@ -38,28 +43,25 @@ public class StoreNode extends MemOpNode { @Override public Type compute() { Type val = val()._type; - TypeMem mem = (TypeMem)mem()._type; // Invariant - if( mem == TypeMem.TOP ) return TypeMem.TOP; - Type t = Type.BOTTOM; // No idea on field contents - // Same alias, lift val to the declared type and then meet into other fields - if( mem._alias == _alias ) { - // Update declared forward ref to the actual - if( _declaredType.isFRef() && val instanceof TypeMemPtr tmp && !tmp.isFRef() ) - _declaredType = tmp; - val = val.join(_declaredType); - t = val.meet(mem._t); - } + if( _declaredType.isFRef() && val instanceof TypeMemPtr tmp && !tmp.isFRef() ) + _declaredType = tmp; + Type t = val.join(_declaredType).meet(MemMergeNode.contents(mem(),_alias,this)); return TypeMem.make(_alias,t); } @Override public Node idealize() { + if( mem() instanceof MemMergeNode merge ) { + setDef(1,CodeGen.CODE.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 - off()==st.off() && // And same offset (could be "same alias" but this handles arrays to same index) + ptr()==st.ptr() && _alias==st._alias && // Must check same object + off()==st.off() && // And same offset 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/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java index 533f2d7b..c8dea1f4 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java @@ -671,6 +671,8 @@ public class arm extends Machine { case NotNode not -> new NotARM(not); case OrNode or -> or(or); case ParmNode parm -> new ParmARM(parm); + case BulkMemPhiNode phi -> new BulkMemPhiNode(phi); + case MemPhiNode phi -> new MemPhiNode(phi); case PhiNode phi -> new PhiNode(phi); case ProjNode prj -> new ProjARM(prj); case ReadOnlyNode read -> new ReadOnlyNode(read); diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java index 54c6b828..8e1cccf9 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java @@ -345,6 +345,8 @@ public class riscv extends Machine { case NotNode not -> new NotRISC(not); case OrNode or -> or(or); case ParmNode parm -> new ParmRISC(parm); + case BulkMemPhiNode phi -> new BulkMemPhiNode(phi); + case MemPhiNode phi -> new MemPhiNode(phi); case PhiNode phi -> new PhiNode(phi); case ProjNode prj -> prj(prj); case ReadOnlyNode read-> new ReadOnlyNode(read); diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java index 6e72a540..c712eb7d 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java @@ -27,6 +27,11 @@ public class LoadX86 extends MemOpX86 { } static void enc( Encoding enc, Type decl, short dst, short ptr, short idx, int off, int scale ) { + // Allocation may put a full-width integer in an XMM register (or a + // floating value in a GPR). Move its bits using the selected register bank. + if( dst>=x86_64_v2.XMM_OFFSET && decl.log_size()==3 ) decl=TypeFloat.F64; + else if( dst<x86_64_v2.XMM_OFFSET && decl instanceof TypeFloat ) + decl = decl==TypeFloat.F32 ? TypeInteger.U32 : TypeInteger.BOT; if( decl == TypeFloat.F32) enc.add1(0xF3); if( decl == TypeFloat.F64) enc.add1(0xF2); diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java index 2b7fddb1..4e3cda14 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java @@ -360,6 +360,8 @@ public class x86_64_v2 extends Machine { case NotNode not -> new NotX86(not); case OrNode or -> or(or); case ParmNode parm -> new ParmX86(parm); + case BulkMemPhiNode phi -> new BulkMemPhiNode(phi); + case MemPhiNode phi -> new MemPhiNode(phi); case PhiNode phi -> new PhiNode(phi); case ProjNode prj -> prj(prj); case ReadOnlyNode read-> new ReadOnlyNode(read); @@ -393,9 +395,8 @@ public class x86_64_v2 extends Machine { if( lhs instanceof LoadNode ld && ld.nOuts() == 1 && ld._declaredType.log_size() >= 3) return new AddMemX86(add, address(ld), ld.ptr(), idx, off, scale, 0, rhs); -// if(rhs instanceof LoadNode ld && ld.nOuts() == 1 && ld._declaredType.log_size() >= 3) { -// throw Utils.TODO(); // Swap load sides -// } + if( rhs instanceof LoadNode ld && ld.nOuts()==1 && ld._declaredType.log_size()>=3 ) + return new AddMemX86(add,address(ld),ld.ptr(),idx,off,scale, 0,lhs); // Attempt a full LEA-style break down. // Returns one of AddX86, AddIX86, LeaX86, or LHS diff --git a/chapter21/src/main/java/com/seaofnodes/simple/print/IRPrinter.java b/chapter21/src/main/java/com/seaofnodes/simple/print/IRPrinter.java index ffa1d742..54063cf8 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/print/IRPrinter.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/print/IRPrinter.java @@ -9,6 +9,9 @@ import com.seaofnodes.simple.type.TypeFunPtr; import java.util.ArrayList; import java.util.BitSet; import java.util.HashMap; +import java.util.Collections; +import java.util.Comparator; +import java.util.IdentityHashMap; public abstract class IRPrinter { @@ -178,6 +181,168 @@ public abstract class IRPrinter { } } + // Whole-program dump before scheduling, using printer-private RPO. + public static String prettyPrint(CodeGen code) { + SB sb = new SB(); + printLine(code._start,sb); + ArrayList<Node> globals = new ArrayList<>(); + ArrayList<FunNode> funs = new ArrayList<>(); + for( Node n : code._start._outputs ) { + if( n!=null && n.isConst() ) globals.add(n); + if( n instanceof FunNode fun && !fun.isDead() ) funs.add(fun); + } + globals.sort(Comparator.comparingInt(n -> n._nid)); + for( Node n : globals ) printLine(n,sb); + funs.sort(Comparator.comparingInt(n -> n._nid)); + for( FunNode fun : funs ) printFunction(fun,sb); + printLine(code._stop,sb); + return sb.toString(); + } + + /** Print one function without ever traversing a call-graph linkage. */ + private static void printFunction(FunNode fun, SB sb) { + sb.nl().p("--- "); + fun.sig().print(sb.p(fun._name==null ? "" : fun._name).p(" "),false); + sb.p("----------------------\n"); + + IdentityHashMap<Node,Boolean> owned = new IdentityHashMap<>(); + collectFunction(fun,fun,owned); + ArrayList<CFGNode> post = new ArrayList<>(); + controlPost(fun,owned,new IdentityHashMap<>(),post); + Collections.reverse(post); + + IdentityHashMap<Node,Integer> blocks = new IdentityHashMap<>(); + ArrayList<ArrayList<Node>> body = new ArrayList<>(); + for( int i=0; i<post.size(); i++ ) { + blocks.put(post.get(i),i); + body.add(new ArrayList<>()); + } + ArrayList<Node> nodes = new ArrayList<>(owned.keySet()); + nodes.sort(Comparator.comparingInt(n -> n._nid)); + for( Node n : nodes ) + if( !(n instanceof CFGNode) && !(n instanceof ConstantNode) ) + body.get(block(n,owned,blocks)).add(n); + + IdentityHashMap<Node,Boolean> emitted = new IdentityHashMap<>(); + for( int i=0; i<post.size(); i++ ) { + CFGNode cfg = post.get(i); + sb.nl(); + emitLocalDefs(cfg,owned,emitted,sb); + printLine(cfg,sb); + emitted.put(cfg,Boolean.TRUE); + if( cfg instanceof MultiNode ) emitProjections(cfg,emitted,sb); + // Loop-carried inputs belong in the body, not before their Phi. + for( Node n : body.get(i) ) + if( n instanceof PhiNode ) { + printLine(n,sb); + emitted.put(n,Boolean.TRUE); + } + for( Node n : body.get(i) ) emit(n,owned,emitted,sb); + } + sb.p("--- ").p(fun._name==null ? "" : fun._name).p(" ----------------------\n"); + } + + private static void emit(Node n, IdentityHashMap<Node,Boolean> owned, + IdentityHashMap<Node,Boolean> emitted, SB sb) { + if( emitted.containsKey(n) || n instanceof CFGNode || n instanceof ConstantNode || + n instanceof PhiNode || !owned.containsKey(n) ) return; + if( n instanceof ProjNode ) { + emit(input0(n),owned,emitted,sb); + return; + } + emitted.put(n,Boolean.TRUE); + emitLocalDefs(n,owned,emitted,sb); + printLine(n,sb); + if( n instanceof MultiNode ) emitProjections(n,emitted,sb); + } + + private static void emitProjections(Node n, IdentityHashMap<Node,Boolean> emitted, SB sb) { + for( Node proj : projections(n) ) { + printLine(proj,sb); + emitted.put(proj,Boolean.TRUE); + } + } + + private static void emitLocalDefs(Node n, IdentityHashMap<Node,Boolean> owned, + IdentityHashMap<Node,Boolean> emitted, SB sb) { + for( Node def : n._inputs ) + if( def!=null ) emit(def,owned,emitted,sb); + } + + // Diagnostic placement only: anchor data at its latest definition in CFG + // RPO. Phi inputs do not move the Phi out of its owning header. This uses + // local identity state, never compiler scheduling or dominator caches. + private static int block(Node n, IdentityHashMap<Node,Boolean> owned, + IdentityHashMap<Node,Integer> blocks) { + Integer old = blocks.get(n); + if( old!=null ) return old; + if( !owned.containsKey(n) || n instanceof ConstantNode ) return 0; + blocks.put(n,0); // Break incomplete data cycles in debugger snapshots. + int b=0; + if( input0(n) instanceof CFGNode || n instanceof ProjNode ) + b=block(input0(n),owned,blocks); + else + for( Node def : n._inputs ) + if( def!=null ) b=Math.max(b,block(def,owned,blocks)); + blocks.put(n,b); + return b; + } + + // Complete the CFG walk before inspecting data uses: a Phi's backedge + // users must not pull the loop's closing Region ahead of its body. + private static void controlPost(CFGNode n, IdentityHashMap<Node,Boolean> owned, + IdentityHashMap<Node,Boolean> visit, + ArrayList<CFGNode> post) { + if( visit.put(n,Boolean.TRUE)!=null ) return; + ArrayList<CFGNode> uses = new ArrayList<>(); + if( !(n instanceof ReturnNode) ) + for( Node use : n._outputs ) + if( use instanceof CFGNode cfg && owned.containsKey(cfg) && + !(cfg instanceof FunNode) && + ((cfg instanceof RegionNode && cfg._inputs.find(n)>0) || input0(cfg)==n) ) + uses.add(cfg); + uses.sort(Comparator.comparingInt(use -> use._nid)); + for( CFGNode use : uses ) controlPost(use,owned,visit,post); + post.add(n); + } + + /** Collect one function without following call-graph linkage. */ + private static void collectFunction(Node n, FunNode owner, + IdentityHashMap<Node,Boolean> visit) { + if( n==null || n.isDead() || visit.containsKey(n) ) return; + if( n instanceof FunNode fun && fun!=owner ) return; + if( n instanceof ParmNode && input0(n)!=owner ) return; + if( n instanceof StopNode || n instanceof StartNode ) return; + if( n instanceof CallEndNode && input0(n) instanceof CallNode call && + !visit.containsKey(call) ) return; + visit.put(n,Boolean.TRUE); + // A Return's users are linkage hooks, not continuation in this function. + if( !(n instanceof ReturnNode) ) + for( Node use : n._outputs ) collectFunction(use,owner,visit); + } + + private static ArrayList<Node> projections(Node multi) { + ArrayList<Node> ps = new ArrayList<>(); + for( Node use : multi._outputs ) + if( use instanceof ProjNode && !use.isDead() && input0(use)==multi ) ps.add(use); + ps.sort(Comparator.comparingInt(IRPrinter::sortOrder)); + return ps; + } + + // Raw, bounds-safe graph inspection for the debugger. In particular do + // not call an accessor which might assert, sharpen, cache, or lazily kill. + private static Node input0(Node n) { + return n==null || n._inputs.isEmpty() + ? null : n._inputs.at(0); + } + + + private static int sortOrder(Node n) { + if( n instanceof ProjNode proj ) return proj._idx; + if( n instanceof CProjNode proj ) return proj._idx; + return n._nid; + } + public static String _prettyPrint( CodeGen code ) { SB sb = new SB(); // Print the Start "block" diff --git a/chapter21/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter21/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index d6dc6ae1..a0f1ba74 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -67,7 +67,7 @@ public class SimpleGraphAdapter extends GraphAdapter<Node> { return n instanceof CFGNode ? 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( n instanceof MemMergeNode || i == 1 && (n instanceof MemOpNode || n instanceof ReturnNode || n instanceof CallNode) ) return Role.MEM; + if( n instanceof MemMergeNode || (i == 1 && (n instanceof MemOpNode || n instanceof ReturnNode || n instanceof CallNode || n instanceof NewNode)) ) return Role.MEM; Node def = n.in(i); if( def != null && isMem(def) ) return Role.MEM; return Role.DATA; diff --git a/chapter21/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter21/src/test/java/com/seaofnodes/simple/Chapter10Test.java index 14cf6e01..7da1354f 100644 --- a/chapter21/src/test/java/com/seaofnodes/simple/Chapter10Test.java +++ b/chapter21/src/test/java/com/seaofnodes/simple/Chapter10Test.java @@ -2,6 +2,8 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.codegen.CodeGen; import org.junit.Test; +import com.seaofnodes.simple.node.*; +import java.util.BitSet; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -450,4 +452,121 @@ return 0; assertEquals("return 0;", code.print()); } + @Test public void testReadBeforeStores() { + var code = new CodeGen(""" + 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().opto(); + StopNode stop = code._stop; + assertEquals("1133",Eval2.eval(code,0)); + assertEquals("2244",Eval2.eval(code,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 code = new CodeGen(NESTED_MEMORY).parse().opto(); + StopNode stop = code._stop; + for (int arg=0; arg<7; arg++) { + assertEquals("S{x="+(5L+arg)+",y="+(7L+arg*(arg+1)-(arg>=2 ? 2 : 0))+ + ",z="+(11L+2*arg-(arg>=2 ? 2 : 0))+"}",Eval2.eval(code,arg)); + } + } + + @Test public void testMemoryAtEarlyReturns() { + var code = new CodeGen(""" + 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().opto(); + StopNode stop = code._stop; + for (int arg=0; arg<2; arg++) { + assertEquals(arg==0 ? "S{x=3,y=17,z=7}" : "S{x=11,y=5,z=13}",Eval2.eval(code,arg)); + } + } + + @Test public void testKeepLoadsAtMerge() { + var code = new CodeGen(""" + struct S { int x; }; + S !a = new S; S !b = new S; + a.x = arg; b.x = arg+1; + S !p = a; S !q = b; + if (arg<0) { p=b; q=a; } + int v; + if (arg>1) v=p.x; else v=q.x; + return v; + """).parse().opto(); + StopNode stop = code._stop; + // The one-step safety check stops at the memory aggregate. + assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet())); + assertEquals("-1",Eval2.eval(code,-1)); + assertEquals("1",Eval2.eval(code,0)); + assertEquals("3",Eval2.eval(code,3)); + } + + @Test public void testDropStores() { + var code = new CodeGen(""" + struct S { int x; }; + S !s = new S; + if (arg) s.x=arg+1; else s.x=arg+2; + return s; + """).parse().opto(); + StopNode stop = code._stop; + assertEquals(1,countMemoryNodes(stop,StoreNode.class,new BitSet())); + assertEquals("S{x=2}",Eval2.eval(code,0)); + assertEquals("S{x=4}",Eval2.eval(code,3)); + } + + @Test public void testKeepReadsBeforeWrites() { + var code = new CodeGen(""" + struct S { int x; int y; }; + S !a = new S; S !b = new S; + a.x=arg; b.x=arg+1; + S !p=a; S !q=b; + if (arg<0) { p=b; q=a; } + int v; + if (arg>1) { v=p.x; p.x=41; p.y=5; } + else { v=q.x; q.x=42; } + return v; + """).parse().opto(); + StopNode stop = code._stop; + assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet())); + assertEquals("-1",Eval2.eval(code,-1)); + assertEquals("1",Eval2.eval(code,0)); + assertEquals("3",Eval2.eval(code,3)); + } + + private static int countMemoryNodes(Node n, Class<?> kind, BitSet seen) { + if (n==null || seen.get(n._nid)) return 0; + seen.set(n._nid); + int cnt=kind.isInstance(n) ? 1 : 0; + for (Node def : n._inputs) cnt+=countMemoryNodes(def,kind,seen); + return cnt; + } } diff --git a/chapter21/src/test/java/com/seaofnodes/simple/Chapter10bTest.java b/chapter21/src/test/java/com/seaofnodes/simple/Chapter10bTest.java new file mode 100644 index 00000000..5d78ae06 --- /dev/null +++ b/chapter21/src/test/java/com/seaofnodes/simple/Chapter10bTest.java @@ -0,0 +1,60 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import org.junit.Test; +import static org.junit.Assert.*; + +public class Chapter10bTest { + @Test public void testIndependentField() { + var code = new CodeGen(""" + 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().opto(); + var stop = code._stop; + assertEquals("return 42;",stop.toString()); + assertEquals("42",Eval2.eval(code,5)); + assertNull(stop.walk(n -> n instanceof LoadNode ? n : null)); + } + + @Test public void testParallelMemoryPhis() { + var code = new CodeGen(""" + 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().opto(); + var stop = code._stop; + 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) && !(n instanceof ParmNode)); + return null; + }); + assertTrue(phis[0]>=3); + assertEquals("S{x=1,y=2,z=3}",Eval2.eval(code,4)); + } + + @Test public void testLateSliceWorklist() { + // 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 code = new CodeGen(Chapter10Test.NESTED_MEMORY,com.seaofnodes.simple.type.TypeInteger.BOT,seed).parse(); + code.opto(); + assertEquals("S{x=9,y=25,z=17}",Eval2.eval(code,4)); + } + } +} diff --git a/chapter21/src/test/java/com/seaofnodes/simple/Chapter14Test.java b/chapter21/src/test/java/com/seaofnodes/simple/Chapter14Test.java index 7af35b56..b83516e3 100644 --- a/chapter21/src/test/java/com/seaofnodes/simple/Chapter14Test.java +++ b/chapter21/src/test/java/com/seaofnodes/simple/Chapter14Test.java @@ -174,7 +174,7 @@ while(v1) break; return v1; """); code.parse().opto(); - assertEquals("return Phi(Loop,(((1<<arg)&1)&Shl),0);", code.print()); + assertEquals("return (Phi(Loop,((1<<arg)&1),0)&Phi(Loop,Shl,4294967295));", code.print()); assertEquals("1", Eval2.eval(code, 0)); } diff --git a/chapter21/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/chapter21/src/test/java/com/seaofnodes/simple/Chapter16Test.java index 5899763f..571a0d54 100644 --- a/chapter21/src/test/java/com/seaofnodes/simple/Chapter16Test.java +++ b/chapter21/src/test/java/com/seaofnodes/simple/Chapter16Test.java @@ -255,4 +255,27 @@ return new Square; assertEquals("Square{side=3.0,diag=1.7320508075688772}", Eval2.eval(code, 3)); assertEquals("Square{side=4.0,diag=2.0}", Eval2.eval(code, 4)); } + static final String CONSTRUCTOR_MEMORY = """ + struct S { int x; int y; }; + struct T { int z=arg+40; }; + T !t = new T; + S !a = new S { x=11; y=7; }; + S !b = new S { x=22; y=9; }; + S !p=a; + if (arg) p=b; + int before=p.x; + S !c = new S { + x=p.x+1; + { int i=0; while (i<2) { p.y=p.y+1; i=i+1; } } + y=p.y; + }; + p.x=33; + return before*10000+c.x*100+c.y+t.z; + """; + + @Test public void testConstructorMemory() { + var code = new CodeGen(CONSTRUCTOR_MEMORY).parse().opto(); + assertEquals("111249",Eval2.eval(code,0)); + assertEquals("222352",Eval2.eval(code,1)); + } } diff --git a/chapter21/src/test/java/com/seaofnodes/simple/Chapter17Test.java b/chapter21/src/test/java/com/seaofnodes/simple/Chapter17Test.java index 78515083..5de4ec3c 100644 --- a/chapter21/src/test/java/com/seaofnodes/simple/Chapter17Test.java +++ b/chapter21/src/test/java/com/seaofnodes/simple/Chapter17Test.java @@ -368,7 +368,7 @@ var b = arg ? new Bar { next = (arg==2) ? new Bar{x=2;}; x=1; }; return b ? b.next ? b.next.x : b.x; // parses "b ? (b.next ? b.next.x : b.x) : 0" """); code.parse().opto(); - assertEquals("return Phi(Region,.x,0);", code.print()); + assertEquals("return Phi(Region,.x,0,.x);", code.print()); assertEquals("0", Eval2.eval(code, 0)); assertEquals("1", Eval2.eval(code, 1)); assertEquals("2", Eval2.eval(code, 2)); diff --git a/chapter21/src/test/java/com/seaofnodes/simple/Chapter18Test.java b/chapter21/src/test/java/com/seaofnodes/simple/Chapter18Test.java index 5743546e..e1e254fe 100644 --- a/chapter21/src/test/java/com/seaofnodes/simple/Chapter18Test.java +++ b/chapter21/src/test/java/com/seaofnodes/simple/Chapter18Test.java @@ -438,4 +438,70 @@ return p.coffee_count; assertEquals("1", Eval2.eval(code, 2)); } + static final String CALL_MEMORY = """ + struct S { int x; int y; }; + val bump = { S s, int d -> int old=s.x; s.x=old+d; return old; }; + S !a = new S { x=10; y=7; }; + S !b = new S { x=20; y=9; }; + S !p=a; if (arg) p=b; + int before=p.x; + int old=bump(p,3); + int after=p.x; + bump(a,5); + return before*1000000+old*10000+after*100+a.x+a.y; + """; + + @Test public void testCallMemory() { + var code = new CodeGen(CALL_MEMORY).parse().opto(); + int[] calls={0}; + code._stop.walk(n -> { if(n instanceof CallNode) calls[0]++; return null; }); + assertEquals(2,calls[0]); // Keep real calls so this checks scheduling across them. + assertEquals("10101325",Eval2.eval(code,0)); + assertEquals("20202322",Eval2.eval(code,1)); + code._stop.walk(n -> { + if(n instanceof CallNode call) { + assertEquals(5,call.nIns()); // No scheduling edges may become extra arguments. + assertTrue(call.fptr()._type instanceof TypeFunPtr); + } + return null; + }); + } + + static final String RECURSIVE_MEMORY = """ + struct S { int x; }; + val rec = { S s, int n -> + if (n==0) return s.x; + int before=s.x; + s.x=before+1; + return before+rec(s,n-1); + }; + S !s=new S { x=10; }; + int value=rec(s,arg); + return value*100+s.x; + """; + + @Test public void testRecursiveMemory() { + var code = new CodeGen(RECURSIVE_MEMORY).parse().opto(); + for(int n=0; n<7; n++) + assertEquals(Long.toString((10L*(n+1)+n*(n+1)/2)*100+10+n),Eval2.eval(code,n)); + } + + static final String INLINE_MEMORY = """ + struct S { int x; int y; }; + val make = { S s -> + S t = new S { x=s.x+1; y=3; }; + s.y=9; + return t; + }; + S !a = new S { x=arg+40; y=7; }; + S t=make(a); + return a.x*10000+a.y*100+t.x+t.y; + """; + + @Test public void testInlineAllocationMemory() { + var code = new CodeGen(INLINE_MEMORY).parse().opto(); + assertNull(code._stop.walk(n -> n instanceof CallNode ? n : null)); + assertEquals("400944",Eval2.eval(code,0)); + assertEquals("420946",Eval2.eval(code,2)); + } } diff --git a/chapter21/src/test/java/com/seaofnodes/simple/Chapter19Test.java b/chapter21/src/test/java/com/seaofnodes/simple/Chapter19Test.java index 1c427d4f..1149c518 100644 --- a/chapter21/src/test/java/com/seaofnodes/simple/Chapter19Test.java +++ b/chapter21/src/test/java/com/seaofnodes/simple/Chapter19Test.java @@ -350,4 +350,72 @@ return sq(arg) + sq(3); code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("Stop[ return (add,#2,#2); return (mul,Parm_x(sq,int),x); ]", code.print()); } + + @Test public void testSelectedMemory() { + for( String cpu : new String[]{"x86_64_v2","riscv","arm"} ) + for( String src : new String[]{Chapter10Test.NESTED_MEMORY,Chapter16Test.CONSTRUCTOR_MEMORY, + Chapter18Test.CALL_MEMORY,Chapter18Test.RECURSIVE_MEMORY} ) { + var code = new CodeGen(src).parse().opto().typeCheck(); + var aliases = memoryPhis(code._stop); + code.loopTree().instSelect(cpu,"SystemV"); + assertEquals(aliases,memoryPhis(code._stop)); + code.GCM().localSched(); + code._stop.walk(n -> { + if( !(n instanceof CFGNode) && !(n instanceof ProjNode) ) + assertTrue(n.in(0) instanceof CFGNode); + if( n instanceof NewNode nn ) { + assertEquals(2,((TypeTuple)nn._type)._types.length); + assertTrue(nn.mem() instanceof MemMergeNode); + assertNull(nn.mem().in(1)); // Partial allocation input. + nn.cacheRegs(code); + MachNode mach = (MachNode)nn; + assertNull(mach.regmap(1)); + assertNotNull(mach.regmap(2)); + assertNotNull(mach.outregmap(0)); + assertNull(mach.outregmap(1)); + } + if( n instanceof CallNode call ) { + assertEquals(4,call.nIns()); // ctrl, memory, two args; direct target is embedded. + } + return null; + }); + } + } + + private static java.util.ArrayList<String> memoryPhis(Node stop) { + var phis = new java.util.ArrayList<String>(); + stop.walk(n -> { + if( n instanceof MemPhiNode phi ) phis.add("alias:"+phi._alias); + if( n instanceof BulkMemPhiNode phi ) phis.add("bulk:"+phi._aliases); + assertFalse(n instanceof PhiNode && n.isMem() && + !(n instanceof MemPhiNode) && !(n instanceof BulkMemPhiNode) && !(n instanceof ParmNode)); + return null; + }); + java.util.Collections.sort(phis); + return phis; + } + + @Test public void testFoldedReadBeforeWrites() { + var code = new CodeGen(""" + struct S { int x; }; + S !a = new S { x=11; }; S !b = new S { x=22; }; + S !p = a; if( arg ) p = b; + int before = p.x + arg; + a.x=33; b.x=44; + return before; + """).parse().opto().typeCheck().loopTree().instSelect("x86_64_v2","SystemV").GCM().localSched(); + var read = (MemOpNode)code._stop.walk(n -> + n instanceof com.seaofnodes.simple.node.cpus.x86_64_v2.AddMemX86 ? n : null); + assertNotNull(read); + int writes = 0; + for( Node use : read.antiDeps() ) + if( use instanceof MemOpNode st && st.isMem() ) { + assertSame(read.cfg0(),st.cfg0()); + assertTrue(st._inputs.find(read)>=0); + assertTrue(read.cfg0()._outputs.find(read)<st.cfg0()._outputs.find(st)); + assertNull(((MachNode)st).regmap(st._inputs.find(read))); + writes++; + } + assertEquals(1,writes); // The next Store follows this first clobber through memory. + } } diff --git a/chapter21/src/test/java/com/seaofnodes/simple/Chapter20Test.java b/chapter21/src/test/java/com/seaofnodes/simple/Chapter20Test.java index 0691d4a2..250acaa2 100644 --- a/chapter21/src/test/java/com/seaofnodes/simple/Chapter20Test.java +++ b/chapter21/src/test/java/com/seaofnodes/simple/Chapter20Test.java @@ -142,9 +142,9 @@ return sqrt(farg) + sqrt(farg+2.0); @Test public void testAlloc2() { String src = "int[] !xs = new int[3]; xs[arg]=1; return xs[arg&1];"; - testTarget(src,"x86_64_v2","SystemV",3,"return .[];"); + testTarget(src,"x86_64_v2","SystemV",4,"return .[];"); testTarget(src,"riscv","SystemV",8,"return .[];"); - testTarget(src,"arm","SystemV",10,"return .[];"); + testTarget(src,"arm","SystemV",9,"return .[];"); } @Test @@ -160,7 +160,7 @@ for( int i=0; i<ary#-1; i++ ) ary[i+1] += ary[i]; return ary[1] * 1000 + ary[3]; // 1 * 1000 + 6 """; - testTarget(src,"x86_64_v2", "SystemV",9,"return .[];"); + testTarget(src,"x86_64_v2", "SystemV",7,"return .[];"); testTarget(src,"riscv" , "SystemV",7,"return (add,.[],(mul,.[],1000));"); testTarget(src,"arm" , "SystemV",5,"return (add,.[],(mul,.[],1000));"); } @@ -203,9 +203,9 @@ s.cs[0] = 67; // C s.cs[1] = 108; // l hashCode(s); """; - testTarget(src,"x86_64_v2", "SystemV",14,null); - testTarget(src,"riscv" , "SystemV", 14,null); - testTarget(src,"arm" , "SystemV", 13,null); + testTarget(src,"x86_64_v2", "SystemV",15,null); + testTarget(src,"riscv" , "SystemV", 10,null); + testTarget(src,"arm" , "SystemV", 10,null); } @Test @@ -364,8 +364,29 @@ return a; """; testTarget(src,"x86_64_v2", "SystemV",52,null); testTarget(src,"riscv" , "SystemV",44,null); - testTarget(src,"arm" , "SystemV",44,null); + testTarget(src,"arm" , "SystemV",41,null); //assertEquals("int[ 1,2,3,4,5,6,7,8,9,10,11]", Eval2.eval(code, 11)); } + + @Test public void testMemoryAllocation() { + for( String cpu : new String[]{"x86_64_v2","riscv","arm"} ) + for( String src : new String[]{Chapter10Test.NESTED_MEMORY,Chapter16Test.CONSTRUCTOR_MEMORY, + Chapter18Test.CALL_MEMORY,Chapter18Test.RECURSIVE_MEMORY, + "int[] !a = new int[3]; a[arg]=arg; return a[0]+1;"} ) { + var code = new CodeGen(src).parse().opto().typeCheck().loopTree() + .instSelect(cpu,"SystemV").GCM().localSched().regAlloc(); + com.seaofnodes.simple.codegen.RegAllocTestSupport.checkRegisters(code); + code._stop.walk(n -> { + // Whole memory, precise Phis, and memory projections need no register. + if( n._type instanceof com.seaofnodes.simple.type.TypeMem ) + assertEquals(-1,code._regAlloc.regnum(n)); + if( n instanceof com.seaofnodes.simple.node.NewNode nn ) { + assertTrue(code._regAlloc.regnum(nn.proj(0))>=0); + assertEquals(-1,code._regAlloc.regnum(nn.proj(1))); + } + return null; + }); + } + } } diff --git a/chapter21/src/test/java/com/seaofnodes/simple/Chapter21AllocTest.java b/chapter21/src/test/java/com/seaofnodes/simple/Chapter21AllocTest.java index 39b410d5..bc300217 100644 --- a/chapter21/src/test/java/com/seaofnodes/simple/Chapter21AllocTest.java +++ b/chapter21/src/test/java/com/seaofnodes/simple/Chapter21AllocTest.java @@ -49,7 +49,7 @@ return sqrt(arg) + sqrt(arg+2); String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/stringHash.smp")); Chapter21Test.testCPU(src,"x86_64_v2", "SystemV", 9,null); Chapter21Test.testCPU(src,"riscv" , "SystemV", 3,null); - Chapter21Test.testCPU(src,"arm" , "SystemV", 3,null); + Chapter21Test.testCPU(src,"arm" , "SystemV", 1,null); } } diff --git a/chapter21/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/chapter21/src/test/java/com/seaofnodes/simple/Chapter21Test.java index 6c943904..a8712a2e 100644 --- a/chapter21/src/test/java/com/seaofnodes/simple/Chapter21Test.java +++ b/chapter21/src/test/java/com/seaofnodes/simple/Chapter21Test.java @@ -217,6 +217,7 @@ public class Chapter21Test { @Test public void testCoalescing() { com.seaofnodes.simple.codegen.RegAllocTestSupport.coalescing(); } + @Test public void testCopyForward() throws Exception { com.seaofnodes.simple.codegen.RegAllocTestSupport.copyForward(); } @Test public void testRisc64BitStore() { byte[] mem = new byte[24]; EvalRisc5 cpu = new EvalRisc5(mem,mem.length); @@ -299,7 +300,7 @@ public class Chapter21Test { String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/stringHash.smp")); testCPU(src,"x86_64_v2", "SystemV", 9,null); testCPU(src,"riscv" , "SystemV", 3,null); - testCPU(src,"arm" , "SystemV", 3,null); + testCPU(src,"arm" , "SystemV", 1,null); } @Test public void testStringExport() throws IOException { @@ -353,11 +354,11 @@ public class Chapter21Test { String sprimes = sb.p("]").toString(); // Compile, link against native C; expect the above string of primes to be printed out by C - TestC.run("sieve",sprimes, 178); + TestC.run("sieve",sprimes, 185); // Evaluate on RISC5 emulator; expect return of an array of primes in // the simulated heap. - EvalRisc5 R5 = TestRisc5.build("sieve", 100, 89, false); + EvalRisc5 R5 = TestRisc5.build("sieve", 100, 92, false); int trap = R5.step(10000); assertEquals(0,trap); // Return register A0 holds sieve(100) @@ -369,7 +370,7 @@ public class Chapter21Test { // Evaluate on ARM5 emulator; expect return of an array of primes in // the simulated heap. - EvalArm64 A5 = TestArm64.build("sieve", 100, 93, false); + EvalArm64 A5 = TestArm64.build("sieve", 100, 91, false); int trap_arm = A5.step(10000); assertEquals(0, trap_arm); int ary_arm = (int)A5.regs[arm.X0]; diff --git a/chapter21/src/test/java/com/seaofnodes/simple/Eval2.java b/chapter21/src/test/java/com/seaofnodes/simple/Eval2.java index 82839399..dabd63cc 100644 --- a/chapter21/src/test/java/com/seaofnodes/simple/Eval2.java +++ b/chapter21/src/test/java/com/seaofnodes/simple/Eval2.java @@ -331,7 +331,7 @@ public abstract class Eval2 { private static Object alloc(NewNode alloc) { TypeStruct type = alloc._ptr._obj; if( type.isAry() ) { - long sz = (Long)val(alloc.in(1)); + long sz = (Long)val(alloc.size()); long x = offToIdx(sz, type); int n = (int)x; if( n!=x || n<0 ) @@ -444,9 +444,9 @@ public abstract class Eval2 { case TypeRPC rpc -> sb.p(x.toString()); case TypeMem mem -> sb.p("$mem"); case TypeTuple tt -> { - if( tt._types.length>1 && tt._types[1] instanceof TypeMemPtr ) + if( tt._types.length>1 && tt._types[0] instanceof TypeMemPtr ) // Assume a NewNode - yield _print( tt._types[1], x, sb, visit ); + yield _print( tt._types[0], x, sb, visit ); throw Utils.TODO(); } case Type tt -> { diff --git a/chapter21/src/test/java/com/seaofnodes/simple/EvalArm64.java b/chapter21/src/test/java/com/seaofnodes/simple/EvalArm64.java index e5aa729b..f8996b00 100644 --- a/chapter21/src/test/java/com/seaofnodes/simple/EvalArm64.java +++ b/chapter21/src/test/java/com/seaofnodes/simple/EvalArm64.java @@ -116,10 +116,11 @@ public class EvalArm64 { } // for the opcode we just match for bits[31:24]; switch(opcode1) { - case 0xD2: { - // movz - // get immediate - rval = (ir >> 5) & 0x7FFF; + case 0xD2: // MOVZ + case 0xF2: { // MOVK preserves the other halfwords + int shift = ((ir >> 21) & 3)*16; + rval = ((ir >> 5) & 0xFFFFL) << shift; + if( opcode1==0xF2 ) rval |= regs[rdid] & ~(0xFFFFL << shift); break; } case 0x38: { diff --git a/chapter21/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java b/chapter21/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java new file mode 100644 index 00000000..0de4e960 --- /dev/null +++ b/chapter21/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java @@ -0,0 +1,176 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegAllocTestSupport; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.cpus.arm.arm; +import com.seaofnodes.simple.node.cpus.riscv.riscv; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.Test; +import static org.junit.Assert.*; + +// Execution regressions are separate from the fixed spill-measurement cohorts. +public class MemoryEncodingTest { + @Test public void testLoopLoadSearch() { + String[] sources = { + """ + u8[] !program=new u8[106]; u8[] !out=new u8[0]; + int sum=0; + for(int i=0; i<arg; i++) { + sum+=program#; + if(i==1) out=new u8[i+1]; + else if(i==3) out=new u8[2]; + } + return sum+out#; + """, + // One possibly aliasing arm must reject the unchanged-memory proof. + """ + struct S { int x; }; S !a=new S; S !b=new S; + a.x=10; b.x=20; S !p=a; if(arg&1) p=b; + int sum=0; + for(int i=0; i<6; i++) { + if(i==2) p.x=30; + else if(i==4) b.x=40; + sum+=a.x; + } + return sum; + """, + // Unchanged memory does not imply an unchanged loop-carried pointer. + """ + struct S { int x; }; S !a=new S; S !b=new S; + a.x=10; b.x=20; S !p=a; int sum=0; + for(int i=0; i<6; i++) { + if(i==2) a.x=30; + sum+=p.x; + p=b; if(i&1) p=a; + } + return sum; + """, + // All arms fold, but their values differ: build a value Phi. + """ + struct S { int x; }; S !a=new S; a.x=1; int sum=0; + for(int i=0; i<6; i++) { + sum+=a.x; + if(i&1) a.x=2; + else if(i==2) a.x=3; + else a.x=4; + } + return sum; + """ + }; + for( int p=0; p<sources.length; p++ ) { + CodeGen code=new CodeGen(sources[p]).parse().opto(); + if( p==0 ) assertNull(code._stop.walk(n -> + n instanceof LoadNode ld && ld.ptr() instanceof ProjNode ptr && + ptr.in(0) instanceof NewNode nn && + nn.size()._type==com.seaofnodes.simple.type.TypeInteger.constant(110) ? ld : null)); + for( int arg=0; arg<7; arg++ ) { + long expected=switch(p) { + case 0 -> 106*arg+(arg>=2 ? 2 : 0); + case 1 -> (arg&1)==0 ? 140 : 60; + case 2 -> 130; + default -> 16; + }; + assertEquals(Long.toString(expected),Eval2.eval(code,arg)); + } + } + } + + private static final String[] SOURCES = { + Chapter16Test.CONSTRUCTOR_MEMORY, + Chapter18Test.CALL_MEMORY, + Chapter18Test.RECURSIVE_MEMORY, + Chapter18Test.INLINE_MEMORY, + "int[] !a = new int[7]; a[arg]=arg+3; return a[0]+1;", + """ + int[] !a = new int[7]; + for( int i=0; i<7; i++ ) a[i]=i+arg; + for( int i=0; i<6; i++ ) a[i+1]+=a[i]; + return a[6]; + """ + }; + + private static long expected(int program, long arg) { + return switch(program) { + case 0 -> arg==0 ? 111249 : 222351+arg; + case 1 -> arg==0 ? 10101325 : 20202322; + case 2 -> (10*(arg+1)+arg*(arg+1)/2)*100+10+arg; + case 3 -> 400944+arg*10001; + case 4 -> arg==0 ? 4 : 1; + case 5 -> 21+7*arg; + default -> throw new AssertionError(); + }; + } + + @Test public void testEmulatedMemory() throws IOException { + for( String target : new String[]{"riscv","arm"} ) + for( int p=0; p<SOURCES.length; p++ ) { + CodeGen code = new CodeGen(SOURCES[p]).driver(target,"SystemV",null); + RegAllocTestSupport.checkRegisters(code); + int entry=0; + for( var n : code._start._outputs ) + if( n instanceof FunNode fun && "main".equals(fun._name) ) + entry=code._encoding._opStart[fun._nid]; + for( int arg=0; arg<7; arg++ ) { + byte[] image = new byte[1<<20]; + byte[] bits = code._encoding.bits(); + System.arraycopy(bits,0,image,0,bits.length); + long result; + if( target.equals("riscv") ) { + EvalRisc5 cpu = new EvalRisc5(image,1<<16); + cpu._pc=entry; + cpu.regs[riscv.A0]=arg; + assertEquals(target+" program "+p+" arg "+arg,0,cpu.step(100000)); + result=cpu.regs[riscv.A0]; + } else { + EvalArm64 cpu = new EvalArm64(image,1<<16); + cpu._pc=entry; + cpu.regs[arm.X0]=arg; + assertEquals(target+" program "+p+" arg "+arg,0,cpu.step(100000)); + result=cpu.regs[arm.X0]; + } + assertEquals(target+" program "+p+" arg "+arg,expected(p,arg),result); + } + } + } + + @Test public void testNeverMemory() { + // A synthetic exit must retain both precise slices of a no-exit loop. + // Stop at scheduling: the existing allocator cannot coalesce TOP on + // the synthetic scalar return arm. + for( String target : new String[]{"x86_64_v2","riscv","arm"} ) { + CodeGen code = new CodeGen(""" + struct S { int x; int y; }; + S !a=new S; + if( arg ) while( 1 ) { a.x+=arg; a.y+=a.x; } + return a.x+a.y; + """).driver(CodeGen.Phase.LoopTree); + assertNotNull(code._stop.walk(n -> { + if( n instanceof MemMergeNode mem && + mem.in(0) instanceof CProjNode exit && exit.in(0) instanceof NeverNode ) { + assertTrue(mem.alias(2) instanceof MemPhiNode); + assertTrue(mem.alias(3) instanceof MemPhiNode); + return mem; + } + return null; + })); + code.instSelect(target,"SystemV").GCM().localSched(); + } + } + + @Test public void testNativeMemory() throws IOException { + Files.createDirectories(Path.of("build/objs")); + for( int p=0; p<SOURCES.length; p++ ) { + String file="build/objs/memory"+p; + StringBuilder c = new StringBuilder("extern long long memory(long long);\nint main() {\n"); + for( int arg=0; arg<7; arg++ ) + c.append("if(memory(").append(arg).append(")!=").append(expected(p,arg)).append("LL) return ").append(arg+1).append(";\n"); + c.append("return 0;\n}\n"); + Files.writeString(Path.of(file+".c"),c); + TestC._run("val memory = { int arg -> "+SOURCES[p]+" };", + TestC.CALL_CONVENTION,"",file+".c",file,"S","",-1); + } + } +} diff --git a/chapter21/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java b/chapter21/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java index abc2ae82..3f624fcd 100644 --- a/chapter21/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java +++ b/chapter21/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java @@ -166,6 +166,31 @@ public class RegAllocTestSupport { assertFalse("A fixed definition clobbers even before coloring",uncolored.sameBlockNoClobber(copy)); } + public static void copyForward() throws Exception { + // Forwarding, fixed operand, two-address tie, intervening clobber, + // another user, and a scheduling-only edge. + for( int kind=0; kind<6; kind++ ) { + CodeGen code = graph(); + RegAlloc alloc = new RegAlloc(code); + Op def = new Op(A,null,null,false,code._start); + alloc.newLRG(def)._reg=0; + SplitNode copy = code._mach.split("test",(byte)0,null); + copy.setDef(0,code._start); copy.setDef(1,def); + alloc.newLRG(copy)._reg=1; + if( kind==3 ) new Op(null,null,A,false,code._start); + int twoAddress = kind==2 ? 1 : 0; + RegMask mask = kind==1 ? B : kind==5 ? null : new RegMask(3L); + Op use = new Op(null,mask,null,false,code._start,copy) { + @Override public int twoAddress() { return twoAddress; } + }; + if( kind==4 ) new Op(null,B,null,false,code._start,copy); + var post = RegAlloc.class.getDeclaredMethod("postColor"); + post.setAccessible(true); post.invoke(alloc); + assertSame("copy case "+kind,kind==0 ? def : copy,use.in(1)); + assertEquals(kind==0 ? 0 : 1,alloc._spills); + } + } + public static void coalescing() { // Merge, incompatible masks, interference, capacity, and adjacency remapping. for( int kind=0; kind<5; kind++ ) { diff --git a/chapter21/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/chapter21/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java index a3b87e90..3f023bb2 100644 --- a/chapter21/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java +++ b/chapter21/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java @@ -185,7 +185,7 @@ public class Evaluator { Object[] body=null; int num; if( type.isAry() ) { - long sz = (Long)val(alloc.in(1)); + long sz = (Long)val(alloc.size()); long n = offToIdx(sz, type); if( n < 0 ) throw new NegativeArraySizeException(""+n); @@ -199,21 +199,17 @@ public class Evaluator { assert elem instanceof TypeMemPtr; } // Length value - body[0] = vall(alloc.in(2+2)); + body[0] = n; } else { body = new Object[num = type._fields.length]; for( int i=0; i<num; i++ ) body[i] = switch( alloc._ptr._obj._fields[i]._type ) { case TypeInteger ti -> 0L; - case TypeFloat tf -> 0; + case TypeFloat tf -> 0D; default -> null; }; } - Object[] mems = new Object[type._fields.length+2]; - // mems[0] is control - mems[1] = new Obj(type,body); // the ref - // mems[2+...] are memory aliases - return mems; + return new Object[]{new Obj(type,body),null}; } private Object load(LoadNode load) { diff --git a/chapter22/README.md b/chapter22/README.md index 229c78d9..426db34f 100644 --- a/chapter22/README.md +++ b/chapter22/README.md @@ -29,6 +29,12 @@ Let's break it down. pool*, and from there in an ELF file's RODATA section. +Chapter 10b's lazy memory partitioning continues here: the parser carries one +`$mem`, and BulkMemPhi/MemPhi discover field aliases during optimization. +Allocations consume a partial MemMerge and produce `{ptr, $mem}`; calls carry +whole memory. The loop Load search follows every arm of a memory merge, so +BrainFuck's invariant program length folds out of its main loop. + You can also read [this chapter](https://github.com/SeaOfNodes/Simple/tree/linear-chapter22) 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-chapter21...linear-chapter22) it to the previous chapter. @@ -189,17 +195,23 @@ are cheap to split. Register order breaks ties between callee saves. These are small preferences, not a general cost model. Grouping popular values by register class is left for Chapter 23, cold loop splits for 24, and area/cost ranking for 25. +Chapter 21's adjacent-copy forwarding also carries forward: after coloring, +a sole use in the next instruction can read the source register when its +operand mask permits it and no two-address tie is broken. The table below +includes this cleanup and the lazy memory forward port. Cohort 22 now also includes the two zero-move C return +ABI checks added since the earlier audit (26 entries instead of 24). + Run `make spill-stats` in this directory. Each row below uses **this chapter's compiler**, default worklist seed 123, and the same source/target combinations as the earlier cohort. The Windows run combines x86 SystemV/Win64 and RISC-V/ARM SystemV. Diagnostic graphs and mask regressions do not contribute to the totals. -| Program cohort | Compilations | Split/move count | Loop-weighted count | +| Program cohort | Compilations | Retained moves | Loop-weighted moves | |---|---:|---:|---:| | Chapter 20 | 39 | 324 | 443 | -| Chapter 21 | 52 | 447 | 986 | -| Chapter 22 | 24 | 67 | 67 | -| **Total** | **115** | **838** | **1,496** | +| Chapter 21 | 52 | 432 | 964 | +| Chapter 22 | 26 | 67 | 67 | +| **Total** | **117** | **823** | **1,474** | `_spills` counts retained SplitNodes, including register moves; `_spillScaled` weights each by `8^loopDepth`. These are compiler estimates, not measured runtime @@ -207,7 +219,7 @@ memory traffic. The reporter also prints individual allocations and CPU/ABI sums Chapter 21's original `int age` person example is preserved as `person21`; this chapter's `i32 age` version and revised infinite-loop example belong to cohort 22. -For a controlled comparison, the same compiler and correctness fixes with +Before adjacent-copy forwarding, a controlled comparison of the same compiler and correctness fixes with Chapter 21's color preferences and spill ordering produce 885 moves and 1,543 weighted moves. This chapter saves **47 weighted moves (3.0%)**. RISC-V BrainFuck improves from 42 to 28 and ARM from 34 to 28 in each of its two cohorts. Some @@ -219,7 +231,9 @@ The original Chapter 22 snapshot, which mixed several later heuristics, produced staged version saves 240 weighted moves (13.8%). Neither comparison should be confused with comparing entire compiler chapters: lowering also changes. For example, the frozen Chapter 20 String input has no explicit return, and Chapter -22's default-return handling eliminates that workload at the default seed. +22's default-return handling eliminated that workload in the earlier audit. +With lazy memory, some target compilations retain callable bodies; the current +table includes their moves. The full suite and statistics runner pass. Routine backend comparisons use a fixed optimizer seed: diff --git a/chapter22/src/main/java/com/seaofnodes/simple/Parser.java b/chapter22/src/main/java/com/seaofnodes/simple/Parser.java index 8132f782..7a60a2dc 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/Parser.java @@ -137,7 +137,7 @@ public class Parser { _scope.define(ScopeNode.ARG0, TypeInteger.BOT, false, null, _lexer); ctrl(XCTRL); - _scope.mem(new MemMergeNode(false)); + _scope.mem(con(TypeMem.BOT)); // Parse the sys import _lexer = new Lexer(com.seaofnodes.simple.sys.SYS); @@ -207,7 +207,7 @@ public class Parser { // Build a multi-exit return point for all function returns RegionNode r = new RegionNode((Lexer)null,null,null).init(); assert r.inProgress(); - PhiNode rmem = new PhiNode(ScopeNode.MEM0,TypeMem.BOT,r,null).init(); + PhiNode rmem = new BulkMemPhiNode(ScopeNode.MEM0,r,null).init(); PhiNode rrez = new PhiNode(ScopeNode.ARG0,Type.BOTTOM,r,null).init(); ReturnNode ret = new ReturnNode(r, rmem, rrez, rpc, fun).init(); fun.setRet(ret); @@ -219,11 +219,8 @@ public class Parser { // external calls. _scope.push(ScopeNode.Kind.Function); ctrl(fun); // Scope control from function - // Private mem alias tracking per function - MemMergeNode mem = new MemMergeNode(true); - mem.addDef(null); // Alias#0 - mem.addDef(new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,con(TypeMem.BOT)).peephole()); // All aliases - _scope.mem(mem); + // Unknown callers supply whole memory; field aliases are discovered lazily. + _scope.mem(new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,con(TypeMem.BOT)).peephole()); // All args, "as-if" called externally for( int i=0; i<ids.length; i++ ) { Type t = sig.arg(i); @@ -238,7 +235,7 @@ public class Parser { // Last expression is the return except for the top-level main if( ctrl()._type==Type.CONTROL ) if( fun.sig() != _code._main ) - fun.addReturn(ctrl(), _scope.mem().merge(), last); + fun.addReturn(ctrl(), _scope.mem(), last); else { fun.setDef(1,XCTRL); // Kill default main _code.addAll(fun._outputs); @@ -604,9 +601,8 @@ public class Parser { */ private Node parseReturn() { var expr = require(parseAsgn(), ";"); - // Need default memory, since it can be lazy, need to force - // a non-lazy Phi - _fun.addReturn(ctrl(), _scope.mem().merge(), expr); + // Force the lazy whole-memory Phi before closing this exit. + _fun.addReturn(ctrl(), _scope.mem(), expr); ctrl(XCTRL); // Kill control return expr; } @@ -1272,29 +1268,31 @@ public class Parser { Field[] fs = obj._fields; if( fs==null ) throw error("Unknown struct type '" + obj._name + "'"); - int len = fs.length; - Node[] ns = new Node[2+len]; - ns[0] = ctrl(); // Control in slot 0 - // Total allocated length in bytes - ns[1] = size; - // Memory aliases for every field - for( int i = 0; i < len; i++ ) - ns[2+i] = memAlias(fs[i]._alias); - Node nnn = new NewNode(TypeMemPtr.make(obj), ns).peephole().keep(); - for( int i = 0; i < len; i++ ) - memAlias(fs[i]._alias, new ProjNode(nnn,i+2,memName(fs[i]._alias)).peephole()); - Node ptr = new ProjNode(nnn.unkeep(),1,obj._name).peephole().keep(); - - // Initial nonzero values for every field - for( int i = 0; i < len; i++ ) { - Node val = init.get( i + idx ); + Node prior = mem().keep(); + MemMergeNode input = new MemMergeNode((Node)null); + for( Field f : fs ) input.alias(f._alias,prior); + // Allocation zeroes its fields; nonzero initializers become explicit Stores. + Node[] ns = new Node[3]; + ns[0] = ctrl(); + ns[1] = input.peephole(); + ns[2] = size; + Node nnn = new NewNode(TypeMemPtr.make(obj),ns).peephole().keep(); + Node ptr = new ProjNode(nnn,0,obj._name).peephole().keep(); + Node out = new ProjNode(nnn,1,"$mem").peephole().keep(); + MemMergeNode after = new MemMergeNode(prior); + for( Field f : fs ) after.alias(f._alias,out); + mem(after.peephole()); + out.unkeep(); + nnn.unkeep(); + prior.unkeep(); + for( int i=0; i<fs.length; i++ ) { + Node val = init.get(idx+i); if( val._type != val._type.makeZero() ) { - Node mem = memAlias(fs[i]._alias); - Node st = new StoreNode(loc(),fs[i]._fname,fs[i]._alias,fs[i]._type,mem,ptr,con(obj.offset(i)),val,true).peephole(); - memAlias(fs[i]._alias,st); + Field f = fs[i]; + Node st = new StoreNode(loc(),f._fname,f._alias,f._type,mem(),ptr,con(obj.offset(i)),val,true).peephole(); + mem(new MemMergeNode(mem(),f._alias,st).peephole()); } } - return ptr.unkeep(); } @@ -1319,13 +1317,18 @@ public class Parser { return con(str); } - // 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.mem(alias ); } - private void memAlias(int alias, Node st) { _scope.mem(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.mem(); } + private void mem(Node n) { _scope.mem(n); } + + private void store(String name, int alias, Type glb, Node ptr, Node off, Node val, boolean init, Node ctrl) { + Node prior = mem().keep(); + Node st = new StoreNode(loc(),name,alias,glb,prior,ptr,off,val,init); + st.setDef(0,ctrl); + st = st.peephole(); + mem(new MemMergeNode(prior,alias,st).peephole()); + prior.unkeep(); + } /** * Parse postfix expression; this can be a field expression, an array @@ -1366,6 +1369,7 @@ public class Parser { if( tf instanceof TypeMemPtr ftmp && ftmp.isFRef() ) tf = ftmp.makeFrom(((TypeMemPtr)(TYPES.get(ftmp._obj._name)))._obj); + expr.keep(); // Field offset; fixed for structs, computed for arrays Node off = (name.equals("[]") // If field is an array body // Array index math @@ -1378,16 +1382,11 @@ public class Parser { Node val = parseAsgn().keep(); Node lift = liftExpr( val, tf, f._final, false ); - Node st = new StoreNode(loc(), name, f._alias, tf, memAlias(f._alias), expr, off.unkeep(), lift, false); - // Arrays include control, as a proxy for a safety range check. - // Structs don't need this; they only need a NPE check which is - // done via the type system. - if( base.isAry() ) st.setDef(0,ctrl()); - memAlias(f._alias, st.peephole()); + store(name,f._alias,tf,expr.unkeep(),off.unkeep(),lift,false,base.isAry() ? ctrl() : null); return val.unkeep(); // "obj.a = expr" returns the expression while updating memory } - Node load = new LoadNode(loc(),name, f._alias, tf, memAlias(f._alias), expr.keep(), off); + Node load = new LoadNode(loc(),name, f._alias, tf, mem(), expr, off); // Arrays include control, as a proxy for a safety range check // Structs don't need this; they only need a NPE check which is // done via the type system. @@ -1398,12 +1397,7 @@ public class Parser { char ch = _lexer.matchOperAssign(); if( ch!=0 ) { Node op = opAssign(ch,load,f._final,tf,name); - Node st = new StoreNode(loc(), name, f._alias, tf, memAlias(f._alias), expr.unkeep(), off, op, false); - // Arrays include control, as a proxy for a safety range check. - // Structs don't need this; they only need a NPE check which is - // done via the type system. - if( base.isAry() ) st.setDef(0,ctrl()); - memAlias(f._alias, peep(st)); + store(name,f._alias,tf,expr.unkeep(),off,op,false,base.isAry() ? ctrl() : null); load = postfix(ch) ? load.unkeep() : op; // And use the original loaded value as the result } else expr.unkill(); @@ -1484,7 +1478,7 @@ public class Parser { } // Control & memory after parsing args args.set(0,ctrl().keep()); - args.set(1,_scope.mem().merge().keep()); + args.set(1,_scope.mem().keep()); args.push(expr); // Function pointer // Unkeep them all for( Node arg : args ) @@ -1505,11 +1499,8 @@ public class Parser { call.peephole(); // Rerun peeps after CallEnd, allows early inlining // Control from CallEnd ctrl(new CProjNode(cend,0,ScopeNode.CTRL).peephole()); - // Memory from CallEnd - MemMergeNode mem = new MemMergeNode(true); - mem.addDef(null); // Alias#0 - mem.addDef(new ProjNode(cend,1,ScopeNode.MEM0).peephole()); - _scope.mem(mem); + // A call may update any alias, so its memory result is the new whole state. + _scope.mem(new ProjNode(cend,1,ScopeNode.MEM0).peephole()); // Call result return new ProjNode(cend,2,"#2").peephole(); } diff --git a/chapter22/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java b/chapter22/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java index 68b310bf..21a8ad76 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java @@ -50,7 +50,7 @@ public abstract class GlobalCodeMotion { var cons = new ArrayList<Node>(); for( Node con : start._outputs ) if( con!=null && !(con instanceof CFGNode) && - (con.isConst() || con instanceof MachNode mach && mach.outregmap()!=null) ) { + (con.isConst() || (con instanceof MachNode mach && mach.outregmap()!=null)) ) { globals.put(con,true); cons.add(con.keep()); // Preserve original inputs while rewiring users. } @@ -172,16 +172,8 @@ public abstract class GlobalCodeMotion { // Loads need their memory inputs' uses also done if( n instanceof MemOpNode ld && ld._isLoad ) - for( Node memuse : ld.mem()._outputs ) - if( late[memuse._nid]==null && - // New makes new memory, never crushes load memory - !(memuse instanceof NewNode) && - // Load-use directly defines memory - (memuse._type instanceof TypeMem || - // Load-use directly defines memory - memuse instanceof CallNode || - // Load-use indirectly defines memory - (memuse._type instanceof TypeTuple tt && tt._types[ld._alias] instanceof TypeMem)) ) + for( Node memuse : ld.antiDeps() ) + if( late[memuse._nid]==null ) continue outer; // All uses done, schedule @@ -193,9 +185,7 @@ public abstract class GlobalCodeMotion { for( Node def : n._inputs ) { if( def==null ) continue; if( late[def._nid]==null ) work.push(def); - for( Node out : def._outputs ) - if( out instanceof MemOpNode ld && ld._isLoad && late[ld._nid]==null ) - work.push(ld); + wakeLoads(def,late,work,new BitSet()); } if( n instanceof LoopNode loop ) for( Node phi : loop._outputs ) @@ -255,23 +245,32 @@ public abstract class GlobalCodeMotion { best instanceof IfNode; } + // An allocation may consume a load's memory through a partial aggregate. + private static void wakeLoads(Node def, CFGNode[] late, WorkList<Node> work, BitSet visit) { + if( visit.get(def._nid) ) return; + visit.set(def._nid); + for( Node out : def._outputs ) + if( out instanceof MemOpNode ld && ld._isLoad && late[ld._nid]==null ) work.push(ld); + if( def instanceof MemMergeNode ) + for( int i=1; i<def.nIns(); i++ ) + if( def.in(i)!=null ) wakeLoads(def.in(i),late,work,visit); + } + private static CFGNode find_anti_dep(CFGNode lca, MemOpNode load, CFGNode early, CFGNode[] late, int[] anti) { // We could skip final-field loads here. // Walk LCA->early, flagging Load's block location choices for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() ) anti[cfg._nid] = load._nid; // Walk load->mem uses, looking for Stores causing an anti-dep - for( Node mem : load.mem()._outputs ) { + for( Node mem : load.antiDeps() ) { switch( mem ) { case MemOpNode st: - if( !st._isLoad && load._alias == st._alias ) { - assert late[mem._nid] != null; - lca = anti_dep(load,late[mem._nid],lca,st,anti); - } - break; // Loads do not cause anti-deps on other loads - case CallNode call: - assert late[call._nid]!=null; - lca = anti_dep(load,late[call._nid],lca,call,anti); + assert late[st._nid]!=null; + lca = anti_dep(load,late[st._nid],lca,st,anti); + break; + case CallNode st: + assert late[st._nid]!=null; + lca = anti_dep(load,late[st._nid],lca,st,anti); break; case PhiNode phi: // Repeat anti-dep for matching Phi inputs. @@ -280,10 +279,10 @@ public abstract class GlobalCodeMotion { if( phi.in(i)==load.mem() ) lca = anti_dep(load,phi.region().cfg(i),lca,null,anti); break; - case NewNode st: break; - case ReturnNode ret: break; // Load must already be ahead of Return - case MemMergeNode ret: break; // Mem uses now on ScopeMin - case NeverNode never: break; + case NewNode st: + assert late[st._nid]!=null; + lca = anti_dep(load,late[st._nid],lca,st,anti); + break; default: throw Utils.TODO(); } } @@ -295,7 +294,7 @@ public abstract class GlobalCodeMotion { // Stores are already placed. Constrain the load at that block only. if( anti[stblk._nid]==load._nid ) { lca = stblk.domLCA(lca,null); - if( lca==stblk && st!=null && st._inputs.find(load) == -1 ) + if( lca==stblk && st!=null && !(st instanceof CallNode) && st._inputs.find(load) == -1 ) st.addDef(load); // Same-block load must precede the store. } return lca; diff --git a/chapter22/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java b/chapter22/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java index c0be96f1..ea911007 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java @@ -536,8 +536,8 @@ public class RegAlloc { if( defreg != usereg && splitBypass(bb,j,n,defreg) ) usereg = lrg(n.in(1))._reg; - // Split has same reg? Useless! Can remove it! - if( defreg == usereg ) { + // Same register, or the next instruction can use the source directly. + if( defreg == usereg || splitForward(bb,j,n,usereg) ) { n.removeSplit(); j--; continue; @@ -550,6 +550,18 @@ public class RegAlloc { } } + // A sole, adjacent use can read the source register directly. Nothing can + // clobber it in between; only the operand mask and two-address tie remain. + private boolean splitForward( CFGNode bb, int j, Node split, int reg ) { + if( split.nOuts()!=1 || j+1==bb.nOuts() ) return false; + Node use = split.out(0); + if( use!=bb.out(j+1) || use instanceof PhiNode || use instanceof CFGNode || + !(use instanceof MachNode mach) ) return false; + int i = use._inputs.find(split); + RegMask mask = mach.regmap(i); + return mask!=null && mask.test(reg) && mach.twoAddress()!=i; + } + private boolean splitBypass( CFGNode bb, int j, Node lo, int defreg ) { // Attempt to bypass split Node hi = lo.in(1); diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java new file mode 100644 index 00000000..7d4f1fb8 --- /dev/null +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java @@ -0,0 +1,240 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.codegen.CodeGen; +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); } + + public BulkMemPhiNode(BulkMemPhiNode phi) { super(phi); _aliases = phi._aliases; } + + @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,CodeGen.CODE.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 mmm.in(1)!=null && missingAlias(mmm)==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 ParmNode parm -> 0; + case ConstantNode con -> 0; + case MemMergeNode mmm -> missingAlias(mmm); + 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. + addDepForwards(use); + return switch(use) { + case ScopeNode scope -> 0; + case MemMergeNode mmm -> missingAlias(mmm); + case BulkMemPhiNode bulk -> missingAlias(bulk); + case MemOpNode mem -> unsplit(mem._alias); + case MemPhiNode phi -> unsplit(phi._alias); + default -> { + assert use instanceof ReturnNode || use instanceof CallNode || use instanceof ParmNode + : "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; + } + + // Every explicit slot requests that alias, including a partial allocation + // input whose slot still points to this bulk Phi. + private int missingAlias(MemMergeNode mmm) { + for( int alias=2; alias<mmm.nIns(); alias++ ) + if( mmm.in(alias)!=null && + mmm.alias(alias)!=mmm.in(1) && + !_aliases.get(alias) ) + 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++ ) + CodeGen.CODE.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; + CodeGen.CODE.add(bulk); + CodeGen.CODE.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,Type.BOTTOM)); + for( int i=1; i<nIns(); i++ ) + mphi.addDef(CodeGen.CODE.add(preciseInput(in(i),alias))); + return CodeGen.CODE.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/chapter22/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/LoadNode.java index e0a6d09a..0432bcbc 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -1,7 +1,7 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.Parser; -import com.seaofnodes.simple.Utils; import com.seaofnodes.simple.type.*; import java.util.BitSet; @@ -23,6 +23,25 @@ public class LoadNode extends MemOpNode { super(loc, name, alias, true, glb, mem, ptr, off); } + @Override boolean canDrop(MemOpNode other, Node dep) { + return super.canDrop(other,dep) && !clobbered(dep); + } + + // Check only immediate memory users. Writers clobber memory; Phis and + // aggregates might lead to a clobber, so stop rather than search further. + private boolean clobbered(Node dep) { + Node mem = mem(); + dep.addDepForwards(mem); + for( Node use : mem._outputs ) { + if( use==null ) continue; + dep.addDepForwards(use); + if( use instanceof StoreNode || use instanceof PhiNode || + use instanceof MemMergeNode || + use instanceof NewNode || use instanceof CallNode ) return true; + } + return false; + } + // Debugger label @Override public String label() { return "ld_"+mlabel(); } @Override @@ -30,39 +49,24 @@ public class LoadNode extends MemOpNode { @Override public Type compute() { - if( mem()._type instanceof TypeMem mem ) { - // Update declared forward ref to the actual - if( _declaredType.isFRef() && mem._t instanceof TypeMemPtr tmp && !tmp.isFRef() ) - _declaredType = tmp; - // No lifting if ptr might null-check - if( err()==null ) - return _declaredType.join(mem._t); - } - return _declaredType; + Type t = MemMergeNode.contents(mem(),_alias,this); + // Update declared forward ref to the actual. + if( _declaredType.isFRef() && t instanceof TypeMemPtr tmp && !tmp.isFRef() ) + _declaredType = tmp; + return err()==null ? _declaredType.join(t) : _declaredType; } @Override public Node idealize() { - Node ptr = ptr(); - Node mem = mem(); - - // Simple Load-after-Store on same address. - if( mem instanceof StoreNode st && - ptr == st.ptr() && off() == st.off() ) { // Must check same object - assert _name.equals(st._name); // Equiv class aliasing is perfect - return extend(st.val()); - } - - // Simple load-after-MemMerge to a known alias can bypass. Happens when inlining. - if( mem instanceof MemMergeNode mem2 ) { - setDef(1,mem2.alias(_alias)); + if( mem() instanceof MemMergeNode merge ) { + setDef(1,CodeGen.CODE.add(merge.alias(_alias))); return this; } + Node ptr = ptr(); + Node mem = mem(); - // Simple Load-after-New on same address. - if( mem instanceof ProjNode p && p.in(0) instanceof NewNode nnn && - ptr == nnn.proj(1) ) // Must check same object - return zero(nnn); // Load zero from new + Node win = find(mem,ptr,null,null); + if( win!=null ) return folded(win); // Uplift control to a prior dominating load. for( Node memuse : mem._outputs ) @@ -71,49 +75,7 @@ public class LoadNode extends MemOpNode { cfg0()!=null && cfg0().domLCA(ld.cfg0(),this) == ld.cfg0() ) // Higher control means load is legal earlier return ld; - // Load-after-Store on same address, but bypassing provably unrelated - // stores. This is a more complex superset of the above two peeps. - // "Provably unrelated" is really weak. - if( ptr instanceof ReadOnlyNode ro ) - ptr = ro.in(1); - outer: - while( true ) { - switch( mem ) { - case StoreNode st: - if( ptr == addDep(st.ptr()) && off() == st.off() ) - return extend(castRO(st.val())); // Proved equal - // Can we prove unequal? Offsets do not overlap? - if( !off()._type.join(st.off()._type).isHigh() && // Offsets overlap - !neverAlias(ptr,st.ptr()) ) // And might alias - break outer; // Cannot tell, stop trying - // Pointers cannot overlap - mem = st.mem(); // Proved never equal - break; - case PhiNode phi: - // Assume related - addDep(phi); - break outer; - case ConstantNode top: break outer; // Assume shortly dead - case ProjNode mproj: // Memory projection - switch( mproj.in(0) ) { - case NewNode nnn1: - if( ptr instanceof ProjNode pproj && pproj.in(0) == mproj.in(0) ) - return zero(nnn1); - if( !(ptr instanceof ProjNode pproj && pproj.in(0) instanceof NewNode) ) - break outer; // Cannot tell, ptr not related to New - mem = nnn1.in(nnn1.findAlias(_alias));// Bypass unrelated New - break; - case StartNode start: break outer; - case CallEndNode cend: break outer; // TODO: Bypass no-alias call - default: throw Utils.TODO(); - } - break; - case MemMergeNode merge: mem = merge.alias(_alias); break; - - default: - throw Utils.TODO(); - } - } + if( ptr instanceof ReadOnlyNode ro ) ptr=ro.in(1); // 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 @@ -122,19 +84,27 @@ 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 && + // Array access control must not be moved across the merge. + in(0)==null && // Offset can be hoisted off() instanceof ConstantNode && // Pointer can be hoisted hoistPtr(ptr,memphi) ) { - // 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 = ld(1); - Node ld2 = ld(2); - return new PhiNode(_name,_declaredType,memphi.region(),ld1,ld2); + // Returning to this memory means no change only if the pointer + // is also loop invariant. A pointer Phi may select another object. + Node stop = memphi.region() instanceof LoopNode && + !(ptr instanceof PhiNode p && p.region()==memphi.region()) ? memphi : null; + if( find(memphi.in(2),ptr,stop,new BitSet())!=null || + (!(memphi.region() instanceof LoopNode) && find(memphi.in(1),ptr,null,new BitSet())!=null) ) { + PhiNode phi = new PhiNode(_name,_declaredType,memphi.region()).keep(); + phi.setType(_type); // Backedge searches may return this value Phi. + for( int i=1; i<memphi.nIns(); i++ ) { + Node p = ptr instanceof PhiNode pp && pp.region()==memphi.region() ? pp.in(i) : ptr; + phi.addDef(load(memphi.in(i),p,stop,phi)); + } + return CodeGen.CODE.add(phi.unkeep()); } } @@ -149,9 +119,66 @@ public class LoadNode extends MemOpNode { return castRO(new ConstantNode(zero).peephole()); } - private Node ld( int idx ) { - Node mem = mem(), ptr = ptr(); - return new LoadNode(_loc,_name,_alias,_declaredType,mem.in(idx),ptr instanceof PhiNode && ptr.in(0)==mem.in(0) ? ptr.in(idx) : ptr, off()).peephole(); + // Search only; no new nodes or rewiring. Return a Store/New that folds, + // the unchanged loop memory, or a merge whose every arm folds. Null fails. + // Register dependencies so a later pointer/offset rewrite retries the query. + private Node find(Node mem, Node ptr, Node stop, BitSet visit) { + if( ptr instanceof ReadOnlyNode ro ) ptr=ro.in(1); + while( mem!=null ) { + addDep(mem); + if( mem==stop ) return mem; + switch( mem ) { + case MemMergeNode merge: mem=merge.alias(_alias); break; + case StoreNode st: + addDep(st.ptr()); + if( _alias==st._alias && ptr==st.ptr() && off()==st.off() ) return st; + if( _alias==st._alias && !neverAlias(ptr,st.ptr()) && + !addDep(off())._type.join(addDep(st.off())._type).isHigh() ) return null; + mem=st.mem(); + break; + case ProjNode proj: + if( !(proj.in(0) instanceof NewNode nnn) ) return null; + if( ptr instanceof ProjNode p && p.in(0)==nnn ) return nnn; + if( !(ptr instanceof ProjNode p && p.in(0) instanceof NewNode) ) return null; + mem=nnn.mem(); // Distinct allocations cannot overlap. + break; + case MemPhiNode phi: + addDep(phi.region()); + // Only the original loop closes a successful cycle. Another + // loop or an unfinished merge needs a separate proof. + if( visit==null || !(phi.region() instanceof RegionNode r) || r instanceof LoopNode || + r.inProgress() || phi.inProgress() || r._type!=Type.CONTROL ) return null; + if( visit.get(phi._nid) ) return phi; // Already proved every arm. + for( int i=1; i<phi.nIns(); i++ ) + if( find(phi.in(i),ptr,stop,visit)==null ) return null; + visit.set(phi._nid); + return phi; + default: return null; + } + } + return null; + } + + private Node folded(Node win) { + return win instanceof StoreNode st ? extend(castRO(st.val())) : zero((NewNode)win); + } + + // Materialize only after the profitability search succeeds. The value Phi + // stands for a load which comes back around the loop with unchanged memory. + private Node load(Node mem, Node ptr, Node stop, PhiNode value) { + Node win=find(mem,ptr,stop,new BitSet()); + if( win!=null ) { + if( win==stop ) return value; + if( win instanceof MemPhiNode mp ) { + PhiNode phi=new PhiNode(_name,_declaredType,mp.region()).keep(); + for( int i=1; i<mp.nIns(); i++ ) phi.addDef(load(mp.in(i),ptr,stop,value)); + return phi.unkeep().peephole(); + } + return folded(win).peephole(); + } + LoadNode ld=new LoadNode(_loc,_name,_alias,_declaredType,mem,ptr,off()); + ld.setDef(0,in(0)); + return ld.peephole(); } private static boolean neverAlias( Node ptr1, Node ptr2 ) { @@ -180,16 +207,6 @@ public class LoadNode extends MemOpNode { return false; } - // Profitable if we find a matching Store on this Phi arm. - private boolean profit(PhiNode phi, int idx) { - Node px = phi.in(idx); - if( px==null ) return false; - if( px._type instanceof TypeMem mem && mem._t.isHighOrConst() ) return true; - if( px instanceof StoreNode st1 && ptr()==st1.ptr() && off()==st1.off() ) return true; - addDep(px); - return false; - } - // Read-Only is a deep property, and cannot be cast-away private Node castRO(Node rez) { if( ptr()._type.isFinal() && !rez._type.isFinal() ) @@ -211,4 +228,5 @@ public class LoadNode extends MemOpNode { Node shl = new ShlNode(null,val,shf.keep()).peephole(); return new SarNode(null,shl,shf.unkeep()); } + } diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/LoopNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/LoopNode.java index 334ac686..c645968e 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/LoopNode.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/LoopNode.java @@ -57,11 +57,12 @@ public class LoopNode extends RegionNode { // Now fold control into the exit. Might have 1 valid exit, or an // XCtrl or a bunch of prior NeverNode exits. Node top = new ConstantNode(Type.TOP).peephole(); - Node memout = new MemMergeNode(false); - memout.addDef(f); // placeholder for control + MemMergeNode memout = new MemMergeNode(new ConstantNode(TypeMem.BOT).peephole()); + memout.setDef(0,f); for( Node u : _outputs ) - if( u instanceof PhiNode phi && phi._type.isa(TypeMem.BOT) ) - memout.addDef(phi); + if( u instanceof BulkMemPhiNode ) memout.setDef(1,u); + else if( u instanceof MemPhiNode phi ) memout.alias(phi._alias,phi); + memout.init(); Node ctrl = ret.ctrl(), mem = ret.mem(), expr = ret.expr(); if( ctrl!=null && ctrl._type != Type.XCONTROL ) { @@ -72,7 +73,7 @@ public class LoopNode extends RegionNode { // Nope, insert an aligned exit layer RegionNode r = new RegionNode(_loc,null,ctrl).init(); ctrl = r; r._ltree = stop._ltree; - mem = new PhiNode(r,mem ).init(); + mem = new BulkMemPhiNode(ScopeNode.MEM0,r,mem).init(); expr = new PhiNode(r,expr).init(); } // Append new Never exit diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java index 087cc18e..c1d11f88 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java @@ -5,173 +5,84 @@ import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.*; import java.util.*; -/** - * Memory Merge - a merge of many aliases into a "fat memory". All aliases - * are here, but most will be lazy - take the default fat memory. +/** A default slice and explicit alias overrides. A null default means partial + * memory: absent aliases are not covered, rather than having unknown contents. */ public class MemMergeNode extends Node { + public MemMergeNode(MemMergeNode mem) { super(mem); } - /* - * In-Progress means this is being used by the Parser to track memory - * aliases. No optimizations are allowed. When no longer "in progress" - * normal peeps work. - */ - public final boolean _inProgress; - - public MemMergeNode( boolean inProgress) { _type = TypeMem.BOT; _inProgress = inProgress; } - public MemMergeNode(MemMergeNode mem) { super(mem); _inProgress = false; } - - - // If being used by a Scope, this is "in progress" from the Parser. - // Otherwise, it's a memory merge - boolean inProgress() { return _inProgress; } + 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); + n = n==null ? in(1) : n; + assert n!=null : "Alias not covered: "+alias; + return n; + } - @Override public String label() { return "ALLMEM"; } - @Override public boolean isMem() { return true; } + public void alias(int alias, Node mem) { + assert alias > 1; + while( nIns()<=alias ) addDef(null); + setDef(alias,mem); + } - @Override - public StringBuilder _print1(StringBuilder sb, BitSet visited) { - sb.append(_inProgress ? "Merge[" : "MEM[ "); - for( int j=2; j<nIns(); j++ ) { - sb.append(j); - sb.append(":"); - Node n = in(j); - while( n instanceof ScopeNode loop ) { - sb.append("Lazy_"); - n = loop.mem().alias(j); - } - if( n==null ) sb.append("___ "); - else n._print0(sb, visited).append(" "); + // Scalar contents of one alias. New zeroes the allocated fields; + // join it with the incoming contents for all previously allocated objects. + // Phis use their cached types, so this query does not recurse around loops. + static Type contents(Node mem, int alias, Node dep) { + dep.addDep(mem); + if( mem instanceof MemMergeNode merge ) + return contents(merge.alias(alias),alias,dep); + if( mem instanceof ProjNode proj && proj.in(0) instanceof NewNode nnn ) { + assert proj._idx==1 && nnn.field(alias)!=null; + dep.addDep(nnn); + return contents(nnn.mem(),alias,dep).meet(nnn.field(alias)._type.makeZero()); } - sb.setLength(sb.length()-1); - return sb.append("]"); + // Function parameters and call results may contain arbitrary heap values. + // Their bulk memory types do not imply empty or zero-filled storage. + if( mem._type==Type.TOP || mem._type==TypeMem.TOP ) return Type.TOP; + if( mem._type instanceof TypeMem mt && mt._alias==alias ) return mt._t; + return Type.BOTTOM; } - // Make a memory merge: no longer a Scope really, tracking memory state but - // not related to the parser in any way. - public Node merge() { - // Force default memory to not be lazy - MemMergeNode merge = new MemMergeNode(false); - for( Node n : _inputs ) - merge.addDef(n); + @Override public String label() { return "MemMerge"; } + @Override public boolean isMem() { return true; } + @Override public Type compute() { return TypeMem.BOT; } + @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { + sb.append("MEM["); for( int i=1; i<nIns(); i++ ) - merge._mem(i,null); - return merge.peephole(); + if( in(i)!=null ) { + sb.append(i==1 ? "default:" : i+":"); + in(i)._print0(sb,visited).append(" "); + } + return sb.append("]"); } - - @Override public Type compute() { return TypeMem.BOT; } - @Override public Node idealize() { - if( inProgress() ) return null; - - // Fold defaults into the default boolean progress=false, allDefault=true; - for( int i=2; i<nIns(); i++ ) - if( in(1) == in(i) ) { setDef(i,null); progress=true; } - else { allDefault=false; } - - // If not merging any memory (all memory is just the default) - if( allDefault ) - return in(1); // Become default memory + for( int i=2; i<nIns(); i++ ) { + if( in(i)!=null && in(i)==in(1) ) { setDef(i,null); progress=true; } + if( in(i) instanceof MemMergeNode mem ) { + setDef(i,CodeGen.CODE.add(mem.alias(i))); + progress=true; + } + if( in(i)!=null ) allDefault=false; + } + if( allDefault && in(1)!=null ) return in(1); - // Collapse stacked all-mem if( in(1) instanceof MemMergeNode mem ) { - // Goal is to swap my default mem with mem's default mem - for( int i=2; i<mem.nIns(); i++ ) { - if( mem.in(i) != null ) { - // deeper default mem has a non-default - if( i>=nIns() || in(i)==null ) - setDefX(i,mem.in(i)); - } - } + for( int i=2; i<mem.nIns(); i++ ) + if( mem.in(i)!=null && (i>=nIns() || in(i)==null) ) + alias(i,mem.in(i)); + CodeGen.CODE.add(mem.in(1)); setDef(1,mem.in(1)); return this; } - + assert BulkMemPhiNode.checkMerge(this); return progress ? this : null; } - - - public Node in( Var v ) { return in(v._idx); } - - public Node alias( int alias ) { - return alias < nIns() && in(alias)!=null ? in(alias) : in(1); - } - - Node alias( int alias, Node st ) { - while( alias >= nIns() ) addDef(null); - return setDef(alias,st); - } - - // Read or update from memory. - // A shared implementation allows us to create lazy phis both during - // lookups and updates; the lazy phi creation is part of chapter 8. - Node _mem( int alias, Node st ) { - // Memory projections are made lazily; if one does not exist - // then it must be START.proj(1) - Node old = alias(alias); - if( old instanceof ScopeNode loop ) { - MemMergeNode loopmem = loop.mem(); - Node memdef = loopmem.alias(alias); - // Lazy phi! - old = memdef instanceof PhiNode phi && loop.ctrl()==phi.region() - // Loop already has a real Phi, use it - ? memdef - // 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. - : loopmem.alias(alias, new PhiNode(Parser.memName(alias), TypeMem.BOT,loop.ctrl(),loopmem._mem(alias,null),null).peephole() ); - alias(alias,old); - } - // Memory projections are made lazily; expand as needed - return st==null ? old : alias(alias,st); // Not lazy, so this is the answer - } - - - void _merge( MemMergeNode that, RegionNode r) { - int len = Math.max(nIns(),that.nIns()); - for( int i = 1; i < len; i++) - if( alias(i) != that.alias(i) ) { // No need for redundant Phis - // If we are in lazy phi mode we need to a lookup - // by alias as it will trigger a phi creation - Node lhs = this._mem(i,null); - Node rhs = that._mem(i,null); - alias(i, new PhiNode(Parser.memName(i), lhs._type.glb(true).meet(rhs._type.glb(true)), r, lhs, rhs).peephole()); - } - } - - // Fill in the backedge of any inserted Phis - void _endLoopMem( ScopeNode scope, MemMergeNode back, MemMergeNode exit ) { - Node exit_def = exit.alias(1); - for( int i=1; i<nIns(); i++ ) { - if( in(i) instanceof PhiNode phi && phi.region()==scope.ctrl() ) { - assert phi.in(2)==null; - phi.setDef(2,back.alias(i)==scope ? phi : back.alias(i)); // Fill backedge - } - if( exit_def == scope ) // Replace a lazy-phi on the exit path also - exit.alias(i,in(i)); - } - } - - // Now one-time do a useless-phi removal - void _useless( ) { - for( int i=2; i<nIns(); i++ ) { - if( in(i) instanceof PhiNode phi ) { - // Do an eager useless-phi removal - Node in = phi.peephole(); - CodeGen.CODE.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 - } - } - } - } - - @Override public boolean eq( Node n ) { - return this==n || !_inProgress; - } } diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/MemOpNode.java index bb84019e..3428b5a5 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/MemOpNode.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/MemOpNode.java @@ -6,6 +6,7 @@ import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeMemPtr; import java.lang.StringBuilder; import java.util.BitSet; +import java.util.ArrayList; /** * Convenience common base for Load and Store. @@ -78,6 +79,8 @@ public abstract class MemOpNode extends Node { static String mlabel(String name) { return "[]".equals(name) ? "ary" : ("#".equals(name) ? "len" : name); } String mlabel() { return mlabel(_name); } + @Override public boolean isMem() { return !_isLoad; } + public Node mem() { return in(1); } public Node ptr() { return in(2); } public Node off() { return in(3); } @@ -86,6 +89,13 @@ public abstract class MemOpNode extends Node { public StringBuilder _printMach( StringBuilder sb, BitSet visited ) { throw Utils.TODO(); } public int log_size() { return _declaredType.log_size(); } + // Extra conditions for factoring matching memory operations through a Phi. + // The caller has already checked the opcode, input shape and control. + boolean canDrop(MemOpNode other, Node dep) { + dep.addDep(ptr()); + return _alias==other._alias && _declaredType==other._declaredType && err()==null; + } + @Override public boolean eq(Node n) { MemOpNode mem = (MemOpNode)n; // Invariant @@ -106,4 +116,29 @@ public abstract class MemOpNode extends Node { return null; return Parser.error( "Might be null accessing '" + _name + "'",_loc); } + // Memory writers which must follow this read. A packaging node is not a + // write, but can hide New's input, so follow this alias through aggregates. + public ArrayList<Node> antiDeps() { + ArrayList<Node> deps = new ArrayList<>(); + antiDeps(mem(),deps,new BitSet()); + return deps; + } + + private void antiDeps(Node mem, ArrayList<Node> deps, BitSet visit) { + if( visit.get(mem._nid) ) return; + visit.set(mem._nid); + for( Node use : mem._outputs ) { + if( use instanceof MemMergeNode merge ) { + Node slice = _alias<merge.nIns() ? merge.in(_alias) : null; + if( (slice==mem) || (slice==null && merge.in(1)==mem) ) + antiDeps(merge,deps,visit); + } else if( use instanceof CallNode || + (use instanceof MemOpNode st && !st._isLoad && st._alias==_alias) || + (use instanceof NewNode nnn && nnn.field(_alias)!=null) || + (use instanceof MemPhiNode phi && phi._alias==_alias) || + (use instanceof BulkMemPhiNode phi && !phi.isSplit(_alias)) ) { + if( !deps.contains(use) ) deps.add(use); + } + } + } } diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java new file mode 100644 index 00000000..e5a29329 --- /dev/null +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java @@ -0,0 +1,44 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.codegen.CodeGen; +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,Type.BOTTOM),inputs); + assert alias > 1; + _alias = alias; + } + + public MemPhiNode(MemPhiNode phi) { super(phi); _alias = phi._alias; } + + @Override public String label() { return "MemPhi_"+_alias; } + + @Override public Type compute() { + assert BulkMemPhiNode.checkMem(this,_alias); + if( !(region() instanceof RegionNode r) ) + return region()._type==Type.XCONTROL ? TypeMem.TOP : _type; + if( r.inProgress() ) return _declaredType; + Type t = Type.TOP; + for( int i=1; i<nIns(); i++ ) + if( addDep(r.in(i))._type!=Type.XCONTROL ) + t = t.meet(MemMergeNode.contents(in(i),_alias,this)); + return TypeMem.make(_alias,t); + } + + @Override public Node idealize() { + for( int i=1; i<nIns(); i++ ) + if( in(i) instanceof MemMergeNode mem ) { + setDef(i,CodeGen.CODE.add(mem.alias(_alias))); + return this; + } + return super.idealize(); + } + + @Override public boolean eq(Node n) { return _alias==((MemPhiNode)n)._alias && super.eq(n); } + @Override int hash() { return _alias; } +} diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/NewNode.java index 27f7d634..57d351ef 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/NewNode.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/NewNode.java @@ -6,34 +6,35 @@ import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.type.*; import java.util.BitSet; -/** - * Allocation! Allocate a chunk of memory, and pre-zero it. - * The inputs include control and size, and ALL aliases being set. - * The output is large tuple, one for every alias plus the created pointer. - * New is expected to be followed by projections for every alias. +/** Allocate a zeroed object. Inputs {ctrl, $mem, size}; + * results {ptr, $mem}. + * The memory input and result cover only the aliases in the allocated struct. */ public class NewNode extends Node implements MultiNode { public final TypeMemPtr _ptr; - public final int _len; public NewNode(TypeMemPtr ptr, Node... nodes) { super(nodes); assert !ptr.nullable(); _ptr = ptr; - _len = ptr._obj._fields.length; - // Control in slot 0 - assert nodes[0]._type==Type.CONTROL || nodes[0]._type == Type.XCONTROL; - // Malloc-length in slot 1 - assert nodes[1]._type instanceof TypeInteger || nodes[1]._type==Type.NIL; - for( int i=0; i<_len; i++ ) - // Memory slices for all fields. - assert nodes[2+i]._type.isa(TypeMem.BOT); + assert nodes.length==3; + assert nodes[0]._type==Type.CONTROL || nodes[0]._type==Type.XCONTROL; + assert nodes[1]._type instanceof TypeMem; + assert nodes[2]._type instanceof TypeInteger || nodes[2]._type==Type.NIL; } - public NewNode(NewNode nnn) { super(nnn); _ptr = nnn._ptr; _len = nnn._len; } + public Node mem() { return in(1); } + public Node size() { return in(2); } + + public Field field(int alias) { + for( Field f : _ptr._obj._fields ) + if( f._alias==alias ) return f; + return null; + } + + public NewNode(NewNode nnn) { super(nnn); _ptr = nnn._ptr; } - public Node mem (int idx) { return in(idx+2); } @Override public String label() { return "new_"+(_ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._type.str() : _ptr._obj.str()); } @Override @@ -42,32 +43,10 @@ public class NewNode extends Node implements MultiNode { return sb.append(_ptr._obj.str()); } - int findAlias(int alias) { - int idx = _ptr._obj.findAlias(alias); - assert idx!= -1; // Error, caller should be calling - return idx+2; // Skip ctrl, size - } - - // 0 - ctrl - // 1 - byte size - // 2-len+2 - aliases, one per field - // len+2 - 2*len+2 - initial values, one per field - public Node size() { return in(1); } @Override public TypeTuple compute() { - Field[] fs = _ptr._obj._fields; - Type[] ts = new Type[fs.length+2]; - ts[0] = Type.CONTROL; - ts[1] = _ptr; - for( int i=0; i<fs.length; i++ ) { - Type mt = in(i+2)._type; - TypeMem mem = mt==Type.TOP ? TypeMem.TOP : (TypeMem)mt; - Type tfld = mem._t.meet(mem._t.makeZero()); - Type tfld2 = tfld.join(fs[i]._type); - ts[i+2] = TypeMem.make(fs[i]._alias,tfld2); - } - return TypeTuple.make(ts); + return TypeTuple.make(_ptr,TypeMem.BOT); } @Override @@ -105,9 +84,9 @@ public class NewNode extends Node implements MultiNode { _kills = kills; } public String op() { return "alloc"; } - public RegMask regmap(int i) { return i==1 ? _arg3Mask : null; } + public RegMask regmap(int i) { return i==2 ? _arg3Mask : null; } public RegMask outregmap() { return null; } - public RegMask outregmap(int idx) { return idx==1 ? _retMask : null; } + public RegMask outregmap(int idx) { return idx==0 ? _retMask : null; } public RegMask killmap() { return _kills; } public void asm(CodeGen code, SB sb) { sb.p("#calloc, ").p(code.reg(size())); diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter22/src/main/java/com/seaofnodes/simple/node/Node.java index be26fc2f..339ef9af 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/Node.java @@ -230,6 +230,7 @@ public abstract class Node implements Cloneable { // Error is 'use' does not exist; ok for 'use' to be null. protected boolean delUse( Node use ) { _outputs.del(_outputs.find(use)); + moveDepsToWorklist(); // User-count and anti-dependence queries can now change. return _outputs.isEmpty(); } @@ -303,6 +304,7 @@ public abstract class Node implements Cloneable { n.unlock(); int idx = n._inputs.find(this); n._inputs.set(idx,nnn); + n.moveDepsToWorklist(); // Rewiring can change a dependent query without changing type. nnn.addUse(n); CODE.addAll(n._outputs); } @@ -549,7 +551,12 @@ public abstract class Node implements Cloneable { * 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. */ - <N extends Node> N addDep( N dep ) { + <N extends Node> N addDep( N dep ) { return addDep(dep,false); } + + // Keep an explicit dependency when inspecting an immediate user's shape. + <N extends Node> N addDepForwards(N dep) { return addDep(dep,true); } + + private <N extends Node> N addDep(N dep, boolean forwards) { // Running peepholes during the big assert cannot have side effects // like adding dependencies. if( CODE._midAssert ) return dep; @@ -557,8 +564,8 @@ public abstract class Node implements Cloneable { if( obs != null ) obs.dep(this, dep); if( dep._deps==null ) dep._deps = new Ary<>(Node.class); if( dep._deps .find(this) != -1 ) return dep; // Already on list - if( dep._inputs .find(this) != -1 ) return dep; // No need for deps on immediate neighbors - if( dep._outputs.find(this) != -1 ) return dep; + if( !forwards && dep._inputs .find(this) != -1 ) return dep; // No need for deps on immediate neighbors + if( !forwards && dep._outputs.find(this) != -1 ) return dep; dep._deps.add(this); return dep; } diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/PhiNode.java index f95a452d..5d0c842f 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -24,6 +24,10 @@ public class PhiNode extends Node { addDef(sample); } + 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_"+MemOpNode.mlabel(_label); } @Override @@ -77,10 +81,9 @@ public class PhiNode extends Node { if( r.in(i)._type == Type.XCONTROL ) return null; - // Generic "pull down op" - Node progress; - if( same_op() && (progress = drop_same_op()) != null ) - return progress; + // Phi(op(a,b,...),op(c,d,...)) -> op(Phi(a,c),Phi(b,d),...). + Node down; + if( same_op() && (down=drop_same_op())!=null ) return down; // If merging Phi(N, cast(N)) - we are losing the cast JOIN effects, so just remove. if( nIns()==3 ) { @@ -114,50 +117,71 @@ public class PhiNode extends Node { // Same op on all Phi paths; all ops have only the Phi as a use. // None have a control input. private boolean same_op() { + Node op = in(1); + if( op instanceof CFGNode || op instanceof ConstantNode || op instanceof PhiNode || + op instanceof ProjNode || op instanceof NewNode || op instanceof ScopeNode || op instanceof MemMergeNode ) return false; + // A bulk Phi must split its aliases before factoring precise Stores. + if( this instanceof BulkMemPhiNode ) return false; + // Bulk splitting currently identifies parallel slices by Region/alias. + // Factoring can introduce another slice at a different memory point; + // wait until that Region's bulk partitioning has finished. + if( op instanceof MemOpNode ) + for( Node use : region()._outputs ) + if( use instanceof BulkMemPhiNode ) { + addDepForwards(use); + return false; + } + Node busy = null; for( int i=1; i<nIns(); i++ ) { - Node op = in(i); - if( in(1).getClass() != op.getClass() || op.in(0)!=null || in(1).nIns() != op.nIns() ) - return false; // Wrong class or CFG bound or mismatched inputs - if( in(1) instanceof MemOpNode mem && mem._alias != ((MemOpNode)op)._alias ) - return false; - if( op.nOuts() > 1 ) { // Too many users, but addDep in case lose users - for( Node out : op._outputs ) - if( out!=null && out!=this ) - addDep(out); - return false; + Node n = in(i); + if( addDep(region().in(i))._type==Type.XCONTROL || + op.getClass()!=n.getClass() || n.nIns()!=op.nIns() || n.in(0)!=null || !op.eq(n) ) return false; + if( n instanceof MemOpNode mem && !mem.canDrop((MemOpNode)op,this) ) return false; + // Moving a Store must remove the old effect, not duplicate it. + // Allow one shared arm: the other arms disappear into the single + // factored operation, usually reducing the total operation count. + addDepForwards(n); + if( n.nOuts()>1 ) { + for( Node use : n._outputs ) + if( use!=null && use!=this ) addDepForwards(use); + if( n instanceof StoreNode || busy!=null ) return false; + busy=n; } - for( int j=1; j<in(1).nIns(); j++ ) - if( op.in(j) instanceof ScopeNode || (op.in(j)==null ^ in(1).in(j)==null) ) - return false; // Lazy Phi input + for( int j=1; j<n.nIns(); j++ ) + if( n.in(j) instanceof ScopeNode || ((n.in(j)==null) != (op.in(j)==null)) ) return false; } return true; } private Node drop_same_op() { - assert !(in(1) instanceof CFGNode); - Node op = in(1); - Node cp = op.copy(); - cp._type = null; // Fresh type - cp.addDef(null); // No control - + Node op = in(1), cp = op.copyEmpty(); + cp._type = null; + cp.addDef(null); for( int j=1; j<op.nIns(); j++ ) { - boolean needsPhi = false; - Node x = op.in(j); // Jth input from sample #1 + Node x = op.in(j); + boolean different = false; for( int i=2; i<nIns(); i++ ) - if( in(i).in(j) != x ) - { needsPhi=true; break; } - if( needsPhi ) { - x = new PhiNode(_label,op.in(j)._type.glb(false)); - x.addDef(region()); - for( int i=1; i<nIns(); i++ ) - x.addDef(in(i).in(j)); - x = x.peephole(); + if( in(i).in(j)!=x ) different=true; + if( different ) { + Node[] ins = new Node[nIns()]; + ins[0] = region(); + Type t = Type.TOP; + for( int i=1; i<nIns(); i++ ) { + ins[i] = in(i).in(j); + t = t.meet(ins[i]._type); + } + PhiNode phi = j==1 && op instanceof MemOpNode mem + ? new MemPhiNode(_label,mem._alias,ins) + : PhiNode.make(_label,t.glb(false),ins); + x = phi.peephole(); } cp.addDef(x); } - // Test not running backwards, which can happen for e.g. And's - if( cp.compute().isa(compute()) ) - return cp; + // Factoring must not widen the result (e.g. correlated And operands). + if( cp.compute().isa(compute()) ) return cp; + for( int i=1; i<nIns(); i++ ) + for( int j=1; j<in(i).nIns(); j++ ) + if( in(i).in(j)!=null ) addDep(in(i).in(j)); cp.kill(); return null; } diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/RegionNode.java index 64747c63..84383845 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/RegionNode.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/RegionNode.java @@ -48,7 +48,7 @@ public class RegionNode extends CFGNode { if( !hasPhi() && // No Phi users, just a control user in(1) instanceof CProjNode p1 && in(2) instanceof CProjNode p2 && - addDep(p1.in(0))==addDep(p2.in(0)) && + addDepForwards(p1).in(0)==addDepForwards(p2).in(0) && p1.in(0) instanceof IfNode iff ) { // Replace with the iff.ctrl directly if( nIns()==3 ) return iff.ctrl(); diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/ReturnNode.java index 618c8afa..05b19682 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -9,15 +9,7 @@ import com.seaofnodes.simple.codegen.Encoding; 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 all preceding memory effects and the return value alive: {ctrl, $mem, value, rpc}. */ public class ReturnNode extends CFGNode { public FunNode _fun; @@ -51,7 +43,8 @@ public class ReturnNode extends CFGNode { @Override public Type compute() { if( inProgress () ) return TypeTuple.RET; // In progress - return TypeTuple.make(ctrl()._type,mem()._type,expr()._type); + // Inlining can delete the entry while callers still use this return. + return TypeTuple.make(ctrl()._type,TypeMem.BOT,expr()._type); } @Override public Node idealize() { diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/ScopeNode.java index 804d9cdd..0c9bb881 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -7,9 +7,9 @@ import java.util.*; /** * The Scope node is purely a parser helper - it tracks names to nodes with a - * stack of variables. + * stack of variable records. */ -public class ScopeNode extends MemMergeNode { +public class ScopeNode extends Node { /** * The control is a name that binds to the currently active control @@ -19,6 +19,11 @@ public class ScopeNode extends MemMergeNode { public static final String ARG0 = "arg"; public static final String MEM0 = "$mem"; + public Node in(Var v) { return in(v._idx); } + + @Override public Type compute() { return Type.BOTTOM; } + @Override public Node idealize() { return null; } + // All active/live variables in all nested scopes, all run together public final Ary<Var> _vars; @@ -37,7 +42,7 @@ public class ScopeNode extends MemMergeNode { // A new ScopeNode public ScopeNode() { - super(true); + _type = Type.BOTTOM; _vars = new Ary<>(Var .class); _lexSize= new Ary<>(Integer.class); _kinds = new Ary<>(Kind .class); @@ -73,7 +78,7 @@ public class ScopeNode extends MemMergeNode { public Node ctrl() { return in(0); } - public MemMergeNode mem() { return (MemMergeNode)in(1); } + public Node mem() { return in(lookup(MEM0)); } public Var var(int i) { return _vars.at(i); } /** @@ -87,7 +92,7 @@ public class ScopeNode extends MemMergeNode { * @return Node that was bound */ public <N extends Node> N ctrl(N n) { return setDef(0,n); } - public Node mem(Node n) { return setDef(1,n); } + public Node mem(Node n) { update(MEM0,n); return n; } public void push( Kind kind ) { assert _lexSize._len==_kinds._len; @@ -170,12 +175,6 @@ public class ScopeNode extends MemMergeNode { return true; } - // Read from memory - public Node mem( int alias ) { return mem()._mem(alias,null); } - // Write to memory - public void mem( int alias, Node st ) { mem()._mem(alias,st); } - - /** * Lookup a name in all scopes starting from most deeply nested. * @@ -211,7 +210,7 @@ public class ScopeNode extends MemMergeNode { // 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(v._idx,new PhiNode(v._name, v.type(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).peephole()); + : loop.setDef(v._idx,PhiNode.make(v._name, v.type(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).peephole()); setDef(v._idx,old); } assert !v._final || st==null; @@ -250,21 +249,9 @@ public class ScopeNode extends MemMergeNode { n.keep(); dup.addDef(ctrl()); // Control input is just copied - // Memory input is a shallow copy - MemMergeNode memdup = new MemMergeNode(true), mem = mem(); - memdup.addDef(null); - memdup.addDef(loop ? this : mem.in(1)); - for( int i=2; i<mem.nIns(); i++ ) - // For lazy phis on loops we use a sentinel - // that will trigger phi creation on update - memdup.addDef(loop ? this : mem.in(i)); - dup.addDef(memdup); - - // Copy of other inputs - for( int i=2; i<nIns(); i++ ) - // For lazy phis on loops we use a sentinel - // that will trigger phi creation on update - dup.addDef(loop && !var(i)._final ? this : in(i)); + // Memory is one binding, with the same lazy loop Phi as scalar names. + for( int i=1; i<nIns(); i++ ) + dup.addDef(loop && !_vars.at(i)._final ? this : in(i)); return dup; } @@ -278,22 +265,21 @@ public class ScopeNode extends MemMergeNode { */ public RegionNode mergeScopes(ScopeNode that, Parser.Lexer loc) { RegionNode r = ctrl(new RegionNode(loc,null,ctrl(), that.ctrl()).keep()); - mem()._merge(that.mem(),r); - this ._merge(that ,r); + _merge(that,r); that.kill(); // Kill merged scope CodeGen.CODE.add(r); return r.unkeep(); } private void _merge(ScopeNode that, RegionNode r) { - for( int i = 2; i < nIns(); i++) + 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 Var v = var(i); Node lhs = this.in(this.update(v,null)); Node rhs = that.in(that.update(v,null)); - setDef(i, new PhiNode(v._name, v.type(), r, lhs, rhs).peephole()); + setDef(i, PhiNode.make(v._name, v.type(), r, lhs, rhs).peephole()); } } @@ -318,23 +304,17 @@ public class ScopeNode extends MemMergeNode { assert ctrl instanceof LoopNode loop && loop.inProgress(); ctrl.setDef(2,back.ctrl()); - mem()._endLoopMem( this, back.mem(), exit.mem() ); - this ._endLoop ( this, back , exit ); + _endLoop(this,back,exit); back.kill(); // Loop backedge is dead // Now one-time do a useless-phi removal - mem()._useless(); - this ._useless(); - - // The exit mem's lazy default value had been the loop top, - // now it goes back to predating the loop. - exit.mem().setDef(1,mem().in(1)); + _useless(); } // Fill in the backedge of any inserted Phis void _endLoop( ScopeNode scope, Node back, Node exit ) { - for( int i=2; i<nIns(); i++ ) { - if( var(i)._final ) continue; // Final vars did not get modified in the loop - if( var(i).type().isHighOrConst() ) continue; // Cannot lift higher than a constant, so no Phi + for( int i=1; i<nIns(); i++ ) { + if( _vars.at(i)._final ) continue; // Final vars did not get modified in the loop + if( _vars.at(i).type().isHighOrConst() ) continue; // Cannot lift higher than a constant, so no Phi if( back.in(i) != scope ) { PhiNode phi = (PhiNode)in(i); assert phi.region()==scope.ctrl() && phi.in(2)==null; @@ -346,6 +326,23 @@ public class ScopeNode extends MemMergeNode { } + // Now one-time do a useless-phi removal + void _useless( ) { + for( int i=1; i<nIns(); i++ ) { + if( in(i) instanceof PhiNode phi ) { + // Do an eager useless-phi removal + Node in = phi.peephole(); + CodeGen.CODE.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. public void addGuards( Node ctrl, Node pred, boolean invert ) { diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/StartNode.java index 4bd76f50..91bf85a1 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -3,18 +3,11 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.type.*; -import java.util.Arrays; import java.util.BitSet; import java.util.HashSet; import static com.seaofnodes.simple.Utils.TODO; -/** - * 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. - */ +/** Start supplies control, whole memory, and the argument, in that order. */ public class StartNode extends LoopNode implements MultiNode { final Type _arg; @@ -45,7 +38,7 @@ public class StartNode extends LoopNode implements MultiNode { @Override public TypeTuple compute() { - return TypeTuple.make(Type.CONTROL,TypeMem.TOP,_arg); + return TypeTuple.make(Type.CONTROL,TypeMem.BOT,_arg); } @Override public Node idealize() { return null; } diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/StoreNode.java index e151911c..99adfdfc 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.*; import com.seaofnodes.simple.type.*; import java.util.BitSet; @@ -25,6 +26,10 @@ public class StoreNode extends MemOpNode { } // Debugger label + @Override boolean canDrop(MemOpNode other, Node dep) { + return super.canDrop(other,dep) && _init==((StoreNode)other)._init; + } + @Override public String label() { return "st_"+mlabel(); } @Override public boolean isMem() { return true; } @@ -38,28 +43,25 @@ public class StoreNode extends MemOpNode { @Override public Type compute() { Type val = val()._type; - TypeMem mem = (TypeMem)mem()._type; // Invariant - if( mem == TypeMem.TOP ) return TypeMem.TOP; - Type t = Type.BOTTOM; // No idea on field contents - // Same alias, lift val to the declared type and then meet into other fields - if( mem._alias == _alias ) { - // Update declared forward ref to the actual - if( _declaredType.isFRef() && val instanceof TypeMemPtr tmp && !tmp.isFRef() ) - _declaredType = tmp; - val = val.join(_declaredType); - t = val.meet(mem._t); - } + if( _declaredType.isFRef() && val instanceof TypeMemPtr tmp && !tmp.isFRef() ) + _declaredType = tmp; + Type t = val.join(_declaredType).meet(MemMergeNode.contents(mem(),_alias,this)); return TypeMem.make(_alias,t); } @Override public Node idealize() { + if( mem() instanceof MemMergeNode merge ) { + setDef(1,CodeGen.CODE.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 - off()==st.off() && // And same offset (could be "same alias" but this handles arrays to same index) + ptr()==st.ptr() && _alias==st._alias && // Must check same object + off()==st.off() && // And same offset 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. @@ -69,12 +71,6 @@ public class StoreNode extends MemOpNode { return this; } - // Simple store-after-MemMerge to a known alias can bypass. Happens when inlining. - if( mem() instanceof MemMergeNode mem ) { - setDef(1,mem.alias(_alias)); - return this; - } - // Value is automatically truncated by narrow store if( val() instanceof AndNode and && and.in(2)._type.isConstant() ) { int log = _declaredType.log_size(); diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java index 839ab50e..7f175f52 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java @@ -673,6 +673,8 @@ public class arm extends Machine { case NotNode not -> new NotARM(not); case OrNode or -> or(or); case ParmNode parm -> new ParmARM(parm); + case BulkMemPhiNode phi -> new BulkMemPhiNode(phi); + case MemPhiNode phi -> new MemPhiNode(phi); case PhiNode phi -> new PhiNode(phi); case ProjNode prj -> prj(prj); case ReadOnlyNode read -> new ReadOnlyMach(read); diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java index 8775f228..1618334f 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java @@ -345,6 +345,8 @@ public class riscv extends Machine { case NotNode not -> new NotRISC(not); case OrNode or -> or(or); case ParmNode parm -> new ParmRISC(parm); + case BulkMemPhiNode phi -> new BulkMemPhiNode(phi); + case MemPhiNode phi -> new MemPhiNode(phi); case PhiNode phi -> new PhiNode(phi); case ProjNode prj -> prj(prj); case ReadOnlyNode read-> new ReadOnlyMach(read); diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java index 6e72a540..c712eb7d 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java @@ -27,6 +27,11 @@ public class LoadX86 extends MemOpX86 { } static void enc( Encoding enc, Type decl, short dst, short ptr, short idx, int off, int scale ) { + // Allocation may put a full-width integer in an XMM register (or a + // floating value in a GPR). Move its bits using the selected register bank. + if( dst>=x86_64_v2.XMM_OFFSET && decl.log_size()==3 ) decl=TypeFloat.F64; + else if( dst<x86_64_v2.XMM_OFFSET && decl instanceof TypeFloat ) + decl = decl==TypeFloat.F32 ? TypeInteger.U32 : TypeInteger.BOT; if( decl == TypeFloat.F32) enc.add1(0xF3); if( decl == TypeFloat.F64) enc.add1(0xF2); diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java index 8a6ae188..f9dfe8f4 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java @@ -363,6 +363,8 @@ public class x86_64_v2 extends Machine { case NotNode not -> new NotX86(not); case OrNode or -> or(or); case ParmNode parm -> new ParmX86(parm); + case BulkMemPhiNode phi -> new BulkMemPhiNode(phi); + case MemPhiNode phi -> new MemPhiNode(phi); case PhiNode phi -> new PhiNode(phi); case ProjNode prj -> prj(prj); case ReadOnlyNode read-> new ReadOnlyMach(read); @@ -396,9 +398,8 @@ public class x86_64_v2 extends Machine { if( lhs instanceof LoadNode ld && ld.nOuts() == 1 && ld._declaredType.log_size() >= 3) return new AddMemX86(add, address(ld), ld.ptr(), idx, off, scale, 0, rhs); -// if(rhs instanceof LoadNode ld && ld.nOuts() == 1 && ld._declaredType.log_size() >= 3) { -// throw Utils.TODO(); // Swap load sides -// } + if( rhs instanceof LoadNode ld && ld.nOuts()==1 && ld._declaredType.log_size()>=3 ) + return new AddMemX86(add,address(ld),ld.ptr(),idx,off,scale, 0,lhs); // Attempt a full LEA-style break down. // Returns one of AddX86, AddIX86, LeaX86, or LHS diff --git a/chapter22/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter22/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index d4906a1b..d3f22a6d 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -67,7 +67,7 @@ public class SimpleGraphAdapter extends GraphAdapter<Node> { return n instanceof CFGNode ? 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( n instanceof MemMergeNode || i == 1 && (n instanceof MemOpNode || n instanceof ReturnNode || n instanceof CallNode) ) return Role.MEM; + if( n instanceof MemMergeNode || (i == 1 && (n instanceof MemOpNode || n instanceof ReturnNode || n instanceof CallNode || n instanceof NewNode)) ) return Role.MEM; Node def = n.in(i); if( def != null && isMem(def) ) return Role.MEM; return Role.DATA; diff --git a/chapter22/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter22/src/test/java/com/seaofnodes/simple/Chapter10Test.java index aa2cd941..adfdd1ae 100644 --- a/chapter22/src/test/java/com/seaofnodes/simple/Chapter10Test.java +++ b/chapter22/src/test/java/com/seaofnodes/simple/Chapter10Test.java @@ -2,6 +2,8 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.codegen.CodeGen; import org.junit.Test; +import com.seaofnodes.simple.node.*; +import java.util.BitSet; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -452,4 +454,121 @@ return 0; assertEquals("return 0;", code.print()); } + @Test public void testReadBeforeStores() { + var code = new CodeGen(""" + 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().opto(); + StopNode stop = code._stop; + assertEquals("1133",Eval2.eval(code,0)); + assertEquals("2244",Eval2.eval(code,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 code = new CodeGen(NESTED_MEMORY).parse().opto(); + StopNode stop = code._stop; + for (int arg=0; arg<7; arg++) { + assertEquals("S{x="+(5L+arg)+",y="+(7L+arg*(arg+1)-(arg>=2 ? 2 : 0))+ + ",z="+(11L+2*arg-(arg>=2 ? 2 : 0))+"}",Eval2.eval(code,arg)); + } + } + + @Test public void testMemoryAtEarlyReturns() { + var code = new CodeGen(""" + 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().opto(); + StopNode stop = code._stop; + for (int arg=0; arg<2; arg++) { + assertEquals(arg==0 ? "S{x=3,y=17,z=7}" : "S{x=11,y=5,z=13}",Eval2.eval(code,arg)); + } + } + + @Test public void testKeepLoadsAtMerge() { + var code = new CodeGen(""" + struct S { int x; }; + S !a = new S; S !b = new S; + a.x = arg; b.x = arg+1; + S !p = a; S !q = b; + if (arg<0) { p=b; q=a; } + int v; + if (arg>1) v=p.x; else v=q.x; + return v; + """).parse().opto(); + StopNode stop = code._stop; + // The one-step safety check stops at the memory aggregate. + assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet())); + assertEquals("-1",Eval2.eval(code,-1)); + assertEquals("1",Eval2.eval(code,0)); + assertEquals("3",Eval2.eval(code,3)); + } + + @Test public void testDropStores() { + var code = new CodeGen(""" + struct S { int x; }; + S !s = new S; + if (arg) s.x=arg+1; else s.x=arg+2; + return s; + """).parse().opto(); + StopNode stop = code._stop; + assertEquals(1,countMemoryNodes(stop,StoreNode.class,new BitSet())); + assertEquals("S{x=2}",Eval2.eval(code,0)); + assertEquals("S{x=4}",Eval2.eval(code,3)); + } + + @Test public void testKeepReadsBeforeWrites() { + var code = new CodeGen(""" + struct S { int x; int y; }; + S !a = new S; S !b = new S; + a.x=arg; b.x=arg+1; + S !p=a; S !q=b; + if (arg<0) { p=b; q=a; } + int v; + if (arg>1) { v=p.x; p.x=41; p.y=5; } + else { v=q.x; q.x=42; } + return v; + """).parse().opto(); + StopNode stop = code._stop; + assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet())); + assertEquals("-1",Eval2.eval(code,-1)); + assertEquals("1",Eval2.eval(code,0)); + assertEquals("3",Eval2.eval(code,3)); + } + + private static int countMemoryNodes(Node n, Class<?> kind, BitSet seen) { + if (n==null || seen.get(n._nid)) return 0; + seen.set(n._nid); + int cnt=kind.isInstance(n) ? 1 : 0; + for (Node def : n._inputs) cnt+=countMemoryNodes(def,kind,seen); + return cnt; + } } diff --git a/chapter22/src/test/java/com/seaofnodes/simple/Chapter10bTest.java b/chapter22/src/test/java/com/seaofnodes/simple/Chapter10bTest.java new file mode 100644 index 00000000..5d78ae06 --- /dev/null +++ b/chapter22/src/test/java/com/seaofnodes/simple/Chapter10bTest.java @@ -0,0 +1,60 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import org.junit.Test; +import static org.junit.Assert.*; + +public class Chapter10bTest { + @Test public void testIndependentField() { + var code = new CodeGen(""" + 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().opto(); + var stop = code._stop; + assertEquals("return 42;",stop.toString()); + assertEquals("42",Eval2.eval(code,5)); + assertNull(stop.walk(n -> n instanceof LoadNode ? n : null)); + } + + @Test public void testParallelMemoryPhis() { + var code = new CodeGen(""" + 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().opto(); + var stop = code._stop; + 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) && !(n instanceof ParmNode)); + return null; + }); + assertTrue(phis[0]>=3); + assertEquals("S{x=1,y=2,z=3}",Eval2.eval(code,4)); + } + + @Test public void testLateSliceWorklist() { + // 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 code = new CodeGen(Chapter10Test.NESTED_MEMORY,com.seaofnodes.simple.type.TypeInteger.BOT,seed).parse(); + code.opto(); + assertEquals("S{x=9,y=25,z=17}",Eval2.eval(code,4)); + } + } +} diff --git a/chapter22/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/chapter22/src/test/java/com/seaofnodes/simple/Chapter16Test.java index e7163075..84c1637a 100644 --- a/chapter22/src/test/java/com/seaofnodes/simple/Chapter16Test.java +++ b/chapter22/src/test/java/com/seaofnodes/simple/Chapter16Test.java @@ -255,4 +255,27 @@ return new Square; assertEquals("Square{side=3.0,diag=1.7320508075688772}", Eval2.eval(code, 3)); assertEquals("Square{side=4.0,diag=2.0}", Eval2.eval(code, 4)); } + static final String CONSTRUCTOR_MEMORY = """ + struct S { int x; int y; }; + struct T { int z=arg+40; }; + T !t = new T; + S !a = new S { x=11; y=7; }; + S !b = new S { x=22; y=9; }; + S !p=a; + if (arg) p=b; + int before=p.x; + S !c = new S { + x=p.x+1; + { int i=0; while (i<2) { p.y=p.y+1; i=i+1; } } + y=p.y; + }; + p.x=33; + return before*10000+c.x*100+c.y+t.z; + """; + + @Test public void testConstructorMemory() { + var code = new CodeGen(CONSTRUCTOR_MEMORY).parse().opto(); + assertEquals("111249",Eval2.eval(code,0)); + assertEquals("222352",Eval2.eval(code,1)); + } } diff --git a/chapter22/src/test/java/com/seaofnodes/simple/Chapter17Test.java b/chapter22/src/test/java/com/seaofnodes/simple/Chapter17Test.java index bdeb645c..f108b6c5 100644 --- a/chapter22/src/test/java/com/seaofnodes/simple/Chapter17Test.java +++ b/chapter22/src/test/java/com/seaofnodes/simple/Chapter17Test.java @@ -368,7 +368,7 @@ var b = arg ? new Bar { next = (arg==2) ? new Bar{x=2;}; x=1; }; return b ? b.next ? b.next.x : b.x; // parses "b ? (b.next ? b.next.x : b.x) : 0" """); code.parse().opto(); - assertEquals("return Phi(Region,.x,0);", code.print()); + assertEquals("return Phi(Region,.x,0,.x);", code.print()); assertEquals("0", Eval2.eval(code, 0)); assertEquals("1", Eval2.eval(code, 1)); assertEquals("2", Eval2.eval(code, 2)); diff --git a/chapter22/src/test/java/com/seaofnodes/simple/Chapter18Test.java b/chapter22/src/test/java/com/seaofnodes/simple/Chapter18Test.java index 7cd2831a..7d48db49 100644 --- a/chapter22/src/test/java/com/seaofnodes/simple/Chapter18Test.java +++ b/chapter22/src/test/java/com/seaofnodes/simple/Chapter18Test.java @@ -439,4 +439,70 @@ return p.coffee_count; assertEquals("1", Eval2.eval(code, 2)); } + static final String CALL_MEMORY = """ + struct S { int x; int y; }; + val bump = { S s, int d -> int old=s.x; s.x=old+d; return old; }; + S !a = new S { x=10; y=7; }; + S !b = new S { x=20; y=9; }; + S !p=a; if (arg) p=b; + int before=p.x; + int old=bump(p,3); + int after=p.x; + bump(a,5); + return before*1000000+old*10000+after*100+a.x+a.y; + """; + + @Test public void testCallMemory() { + var code = new CodeGen(CALL_MEMORY).parse().opto(); + int[] calls={0}; + code._stop.walk(n -> { if(n instanceof CallNode) calls[0]++; return null; }); + assertEquals(2,calls[0]); // Keep real calls so this checks scheduling across them. + assertEquals("10101325",Eval2.eval(code,0)); + assertEquals("20202322",Eval2.eval(code,1)); + code._stop.walk(n -> { + if(n instanceof CallNode call) { + assertEquals(5,call.nIns()); // No scheduling edges may become extra arguments. + assertTrue(call.fptr()._type instanceof TypeFunPtr); + } + return null; + }); + } + + static final String RECURSIVE_MEMORY = """ + struct S { int x; }; + val rec = { S s, int n -> + if (n==0) return s.x; + int before=s.x; + s.x=before+1; + return before+rec(s,n-1); + }; + S !s=new S { x=10; }; + int value=rec(s,arg); + return value*100+s.x; + """; + + @Test public void testRecursiveMemory() { + var code = new CodeGen(RECURSIVE_MEMORY).parse().opto(); + for(int n=0; n<7; n++) + assertEquals(Long.toString((10L*(n+1)+n*(n+1)/2)*100+10+n),Eval2.eval(code,n)); + } + + static final String INLINE_MEMORY = """ + struct S { int x; int y; }; + val make = { S s -> + S t = new S { x=s.x+1; y=3; }; + s.y=9; + return t; + }; + S !a = new S { x=arg+40; y=7; }; + S t=make(a); + return a.x*10000+a.y*100+t.x+t.y; + """; + + @Test public void testInlineAllocationMemory() { + var code = new CodeGen(INLINE_MEMORY).parse().opto(); + assertNull(code._stop.walk(n -> n instanceof CallNode ? n : null)); + assertEquals("400944",Eval2.eval(code,0)); + assertEquals("420946",Eval2.eval(code,2)); + } } diff --git a/chapter22/src/test/java/com/seaofnodes/simple/Chapter19Test.java b/chapter22/src/test/java/com/seaofnodes/simple/Chapter19Test.java index 7ae094ad..cd1f180c 100644 --- a/chapter22/src/test/java/com/seaofnodes/simple/Chapter19Test.java +++ b/chapter22/src/test/java/com/seaofnodes/simple/Chapter19Test.java @@ -47,7 +47,7 @@ return 0; public void testString() throws IOException { String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/stringHash.smp")); CodeGen code = new CodeGen(src).parse().opto().typeCheck().GCM().localSched(); - assertEquals("Stop[ return Phi(Region,._hashCode,Phi(Region,123456789,Phi(Loop,0,(.[]+((Phi_hash<<5)-Phi_hash))))); return Phi(Region,1,0,0,1); ]", code._stop.toString()); + assertEquals("Stop[ return Phi(Region,1,0,0,1); return Phi(Region,._hashCode,Phi(Region,123456789,Phi(Loop,0,(.[]+((Phi_hash<<5)-Phi_hash))))); ]", code._stop.toString()); //assertEquals("-4898613127354160978", Eval2.eval(code, 2)); } @@ -351,4 +351,72 @@ return sq(arg) + sq(3); code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("Stop[ return (mul,Parm_x(sq,int),x); return (add,#2,#2); ]", code.print()); } + + @Test public void testSelectedMemory() { + for( String cpu : new String[]{"x86_64_v2","riscv","arm"} ) + for( String src : new String[]{Chapter10Test.NESTED_MEMORY,Chapter16Test.CONSTRUCTOR_MEMORY, + Chapter18Test.CALL_MEMORY,Chapter18Test.RECURSIVE_MEMORY} ) { + var code = new CodeGen(src).parse().opto().typeCheck(); + var aliases = memoryPhis(code._stop); + code.loopTree().instSelect(cpu,"SystemV"); + assertEquals(aliases,memoryPhis(code._stop)); + code.GCM().localSched(); + code._stop.walk(n -> { + if( !(n instanceof CFGNode) && !(n instanceof ProjNode) ) + assertTrue(n.in(0) instanceof CFGNode); + if( n instanceof NewNode nn ) { + assertEquals(2,((TypeTuple)nn._type)._types.length); + assertTrue(nn.mem() instanceof MemMergeNode); + assertNull(nn.mem().in(1)); // Partial allocation input. + nn.cacheRegs(code); + MachNode mach = (MachNode)nn; + assertNull(mach.regmap(1)); + assertNotNull(mach.regmap(2)); + assertNotNull(mach.outregmap(0)); + assertNull(mach.outregmap(1)); + } + if( n instanceof CallNode call ) { + assertEquals(4,call.nIns()); // ctrl, memory, two args; direct target is embedded. + } + return null; + }); + } + } + + private static java.util.ArrayList<String> memoryPhis(Node stop) { + var phis = new java.util.ArrayList<String>(); + stop.walk(n -> { + if( n instanceof MemPhiNode phi ) phis.add("alias:"+phi._alias); + if( n instanceof BulkMemPhiNode phi ) phis.add("bulk:"+phi._aliases); + assertFalse(n instanceof PhiNode && n.isMem() && + !(n instanceof MemPhiNode) && !(n instanceof BulkMemPhiNode) && !(n instanceof ParmNode)); + return null; + }); + java.util.Collections.sort(phis); + return phis; + } + + @Test public void testFoldedReadBeforeWrites() { + var code = new CodeGen(""" + struct S { int x; }; + S !a = new S { x=11; }; S !b = new S { x=22; }; + S !p = a; if( arg ) p = b; + int before = p.x + arg; + a.x=33; b.x=44; + return before; + """).parse().opto().typeCheck().loopTree().instSelect("x86_64_v2","SystemV").GCM().localSched(); + var read = (MemOpNode)code._stop.walk(n -> + n instanceof com.seaofnodes.simple.node.cpus.x86_64_v2.AddMemX86 ? n : null); + assertNotNull(read); + int writes = 0; + for( Node use : read.antiDeps() ) + if( use instanceof MemOpNode st && st.isMem() ) { + assertSame(read.cfg0(),st.cfg0()); + assertTrue(st._inputs.find(read)>=0); + assertTrue(read.cfg0()._outputs.find(read)<st.cfg0()._outputs.find(st)); + assertNull(((MachNode)st).regmap(st._inputs.find(read))); + writes++; + } + assertEquals(1,writes); // The next Store follows this first clobber through memory. + } } diff --git a/chapter22/src/test/java/com/seaofnodes/simple/Chapter20Test.java b/chapter22/src/test/java/com/seaofnodes/simple/Chapter20Test.java index 1f4a3d27..0d46afb5 100644 --- a/chapter22/src/test/java/com/seaofnodes/simple/Chapter20Test.java +++ b/chapter22/src/test/java/com/seaofnodes/simple/Chapter20Test.java @@ -115,7 +115,7 @@ val sqrt = { int x -> }; return sqrt(arg) + sqrt(arg+2); """; - testTarget(src,"x86_64_v2", "SystemV",48,null); + testTarget(src,"x86_64_v2", "SystemV",40,null); testTarget(src,"riscv" , "SystemV",17,null); testTarget(src,"arm" , "SystemV",18,null); } @@ -162,7 +162,7 @@ for( int i=0; i<ary#-1; i++ ) ary[i+1] += ary[i]; return ary[1] * 1000 + ary[3]; // 1 * 1000 + 6 """; - testTarget(src,"x86_64_v2", "SystemV",9,"return .[];"); + testTarget(src,"x86_64_v2", "SystemV",5,"return .[];"); testTarget(src,"riscv" , "SystemV",7,"return (add,.[],(mul,.[],1000));"); testTarget(src,"arm" , "SystemV",5,"return (add,.[],(mul,.[],1000));"); } @@ -210,8 +210,8 @@ hashCode(s); for( String cpu : new String[]{"x86_64_v2","riscv","arm"} ) new CodeGen(src,com.seaofnodes.simple.type.TypeInteger.BOT,0) .driver(CodeGen.Phase.Encoding,cpu,"SystemV"); - testTarget(src,"x86_64_v2", "SystemV",0,null); - testTarget(src,"riscv" , "SystemV",0,null); + testTarget(src,"x86_64_v2", "SystemV",9,null); + testTarget(src,"riscv" , "SystemV",3,null); testTarget(src,"arm" , "SystemV",0,null); } @@ -371,8 +371,29 @@ return a; """; testTarget(src,"x86_64_v2", "SystemV",52,null); testTarget(src,"riscv" , "SystemV",44,null); - testTarget(src,"arm" , "SystemV",44,null); + testTarget(src,"arm" , "SystemV",41,null); //assertEquals("int[ 1,2,3,4,5,6,7,8,9,10,11]", Eval2.eval(code, 11)); } + + @Test public void testMemoryAllocation() { + for( String cpu : new String[]{"x86_64_v2","riscv","arm"} ) + for( String src : new String[]{Chapter10Test.NESTED_MEMORY,Chapter16Test.CONSTRUCTOR_MEMORY, + Chapter18Test.CALL_MEMORY,Chapter18Test.RECURSIVE_MEMORY, + "int[] !a = new int[3]; a[arg]=arg; return a[0]+1;"} ) { + var code = new CodeGen(src).parse().opto().typeCheck().loopTree() + .instSelect(cpu,"SystemV").GCM().localSched().regAlloc(); + com.seaofnodes.simple.codegen.RegAllocTestSupport.checkRegisters(code); + code._stop.walk(n -> { + // Whole memory, precise Phis, and memory projections need no register. + if( n._type instanceof com.seaofnodes.simple.type.TypeMem ) + assertEquals(-1,code._regAlloc.regnum(n)); + if( n instanceof com.seaofnodes.simple.node.NewNode nn ) { + assertTrue(code._regAlloc.regnum(nn.proj(0))>=0); + assertEquals(-1,code._regAlloc.regnum(nn.proj(1))); + } + return null; + }); + } + } } diff --git a/chapter22/src/test/java/com/seaofnodes/simple/Chapter21AllocTest.java b/chapter22/src/test/java/com/seaofnodes/simple/Chapter21AllocTest.java index 4fddbb4b..b81483d8 100644 --- a/chapter22/src/test/java/com/seaofnodes/simple/Chapter21AllocTest.java +++ b/chapter22/src/test/java/com/seaofnodes/simple/Chapter21AllocTest.java @@ -21,7 +21,7 @@ val sqrt = { int x -> }; return sqrt(arg) + sqrt(arg+2); """; - Chapter21Test.testCPU(src,"x86_64_v2", "Win64" ,48,null); + Chapter21Test.testCPU(src,"x86_64_v2", "Win64" ,40,null); Chapter21Test.testCPU(src,"riscv" , "SystemV",19,null); Chapter21Test.testCPU(src,"arm" , "SystemV",19,null); } @@ -49,7 +49,7 @@ return sqrt(arg) + sqrt(arg+2); String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/stringHash.smp")); Chapter21Test.testCPU(src,"x86_64_v2", "SystemV", 9,null); Chapter21Test.testCPU(src,"riscv" , "SystemV", 3,null); - Chapter21Test.testCPU(src,"arm" , "SystemV", 3,null); + Chapter21Test.testCPU(src,"arm" , "SystemV", 1,null); } } diff --git a/chapter22/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/chapter22/src/test/java/com/seaofnodes/simple/Chapter21Test.java index 28804cfd..df58f32a 100644 --- a/chapter22/src/test/java/com/seaofnodes/simple/Chapter21Test.java +++ b/chapter22/src/test/java/com/seaofnodes/simple/Chapter21Test.java @@ -231,6 +231,7 @@ public class Chapter21Test { } @Test public void testCoalescing() { com.seaofnodes.simple.codegen.RegAllocTestSupport.coalescing(); } + @Test public void testCopyForward() throws Exception { com.seaofnodes.simple.codegen.RegAllocTestSupport.copyForward(); } @Test public void testRisc64BitStore() { byte[] mem = new byte[24]; EvalRisc5 cpu = new EvalRisc5(mem,mem.length); @@ -312,7 +313,7 @@ public class Chapter21Test { String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/stringHash.smp")); testCPU(src,"x86_64_v2", "SystemV", 9,null); testCPU(src,"riscv" , "SystemV", 3,null); - testCPU(src,"arm" , "SystemV", 3,null); + testCPU(src,"arm" , "SystemV", 1,null); } @Test public void testStringExport() throws IOException { @@ -366,11 +367,11 @@ public class Chapter21Test { String sprimes = sb.p("]").toString(); // Compile, link against native C; expect the above string of primes to be printed out by C - TestC.run("sieve",sprimes, 178); + TestC.run("sieve",sprimes, 185); // Evaluate on RISC5 emulator; expect return of an array of primes in // the simulated heap. - EvalRisc5 R5 = TestRisc5.build("sieve", 100, 89, false); + EvalRisc5 R5 = TestRisc5.build("sieve", 100, 92, false); int trap = R5.step(10000); assertEquals(0,trap); // Return register A0 holds sieve(100) @@ -382,7 +383,7 @@ public class Chapter21Test { // Evaluate on ARM5 emulator; expect return of an array of primes in // the simulated heap. - EvalArm64 A5 = TestArm64.build("sieve", 100, 93, false); + EvalArm64 A5 = TestArm64.build("sieve", 100, 91, false); int trap_arm = A5.step(10000); assertEquals(0, trap_arm); int ary_arm = (int)A5.regs[arm.X0]; diff --git a/chapter22/src/test/java/com/seaofnodes/simple/Eval2.java b/chapter22/src/test/java/com/seaofnodes/simple/Eval2.java index 1e1612ba..cbe7bde0 100644 --- a/chapter22/src/test/java/com/seaofnodes/simple/Eval2.java +++ b/chapter22/src/test/java/com/seaofnodes/simple/Eval2.java @@ -329,7 +329,7 @@ public abstract class Eval2 { private static Object alloc(NewNode alloc) { TypeStruct type = alloc._ptr._obj; if( type.isAry() ) { - long sz = (Long)val(alloc.in(1)); + long sz = (Long)val(alloc.size()); long x = offToIdx(sz, type); int n = (int)x; if( n!=x || n<0 ) @@ -451,9 +451,9 @@ public abstract class Eval2 { case TypeRPC rpc -> sb.p(x.toString()); case TypeMem mem -> sb.p("$mem"); case TypeTuple tt -> { - if( tt._types.length>1 && tt._types[1] instanceof TypeMemPtr ) + if( tt._types.length>1 && tt._types[0] instanceof TypeMemPtr ) // Assume a NewNode - yield _print( tt._types[1], x, sb, visit ); + yield _print( tt._types[0], x, sb, visit ); throw Utils.TODO(); } case Type tt -> { diff --git a/chapter22/src/test/java/com/seaofnodes/simple/EvalArm64.java b/chapter22/src/test/java/com/seaofnodes/simple/EvalArm64.java index cd5a8371..0a3e9196 100644 --- a/chapter22/src/test/java/com/seaofnodes/simple/EvalArm64.java +++ b/chapter22/src/test/java/com/seaofnodes/simple/EvalArm64.java @@ -541,10 +541,13 @@ public class EvalArm64 { break; } - case 0xD2: { + case 0xD2: // MOVZ + case 0xF2: { // MOVK preserves the other halfwords // movz // get immediate - rval = (ir >> 5) & 0x7FFF; + int shift = ((ir >> 21) & 3)*16; + rval = ((ir >> 5) & 0xFFFFL) << shift; + if( opcode1==0xF2 ) rval |= regs[rdid] & ~(0xFFFFL << shift); break; } diff --git a/chapter22/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java b/chapter22/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java new file mode 100644 index 00000000..1717fbcd --- /dev/null +++ b/chapter22/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java @@ -0,0 +1,178 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegAllocTestSupport; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.cpus.arm.arm; +import com.seaofnodes.simple.node.cpus.riscv.riscv; +import com.seaofnodes.simple.type.TypeMemPtr; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.Test; +import static org.junit.Assert.*; + +// Execution regressions are separate from the fixed spill-measurement cohorts. +public class MemoryEncodingTest { + @Test public void testLoopLoadSearch() { + String[] sources = { + """ + u8[] !program=new u8[106]; u8[] !out=new u8[0]; + int sum=0; + for(int i=0; i<arg; i++) { + sum+=program#; + if(i==1) out=new u8[i+1]; + else if(i==3) out=new u8[2]; + } + return sum+out#; + """, + // One possibly aliasing arm must reject the unchanged-memory proof. + """ + struct S { int x; }; S !a=new S; S !b=new S; + a.x=10; b.x=20; S !p=a; if(arg&1) p=b; + int sum=0; + for(int i=0; i<6; i++) { + if(i==2) p.x=30; + else if(i==4) b.x=40; + sum+=a.x; + } + return sum; + """, + // Unchanged memory does not imply an unchanged loop-carried pointer. + """ + struct S { int x; }; S !a=new S; S !b=new S; + a.x=10; b.x=20; S !p=a; int sum=0; + for(int i=0; i<6; i++) { + if(i==2) a.x=30; + sum+=p.x; + p=b; if(i&1) p=a; + } + return sum; + """, + // All arms fold, but their values differ: build a value Phi. + """ + struct S { int x; }; S !a=new S; a.x=1; int sum=0; + for(int i=0; i<6; i++) { + sum+=a.x; + if(i&1) a.x=2; + else if(i==2) a.x=3; + else a.x=4; + } + return sum; + """ + }; + for( int p=0; p<sources.length; p++ ) { + CodeGen code=new CodeGen(sources[p]).parse().opto(); + if( p==0 ) assertNull(code._stop.walk(n -> + n instanceof LoadNode ld && ld.ptr() instanceof ProjNode ptr && + ptr.in(0) instanceof NewNode nn && + nn.size()._type==com.seaofnodes.simple.type.TypeInteger.constant(110) ? ld : null)); + for( int arg=0; arg<7; arg++ ) { + long expected=switch(p) { + case 0 -> 106*arg+(arg>=2 ? 2 : 0); + case 1 -> (arg&1)==0 ? 140 : 60; + case 2 -> 130; + default -> 16; + }; + assertEquals(Long.toString(expected),Eval2.eval(code,arg)); + } + } + } + + private static final String[] SOURCES = { + Chapter16Test.CONSTRUCTOR_MEMORY, + Chapter18Test.CALL_MEMORY, + Chapter18Test.RECURSIVE_MEMORY, + Chapter18Test.INLINE_MEMORY, + "int[] !a = new int[7]; a[arg]=arg+3; return a[0]+1;", + """ + int[] !a = new int[7]; + for( int i=0; i<7; i++ ) a[i]=i+arg; + for( int i=0; i<6; i++ ) a[i+1]+=a[i]; + return a[6]; + """ + }; + + private static long expected(int program, long arg) { + return switch(program) { + case 0 -> arg==0 ? 111249 : 222351+arg; + case 1 -> arg==0 ? 10101325 : 20202322; + case 2 -> (10*(arg+1)+arg*(arg+1)/2)*100+10+arg; + case 3 -> 400944+arg*10001; + case 4 -> arg==0 ? 4 : 1; + case 5 -> 21+7*arg; + default -> throw new AssertionError(); + }; + } + + @Test public void testEmulatedMemory() throws IOException { + for( String target : new String[]{"riscv","arm"} ) + for( int p=0; p<SOURCES.length; p++ ) { + CodeGen code = new CodeGen(SOURCES[p]).driver(target,"SystemV",null); + RegAllocTestSupport.checkRegisters(code); + int entry=0; + for( var n : code._start._outputs ) + if( n instanceof FunNode fun && "main".equals(fun._name) ) + entry=code._encoding._opStart[fun._nid]; + for( int arg=0; arg<7; arg++ ) { + byte[] image = new byte[1<<20]; + byte[] bits = code._encoding.bits(); + System.arraycopy(bits,0,image,0,bits.length); + long result; + if( target.equals("riscv") ) { + EvalRisc5 cpu = new EvalRisc5(image,1<<16); + cpu._pc=entry; + cpu.regs[riscv.A0]=arg; + assertEquals(target+" program "+p+" arg "+arg,0,cpu.step(100000)); + result=cpu.regs[riscv.A0]; + } else { + EvalArm64 cpu = new EvalArm64(image,1<<16); + cpu._pc=entry; + cpu.regs[arm.X0]=arg; + assertEquals(target+" program "+p+" arg "+arg,0,cpu.step(100000)); + result=cpu.regs[arm.X0]; + } + assertEquals(target+" program "+p+" arg "+arg,expected(p,arg),result); + } + } + } + + @Test public void testNeverMemory() { + // A synthetic exit must retain both precise slices of a no-exit loop. + // Stop at scheduling: the existing allocator cannot coalesce TOP on + // the synthetic scalar return arm. + for( String target : new String[]{"x86_64_v2","riscv","arm"} ) { + CodeGen code = new CodeGen(""" + struct S { int x; int y; }; + S !a=new S; + if( arg ) while( 1 ) { a.x+=arg; a.y+=a.x; } + return a.x+a.y; + """).driver(CodeGen.Phase.LoopTree); + assertNotNull(code._stop.walk(n -> { + if( n instanceof MemMergeNode mem && + mem.in(0) instanceof CProjNode exit && exit.in(0) instanceof NeverNode ) { + var fields = ((TypeMemPtr)Parser.TYPES.get("S"))._obj._fields; + assertTrue(mem.alias(fields[0]._alias) instanceof MemPhiNode); + assertTrue(mem.alias(fields[1]._alias) instanceof MemPhiNode); + return mem; + } + return null; + })); + code.instSelect(target,"SystemV").GCM().localSched(); + } + } + + @Test public void testNativeMemory() throws IOException { + Files.createDirectories(Path.of("build/objs")); + for( int p=0; p<SOURCES.length; p++ ) { + String file="build/objs/memory"+p; + StringBuilder c = new StringBuilder("extern long long memory(long long);\nint main() {\n"); + for( int arg=0; arg<7; arg++ ) + c.append("if(memory(").append(arg).append(")!=").append(expected(p,arg)).append("LL) return ").append(arg+1).append(";\n"); + c.append("return 0;\n}\n"); + Files.writeString(Path.of(file+".c"),c); + TestC.run("val memory = { int arg -> "+SOURCES[p]+" };", + TestC.CALL_CONVENTION,"",file+".c",file,"S","",-1); + } + } +} diff --git a/chapter22/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java b/chapter22/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java index e4643ee6..0c5edaa1 100644 --- a/chapter22/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java +++ b/chapter22/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java @@ -176,6 +176,31 @@ public class RegAllocTestSupport { assertFalse("A fixed definition clobbers even before coloring",uncolored.sameBlockNoClobber(copy)); } + public static void copyForward() throws Exception { + // Forwarding, fixed operand, two-address tie, intervening clobber, + // another user, and a scheduling-only edge. + for( int kind=0; kind<6; kind++ ) { + CodeGen code = graph(); + RegAlloc alloc = new RegAlloc(code); + Op def = new Op(A,null,null,false,code._start); + alloc.newLRG(def)._reg=0; + SplitNode copy = code._mach.split("test",(byte)0,null); + copy.setDef(0,code._start); copy.setDef(1,def); + alloc.newLRG(copy)._reg=1; + if( kind==3 ) new Op(null,null,A,false,code._start); + int twoAddress = kind==2 ? 1 : 0; + RegMask mask = kind==1 ? B : kind==5 ? null : new RegMask(3L); + Op use = new Op(null,mask,null,false,code._start,copy) { + @Override public int twoAddress() { return twoAddress; } + }; + if( kind==4 ) new Op(null,B,null,false,code._start,copy); + var post = RegAlloc.class.getDeclaredMethod("postColor"); + post.setAccessible(true); post.invoke(alloc); + assertSame("copy case "+kind,kind==0 ? def : copy,use.in(1)); + assertEquals(kind==0 ? 0 : 1,alloc._spills); + } + } + public static void coalescing() { // Merge, incompatible masks, interference, capacity, and adjacency remapping. for( int kind=0; kind<5; kind++ ) { diff --git a/chapter22/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/chapter22/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java index a3b87e90..3f023bb2 100644 --- a/chapter22/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java +++ b/chapter22/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java @@ -185,7 +185,7 @@ public class Evaluator { Object[] body=null; int num; if( type.isAry() ) { - long sz = (Long)val(alloc.in(1)); + long sz = (Long)val(alloc.size()); long n = offToIdx(sz, type); if( n < 0 ) throw new NegativeArraySizeException(""+n); @@ -199,21 +199,17 @@ public class Evaluator { assert elem instanceof TypeMemPtr; } // Length value - body[0] = vall(alloc.in(2+2)); + body[0] = n; } else { body = new Object[num = type._fields.length]; for( int i=0; i<num; i++ ) body[i] = switch( alloc._ptr._obj._fields[i]._type ) { case TypeInteger ti -> 0L; - case TypeFloat tf -> 0; + case TypeFloat tf -> 0D; default -> null; }; } - Object[] mems = new Object[type._fields.length+2]; - // mems[0] is control - mems[1] = new Obj(type,body); // the ref - // mems[2+...] are memory aliases - return mems; + return new Object[]{new Obj(type,body),null}; } private Object load(LoadNode load) { diff --git a/chapter23/README.md b/chapter23/README.md index 4a1118b5..502387eb 100644 --- a/chapter23/README.md +++ b/chapter23/README.md @@ -43,6 +43,12 @@ Also in this chapter we revisit our Types and make some major changes: etc; only the implementation details change. +The lazy memory model from Chapter 10b continues through the new type system. +BulkMemPhi/MemPhi discover aliases during optimization. Each New covers only +instance fields; class-wide (`_one`) fields are excluded from its partial memory. +Field offsets stay symbolic until layout, and Loads retain cyclic field types +and deep-final information while querying the contents of a precise alias. + You can also read [this chapter](https://github.com/SeaOfNodes/Simple/tree/linear-chapter23) 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-chapter22...linear-chapter23) it to the previous chapter. @@ -481,18 +487,24 @@ the fallback, including splitting an existing copy when all uses have the same loop depth. Cold-first loop splitting is left for Chapter 24; area/cost spill ranking remains for Chapter 25. +Chapter 21's adjacent-copy forwarding also carries forward: after coloring, +a sole use in the next instruction can read the source register when its +operand mask permits it and no two-address tie is broken. The table below +includes this cleanup and the lazy memory forward port. Cohort 22 now also includes the two zero-move C return +ABI checks added since the earlier audit (26 entries instead of 24). + Run `make spill-stats` in this directory. All rows below use **this chapter's compiler**, seed 123, and frozen source/target combinations from each cohort. The Windows run combines x86 SystemV/Win64 and RISC-V/ARM SystemV. Diagnostic machine graphs are checked separately and do not contribute to the counts. -| Program cohort | Compilations | Split/move count | Loop-weighted count | +| Program cohort | Compilations | Retained moves | Loop-weighted moves | |---|---:|---:|---:| -| Chapter 20 | 39 | 332 | 458 | -| Chapter 21 | 52 | 450 | 989 | -| Chapter 22 | 24 | 67 | 67 | -| Chapter 23 | 30 | 84 | 231 | -| **Total** | **145** | **933** | **1,745** | +| Chapter 20 | 39 | 323 | 442 | +| Chapter 21 | 52 | 433 | 965 | +| Chapter 22 | 26 | 67 | 67 | +| Chapter 23 | 30 | 78 | 225 | +| **Total** | **147** | **901** | **1,699** | `_spills` counts retained SplitNodes, including register moves; `_spillScaled` weights them by `8^loopDepth`. These estimate compiler-generated moves, not @@ -500,7 +512,7 @@ runtime memory traffic. The reporter prints individual compilations and sums by CPU/ABI, and still reports failure when a spill expectation or execution check fails. -With grouping disabled and everything else held fixed, this suite produces +In the earlier audit, before adjacent-copy forwarding, disabling grouping produced **the same 933 moves / 1,745 weighted moves**. The original grouping code also has those totals. This suite therefore shows no spill improvement from grouping; the reduced machine-graph regression exercises its compatibility and call rules, @@ -508,7 +520,7 @@ including a null-mask crash in the old implementation. A plausible allocator heuristic needs measurements on workloads that reach it before claiming a win. For the older cohorts alone, Chapter 22's compiler produced 838 moves / 1,496 -weighted moves; this compiler produces 849 / 1,514. That comparison includes +weighted moves; this compiler then produced 849 / 1,514. That comparison includes changes outside allocation. In particular, Chapter 20's frozen String input survives optimization here and costs 16 weighted moves; Chapter 22 eliminated it at this seed. It would be misleading to attribute that difference to grouping. diff --git a/chapter23/src/main/java/com/seaofnodes/simple/Parser.java b/chapter23/src/main/java/com/seaofnodes/simple/Parser.java index 5b3f4d1a..2dc83905 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/Parser.java @@ -138,7 +138,7 @@ public class Parser { _scope.define(ScopeNode.ARG0, TypeInteger.BOT, false, null, _lexer); ctrl(XCTRL); - _scope.mem(new MemMergeNode(false)); + _scope.mem(con(TypeMem.BOT)); // Parse the sys import _lexer = new Lexer(com.seaofnodes.simple.sys.SYS); @@ -214,7 +214,7 @@ public class Parser { // Build a multi-exit return point for all function returns RegionNode r = new RegionNode( null,null,null).init(); assert r.inProgress(); - PhiNode rmem = new PhiNode(ScopeNode.MEM0,TypeMem.BOT,r,null).init(); + PhiNode rmem = new BulkMemPhiNode(ScopeNode.MEM0,r,null).init(); PhiNode rrez = new PhiNode(ScopeNode.ARG0,Type.BOTTOM,r,null).init(); ReturnNode ret = new ReturnNode(r, rmem, rrez, rpc, fun).init(); fun.setRet(ret); @@ -227,11 +227,8 @@ public class Parser { // external calls. _scope.push(new Kind.Func()); ctrl(fun); // Scope control from function - // Private mem alias tracking per function - MemMergeNode mem = new MemMergeNode(true); - mem.addDef(null); // Alias#0 - mem.addDef(new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,con(TypeMem.BOT)).peephole()); // All aliases - _scope.mem(mem); + // Unknown callers supply whole memory; field aliases are discovered lazily. + _scope.mem(new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,con(TypeMem.BOT)).peephole()); // All args, "as-if" called externally for( int i=0; i<ids.length; i++ ) { Type t = sig.arg(i); @@ -246,7 +243,7 @@ public class Parser { // Last expression is the return except for the top-level main if( ctrl()._type==Type.CONTROL ) if( fun.sig() != _code._main ) - fun.addReturn(ctrl(), _scope.mem().merge(), last); + fun.addReturn(ctrl(), _scope.mem(), last); else { fun.setDef(1,XCTRL); // Kill default main _code.addAll(fun._outputs); @@ -627,9 +624,8 @@ public class Parser { */ private Node parseReturn() { var expr = require(parseAsgn(), ";"); - // Need default memory, since it can be lazy, need to force - // a non-lazy Phi - _fun.addReturn(ctrl(), _scope.mem().merge(), expr); + // Force the lazy whole-memory Phi before closing this exit. + _fun.addReturn(ctrl(), _scope.mem(), expr); ctrl(XCTRL); // Kill control return expr; } @@ -1380,31 +1376,31 @@ public class Parser { Field[] fs = obj._fields; if( fs==null ) throw error("Unknown struct type '" + obj._name + "'"); - int len = fs.length; - Node[] ns = new Node[2+len]; - ns[0] = ctrl(); // Control in slot 0 - // Total allocated length in bytes - ns[1] = size; - // Memory aliases for every field - for( int i = 0; i < len; i++ ) - if( !fs[i]._one ) - ns[2+i] = memAlias(fs[i]._alias); - Node nnn = new NewNode(TypeMemPtr.make(obj), ns).peephole().keep(); - for( int i = 0; i < len; i++ ) - if( !fs[i]._one ) - memAlias(fs[i]._alias, new ProjNode(nnn,i+2,memName(fs[i]._alias)).peephole()); - Node ptr = new ProjNode(nnn.unkeep(),1,obj._name).peephole().keep(); - - // Initial nonzero values for every field - for( int i = 0; i < len; i++ ) { - Node val = init.get( i + idx ); + Node prior = mem().keep(); + MemMergeNode input = new MemMergeNode((Node)null); + for( Field f : fs ) if( !f._one ) input.alias(f._alias,prior); + // Allocation zeroes its fields; nonzero initializers become explicit Stores. + Node[] ns = new Node[3]; + ns[0] = ctrl(); + ns[1] = input.peephole(); + ns[2] = size; + Node nnn = new NewNode(TypeMemPtr.make(obj),ns).peephole().keep(); + Node ptr = new ProjNode(nnn,0,obj._name).peephole().keep(); + Node out = new ProjNode(nnn,1,"$mem").peephole().keep(); + MemMergeNode after = new MemMergeNode(prior); + for( Field f : fs ) if( !f._one ) after.alias(f._alias,out); + mem(after.peephole()); + out.unkeep(); + nnn.unkeep(); + prior.unkeep(); + for( int i=0; i<fs.length; i++ ) { + Node val = init.get(idx+i); if( !fs[i]._one && val._type != val._type.makeZero() ) { - Node mem = memAlias(fs[i]._alias); - Node st = new StoreNode(loc(),fs[i]._fname,fs[i]._alias,fs[i]._t,mem,ptr,off(obj,fs[i]._fname),val,true).peephole(); - memAlias(fs[i]._alias,st); + Field f = fs[i]; + Node st = new StoreNode(loc(),f._fname,f._alias,f._t,mem(),ptr,off(obj,f._fname),val,true).peephole(); + mem(new MemMergeNode(mem(),f._alias,st).peephole()); } } - return ptr.unkeep(); } @@ -1430,13 +1426,18 @@ public class Parser { return con(str); } - // 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.mem(alias ); } - private void memAlias(int alias, Node st) { _scope.mem(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.mem(); } + private void mem(Node n) { _scope.mem(n); } + + private void store(String name, int alias, Type glb, Node ptr, Node off, Node val, boolean init, Node ctrl) { + Node prior = mem().keep(); + Node st = new StoreNode(loc(),name,alias,glb,prior,ptr,off,val,init); + st.setDef(0,ctrl); + st = st.peephole(); + mem(new MemMergeNode(prior,alias,st).peephole()); + prior.unkeep(); + } /** * Parse postfix expression; this can be a field expression, an array @@ -1494,6 +1495,7 @@ public class Parser { if( tf instanceof TypeMemPtr ftmp && ftmp.isFRef() ) tf = ftmp.makeFrom(((TypeMemPtr)(TYPES.get(ftmp._obj._name)))._obj); + expr.keep(); // Field offset; fixed for structs, computed for arrays Node off = (name.equals("[]") // If field is an array body // Array index math @@ -1506,21 +1508,14 @@ public class Parser { Node val = parseAsgn().keep(); Node lift = liftExpr( val, tf, f._final, false ); - Node st = new StoreNode(loc(), name, f._alias, tf, memAlias(f._alias), expr, off.unkeep(), lift, false); - // Arrays include control, as a proxy for a safety range check. - // Structs don't need this; they only need a NPE check which is - // done via the type system. - if( base.isAry() ) st.setDef(0,ctrl()); - memAlias(f._alias, st.peephole()); + store(name,f._alias,tf,expr.unkeep(),off.unkeep(),lift,false,base.isAry() ? ctrl() : null); return val.unkeep(); // "obj.a = expr" returns the expression while updating memory } - // Keep expr for possible store update - expr.keep(); // Load field Node load = f._one && INITS.containsKey( base._name ) ? INITS.get(base._name).in(base.find(name)) - : new LoadNode(loc(),name, f._alias, tf, memAlias(f._alias), expr, off); + : new LoadNode(loc(),name, f._alias, tf, mem(), expr, off); // Arrays include control, as a proxy for a safety range check // Structs don't need this; they only need a NPE check which is // done via the type system. @@ -1530,13 +1525,8 @@ public class Parser { // Check for assign-update, "ptr.fld += expr" or "ary[idx]++" char ch = _lexer.matchOperAssign(); if( ch!=0 ) { - Node op = opAssign(ch,load, tf ); - Node st = new StoreNode(loc(), name, f._alias, tf, memAlias(f._alias), expr.unkeep(), off.unkeep(), op, false); - // Arrays include control, as a proxy for a safety range check. - // Structs don't need this; they only need a NPE check which is - // done via the type system. - if( base.isAry() ) st.setDef(0,ctrl()); - memAlias(f._alias, peep(st)); + Node op = opAssign(ch,load,tf); + store(name,f._alias,tf,expr.unkeep(),off.unkeep(),op,false,base.isAry() ? ctrl() : null); load = postfix(ch) ? load.unkeep() : op; // And use the original loaded value as the result return load; @@ -1647,7 +1637,7 @@ public class Parser { } // Control & memory after parsing args args.set(0,ctrl().keep()); - args.set(1,_scope.mem().merge().keep()); + args.set(1,_scope.mem().keep()); args.push(fcn); // Function pointer // Unkeep them all for( Node arg : args ) @@ -1668,11 +1658,8 @@ public class Parser { call.peephole(); // Rerun peeps after CallEnd, allows early inlining // Control from CallEnd ctrl(new CProjNode(cend,0,ScopeNode.CTRL).peephole()); - // Memory from CallEnd - MemMergeNode mem = new MemMergeNode(true); - mem.addDef(null); // Alias#0 - mem.addDef(new ProjNode(cend,1,ScopeNode.MEM0).peephole()); - _scope.mem(mem); + // A call may update any alias, so its memory result is the new whole state. + _scope.mem(new ProjNode(cend,1,ScopeNode.MEM0).peephole()); // Call result return new ProjNode(cend,2,"#2").peephole(); } diff --git a/chapter23/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java b/chapter23/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java index b8a8d2b3..1d802cae 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java @@ -50,7 +50,7 @@ public abstract class GlobalCodeMotion { var cons = new ArrayList<Node>(); for( Node con : start._outputs ) if( con!=null && !(con instanceof CFGNode) && - (con.isConst() || con instanceof MachNode mach && mach.outregmap()!=null) ) { + (con.isConst() || (con instanceof MachNode mach && mach.outregmap()!=null)) ) { globals.put(con,true); cons.add(con.keep()); // Preserve original inputs while rewiring users. } @@ -172,16 +172,8 @@ public abstract class GlobalCodeMotion { // Loads need their memory inputs' uses also done if( n instanceof MemOpNode ld && ld._isLoad ) - for( Node memuse : ld.mem()._outputs ) - if( late[memuse._nid]==null && - // New makes new memory, never crushes load memory - !(memuse instanceof NewNode) && - // Load-use directly defines memory - (memuse._type instanceof TypeMem || - // Load-use directly defines memory - memuse instanceof CallNode || - // Load-use indirectly defines memory - (memuse._type instanceof TypeTuple tt && tt._types[ld._alias] instanceof TypeMem)) ) + for( Node memuse : ld.antiDeps() ) + if( late[memuse._nid]==null ) continue outer; // All uses done, schedule @@ -193,9 +185,7 @@ public abstract class GlobalCodeMotion { for( Node def : n._inputs ) { if( def==null ) continue; if( late[def._nid]==null ) work.push(def); - for( Node out : def._outputs ) - if( out instanceof MemOpNode ld && ld._isLoad && late[ld._nid]==null ) - work.push(ld); + wakeLoads(def,late,work,new BitSet()); } if( n instanceof LoopNode loop ) for( Node phi : loop._outputs ) @@ -255,23 +245,32 @@ public abstract class GlobalCodeMotion { best instanceof IfNode; } + // An allocation may consume a load's memory through a partial aggregate. + private static void wakeLoads(Node def, CFGNode[] late, WorkList<Node> work, BitSet visit) { + if( visit.get(def._nid) ) return; + visit.set(def._nid); + for( Node out : def._outputs ) + if( out instanceof MemOpNode ld && ld._isLoad && late[ld._nid]==null ) work.push(ld); + if( def instanceof MemMergeNode ) + for( int i=1; i<def.nIns(); i++ ) + if( def.in(i)!=null ) wakeLoads(def.in(i),late,work,visit); + } + private static CFGNode find_anti_dep(CFGNode lca, MemOpNode load, CFGNode early, CFGNode[] late, int[] anti) { // We could skip final-field loads here. // Walk LCA->early, flagging Load's block location choices for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() ) anti[cfg._nid] = load._nid; // Walk load->mem uses, looking for Stores causing an anti-dep - for( Node mem : load.mem()._outputs ) { + for( Node mem : load.antiDeps() ) { switch( mem ) { case MemOpNode st: - if( !st._isLoad && load._alias == st._alias ) { - assert late[mem._nid] != null; - lca = anti_dep(load,late[mem._nid],lca,st,anti); - } - break; // Loads do not cause anti-deps on other loads - case CallNode call: - assert late[call._nid]!=null; - lca = anti_dep(load,late[call._nid],lca,call,anti); + assert late[st._nid]!=null; + lca = anti_dep(load,late[st._nid],lca,st,anti); + break; + case CallNode st: + assert late[st._nid]!=null; + lca = anti_dep(load,late[st._nid],lca,st,anti); break; case PhiNode phi: // Repeat anti-dep for matching Phi inputs. @@ -280,10 +279,10 @@ public abstract class GlobalCodeMotion { if( phi.in(i)==load.mem() ) lca = anti_dep(load,phi.region().cfg(i),lca,null,anti); break; - case NewNode st: break; - case ReturnNode ret: break; // Load must already be ahead of Return - case MemMergeNode ret: break; // Mem uses now on ScopeMin - case NeverNode never: break; + case NewNode st: + assert late[st._nid]!=null; + lca = anti_dep(load,late[st._nid],lca,st,anti); + break; default: throw Utils.TODO(); } } @@ -295,7 +294,7 @@ public abstract class GlobalCodeMotion { // Stores are already placed. Constrain the load at that block only. if( anti[stblk._nid]==load._nid ) { lca = stblk.domLCA(lca,null); - if( lca==stblk && st!=null && st._inputs.find(load) == -1 ) + if( lca==stblk && st!=null && !(st instanceof CallNode) && st._inputs.find(load) == -1 ) st.addDef(load); // Same-block load must precede the store. } return lca; diff --git a/chapter23/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java b/chapter23/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java index 11c9409d..793732b4 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java @@ -591,8 +591,8 @@ public class RegAlloc { if( defreg != usereg && splitBypass(bb,j,n,defreg) ) usereg = lrg(n.in(1))._reg; - // Split has same reg? Useless! Can remove it! - if( defreg == usereg ) { + // Same register, or the next instruction can use the source directly. + if( defreg == usereg || splitForward(bb,j,n,usereg) ) { n.removeSplit(); j--; continue; @@ -605,6 +605,18 @@ public class RegAlloc { } } + // A sole, adjacent use can read the source register directly. Nothing can + // clobber it in between; only the operand mask and two-address tie remain. + private boolean splitForward( CFGNode bb, int j, Node split, int reg ) { + if( split.nOuts()!=1 || j+1==bb.nOuts() ) return false; + Node use = split.out(0); + if( use!=bb.out(j+1) || use instanceof PhiNode || use instanceof CFGNode || + !(use instanceof MachNode mach) ) return false; + int i = use._inputs.find(split); + RegMask mask = mach.regmap(i); + return mask!=null && mask.test(reg) && mach.twoAddress()!=i; + } + private boolean splitBypass( CFGNode bb, int j, Node lo, int defreg ) { // Attempt to bypass split Node hi = lo.in(1); diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java new file mode 100644 index 00000000..7d4f1fb8 --- /dev/null +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java @@ -0,0 +1,240 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.codegen.CodeGen; +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); } + + public BulkMemPhiNode(BulkMemPhiNode phi) { super(phi); _aliases = phi._aliases; } + + @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,CodeGen.CODE.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 mmm.in(1)!=null && missingAlias(mmm)==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 ParmNode parm -> 0; + case ConstantNode con -> 0; + case MemMergeNode mmm -> missingAlias(mmm); + 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. + addDepForwards(use); + return switch(use) { + case ScopeNode scope -> 0; + case MemMergeNode mmm -> missingAlias(mmm); + case BulkMemPhiNode bulk -> missingAlias(bulk); + case MemOpNode mem -> unsplit(mem._alias); + case MemPhiNode phi -> unsplit(phi._alias); + default -> { + assert use instanceof ReturnNode || use instanceof CallNode || use instanceof ParmNode + : "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; + } + + // Every explicit slot requests that alias, including a partial allocation + // input whose slot still points to this bulk Phi. + private int missingAlias(MemMergeNode mmm) { + for( int alias=2; alias<mmm.nIns(); alias++ ) + if( mmm.in(alias)!=null && + mmm.alias(alias)!=mmm.in(1) && + !_aliases.get(alias) ) + 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++ ) + CodeGen.CODE.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; + CodeGen.CODE.add(bulk); + CodeGen.CODE.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,Type.BOTTOM)); + for( int i=1; i<nIns(); i++ ) + mphi.addDef(CodeGen.CODE.add(preciseInput(in(i),alias))); + return CodeGen.CODE.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/chapter23/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/LoadNode.java index e752c9ce..8b526be2 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -1,7 +1,7 @@ package com.seaofnodes.simple.node; -import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.type.*; import com.seaofnodes.simple.util.Utils; import java.util.BitSet; @@ -24,6 +24,25 @@ public class LoadNode extends MemOpNode { super(loc, name, alias, true, glb, mem, ptr, off); } + @Override boolean canDrop(MemOpNode other, Node dep) { + return super.canDrop(other,dep) && !clobbered(dep); + } + + // Check only immediate memory users. Writers clobber memory; Phis and + // aggregates might lead to a clobber, so stop rather than search further. + private boolean clobbered(Node dep) { + Node mem = mem(); + dep.addDepForwards(mem); + for( Node use : mem._outputs ) { + if( use==null ) continue; + dep.addDepForwards(use); + if( use instanceof StoreNode || use instanceof PhiNode || + use instanceof MemMergeNode || + use instanceof NewNode || use instanceof CallNode ) return true; + } + return false; + } + // Debugger label @Override public String label() { return "ld_"+mlabel(); } @Override @@ -42,7 +61,7 @@ public class LoadNode extends MemOpNode { t = ary.elem(); // TODO: if offset is known, can peek the constant } // Lift from declared type and memory input - t = t.join(_declaredType).join(mem._t); + t = t.join(_declaredType).join(MemMergeNode.contents(mem(),_alias,this)); if( _declaredType.isFinal() ) t = t.makeRO(); // Deep final applied return t; @@ -50,35 +69,19 @@ public class LoadNode extends MemOpNode { @Override public Node idealize() { - Node ptr = ptr(); - Node mem = mem(); - - if( mem instanceof CastNode cast ) { + if( mem() instanceof CastNode cast ) { setDef(1,cast.in(1)); return this; } - - // Simple Load-after-Store on same address. - if( mem instanceof StoreNode st && - ptr == st.ptr() && off() == st.off() ) { // Must check same object - assert _name.equals(st._name); // Equiv class aliasing is perfect - return extend(st.val()); - } - - // Simple load-after-MemMerge to a known alias can bypass. Happens when inlining. - if( mem instanceof MemMergeNode mem2 ) { - Node memA = mem2.alias(_alias); - for( Node ld : memA._outputs ) - if( ld instanceof LoadNode ) - CodeGen.CODE.add(ld); - setDef(1,memA); + if( mem() instanceof MemMergeNode merge ) { + setDef(1,CodeGen.CODE.add(merge.alias(_alias))); return this; } + Node ptr = ptr(); + Node mem = mem(); - // Simple Load-after-New on same address. - if( mem instanceof ProjNode p && p.in(0) instanceof NewNode nnn && - ptr == nnn.proj(1) ) // Must check same object - return zero(nnn); // Load zero from new + Node win = find(mem,ptr,null,null); + if( win!=null ) return folded(win); // Uplift control to a prior dominating load. for( Node memuse : mem._outputs ) @@ -87,50 +90,7 @@ public class LoadNode extends MemOpNode { cfg0()!=null && cfg0().domLCA(ld.cfg0(),this) == ld.cfg0() ) // Higher control means load is legal earlier return ld; - // Load-after-Store on same address, but bypassing provably unrelated - // stores. This is a more complex superset of the above two peeps. - // "Provably unrelated" is really weak. - if( ptr instanceof ReadOnlyNode ro ) - ptr = ro.in(1); - outer: - while( true ) { - switch( mem ) { - case StoreNode st: - if( ptr == addDep(st.ptr()) && off() == st.off() ) - return extend(castRO(st.val())); // Proved equal - // Can we prove unequal? Offsets do not overlap? - if( !off()._type.join(st.off()._type).isHigh() && // Offsets overlap - !neverAlias(ptr,st.ptr()) ) // And might alias - break outer; // Cannot tell, stop trying - // Pointers cannot overlap - mem = st.mem(); // Proved never equal - break; - case PhiNode phi: - // Assume related - addDep(phi); - break outer; - case ConstantNode top: break outer; // Assume shortly dead - case ProjNode mproj: // Memory projection - switch( mproj.in(0) ) { - case NewNode nnn1: - if( ptr instanceof ProjNode pproj && pproj.in(0) == mproj.in(0) ) - return zero(nnn1); - if( !(ptr instanceof ProjNode pproj && pproj.in(0) instanceof NewNode) ) - break outer; // Cannot tell, ptr not related to New - mem = nnn1.in(nnn1.findAlias(_alias));// Bypass unrelated New - break; - case StartNode start: break outer; - case CallEndNode cend: break outer; // TODO: Bypass no-alias call - default: throw Utils.TODO(); - } - break; - case CastNode cast: mem = cast.in(1); break; - case MemMergeNode merge: mem = merge.alias(_alias); break; - - default: - throw Utils.TODO(); - } - } + if( ptr instanceof ReadOnlyNode ro ) ptr=ro.in(1); // 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 @@ -139,19 +99,27 @@ 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 && + // Array access control must not be moved across the merge. + in(0)==null && // Offset can be hoisted off() instanceof ConstantNode && // Pointer can be hoisted hoistPtr(ptr,memphi) ) { - // 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 = ld(1); - Node ld2 = ld(2); - return new PhiNode(_name,_declaredType,memphi.region(),ld1,ld2); + // Returning to this memory means no change only if the pointer + // is also loop invariant. A pointer Phi may select another object. + Node stop = memphi.region() instanceof LoopNode && + !(ptr instanceof PhiNode p && p.region()==memphi.region()) ? memphi : null; + if( find(memphi.in(2),ptr,stop,new BitSet())!=null || + (!(memphi.region() instanceof LoopNode) && find(memphi.in(1),ptr,null,new BitSet())!=null) ) { + PhiNode phi = new PhiNode(_name,_declaredType,memphi.region()).keep(); + phi.setType(_type); // Backedge searches may return this value Phi. + for( int i=1; i<memphi.nIns(); i++ ) { + Node p = ptr instanceof PhiNode pp && pp.region()==memphi.region() ? pp.in(i) : ptr; + phi.addDef(load(memphi.in(i),p,stop,phi)); + } + return CodeGen.CODE.add(phi.unkeep()); } } @@ -166,9 +134,67 @@ public class LoadNode extends MemOpNode { return castRO(new ConstantNode(zero).peephole()); } - private Node ld( int idx ) { - Node mem = mem(), ptr = ptr(); - return new LoadNode(_loc,_name,_alias,_declaredType,mem.in(idx),ptr instanceof PhiNode && ptr.in(0)==mem.in(0) ? ptr.in(idx) : ptr, off()).peephole(); + // Search only; no new nodes or rewiring. Return a Store/New that folds, + // the unchanged loop memory, or a merge whose every arm folds. Null fails. + // Register dependencies so a later pointer/offset rewrite retries the query. + private Node find(Node mem, Node ptr, Node stop, BitSet visit) { + if( ptr instanceof ReadOnlyNode ro ) ptr=ro.in(1); + while( mem!=null ) { + addDep(mem); + if( mem==stop ) return mem; + switch( mem ) { + case MemMergeNode merge: mem=merge.alias(_alias); break; + case CastNode cast: mem=cast.in(1); break; + case StoreNode st: + addDep(st.ptr()); + if( _alias==st._alias && ptr==st.ptr() && off()==st.off() ) return st; + if( _alias==st._alias && !neverAlias(ptr,st.ptr()) && + !addDep(off())._type.join(addDep(st.off())._type).isHigh() ) return null; + mem=st.mem(); + break; + case ProjNode proj: + if( !(proj.in(0) instanceof NewNode nnn) ) return null; + if( ptr instanceof ProjNode p && p.in(0)==nnn ) return nnn; + if( !(ptr instanceof ProjNode p && p.in(0) instanceof NewNode) ) return null; + mem=nnn.mem(); // Distinct allocations cannot overlap. + break; + case MemPhiNode phi: + addDep(phi.region()); + // Only the original loop closes a successful cycle. Another + // loop or an unfinished merge needs a separate proof. + if( visit==null || !(phi.region() instanceof RegionNode r) || r instanceof LoopNode || + r.inProgress() || phi.inProgress() || r._type!=Type.CONTROL ) return null; + if( visit.get(phi._nid) ) return phi; // Already proved every arm. + for( int i=1; i<phi.nIns(); i++ ) + if( find(phi.in(i),ptr,stop,visit)==null ) return null; + visit.set(phi._nid); + return phi; + default: return null; + } + } + return null; + } + + private Node folded(Node win) { + return win instanceof StoreNode st ? extend(castRO(st.val())) : zero((NewNode)win); + } + + // Materialize only after the profitability search succeeds. The value Phi + // stands for a load which comes back around the loop with unchanged memory. + private Node load(Node mem, Node ptr, Node stop, PhiNode value) { + Node win=find(mem,ptr,stop,new BitSet()); + if( win!=null ) { + if( win==stop ) return value; + if( win instanceof MemPhiNode mp ) { + PhiNode phi=new PhiNode(_name,_declaredType,mp.region()).keep(); + for( int i=1; i<mp.nIns(); i++ ) phi.addDef(load(mp.in(i),ptr,stop,value)); + return phi.unkeep().peephole(); + } + return folded(win).peephole(); + } + LoadNode ld=new LoadNode(_loc,_name,_alias,_declaredType,mem,ptr,off()); + ld.setDef(0,in(0)); + return ld.peephole(); } private static boolean neverAlias( Node ptr1, Node ptr2 ) { @@ -197,16 +223,6 @@ public class LoadNode extends MemOpNode { return false; } - // Profitable if we find a matching Store on this Phi arm. - private boolean profit(PhiNode phi, int idx) { - Node px = phi.in(idx); - if( px==null ) return false; - if( px._type instanceof TypeMem mem && mem._t.isHighOrConst() ) return true; - if( px instanceof StoreNode st1 && ptr()==addDep(st1.ptr() )&& off()==st1.off() ) return true; - addDep(px); - return false; - } - // Read-Only is a deep property, and cannot be cast-away private Node castRO(Node rez) { if( ptr()._type.isFinal() && !rez._type.isFinal() ) @@ -228,4 +244,5 @@ public class LoadNode extends MemOpNode { Node shl = new ShlNode(null,val,shf.keep()).peephole(); return new SarNode(null,shl,shf.unkeep()); } + } diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/LoopNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/LoopNode.java index 6c69a56e..db52c156 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/LoopNode.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/LoopNode.java @@ -57,11 +57,12 @@ public class LoopNode extends RegionNode { // Now fold control into the exit. Might have 1 valid exit, or an // XCtrl or a bunch of prior NeverNode exits. Node top = new ConstantNode(Type.TOP).peephole(); - Node memout = new MemMergeNode(false); - memout.addDef(f); // placeholder for control + MemMergeNode memout = new MemMergeNode(new ConstantNode(TypeMem.BOT).peephole()); + memout.setDef(0,f); for( Node u : _outputs ) - if( u instanceof PhiNode phi && phi._type.isa(TypeMem.BOT) ) - memout.addDef(phi); + if( u instanceof BulkMemPhiNode ) memout.setDef(1,u); + else if( u instanceof MemPhiNode phi ) memout.alias(phi._alias,phi); + memout.init(); Node ctrl = ret.ctrl(), mem = ret.mem(), expr = ret.expr(); if( ctrl!=null && ctrl._type != Type.XCONTROL ) { @@ -72,7 +73,7 @@ public class LoopNode extends RegionNode { // Nope, insert an aligned exit layer RegionNode r = new RegionNode(_loc,null,ctrl).init(); ctrl = r; r._ltree = stop._ltree; - mem = new PhiNode(r,mem ).init(); + mem = new BulkMemPhiNode(ScopeNode.MEM0,r,mem).init(); expr = new PhiNode(r,expr).init(); } // Append new Never exit diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java index 20aeb454..e56eeb06 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java @@ -5,176 +5,86 @@ import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.*; import java.util.*; -/** - * Memory Merge - a merge of many aliases into a "fat memory". All aliases - * are here, but most will be lazy - take the default fat memory. +/** A default slice and explicit alias overrides. A null default means partial + * memory: absent aliases are not covered, rather than having unknown contents. */ public class MemMergeNode extends Node { + public MemMergeNode(MemMergeNode mem) { super(mem); } - /* - * In-Progress means this is being used by the Parser to track memory - * aliases. No optimizations are allowed. When no longer "in progress" - * normal peeps work. - */ - public final boolean _inProgress; - - public MemMergeNode( boolean inProgress) { _type = TypeMem.BOT; _inProgress = inProgress; } - public MemMergeNode(MemMergeNode mem) { super(mem); _inProgress = false; } - - - // If being used by a Scope, this is "in progress" from the Parser. - // Otherwise, it's a memory merge - boolean inProgress() { return _inProgress; } + 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); + n = n==null ? in(1) : n; + assert n!=null : "Alias not covered: "+alias; + return n; + } - @Override public String label() { return "ALLMEM"; } - @Override public boolean isMem() { return true; } + public void alias(int alias, Node mem) { + assert alias > 1; + while( nIns()<=alias ) addDef(null); + setDef(alias,mem); + } - @Override - public StringBuilder _print1(StringBuilder sb, BitSet visited) { - sb.append(_inProgress ? "Merge[" : "MEM[ "); - for( int j=2; j<nIns(); j++ ) { - sb.append(j); - sb.append(":"); - Node n = in(j); - while( n instanceof ScopeNode loop ) { - sb.append("Lazy_"); - n = loop.mem().alias(j); - } - if( n==null ) sb.append("___ "); - else n._print0(sb, visited).append(" "); + // Scalar contents of one alias. New zeroes the allocated fields; + // join it with the incoming contents for all previously allocated objects. + // Phis use their cached types, so this query does not recurse around loops. + static Type contents(Node mem, int alias, Node dep) { + dep.addDep(mem); + if( mem instanceof CastNode cast ) return contents(cast.in(1),alias,dep); + if( mem instanceof MemMergeNode merge ) + return contents(merge.alias(alias),alias,dep); + if( mem instanceof ProjNode proj && proj.in(0) instanceof NewNode nnn ) { + assert proj._idx==1 && nnn.field(alias)!=null; + dep.addDep(nnn); + return contents(nnn.mem(),alias,dep).meet(nnn.field(alias)._t.makeZero()); } - sb.setLength(sb.length()-1); - return sb.append("]"); + // Function parameters and call results may contain arbitrary heap values. + // Their bulk memory types do not imply empty or zero-filled storage. + if( mem._type==Type.TOP || mem._type==TypeMem.TOP ) return Type.TOP; + if( mem._type instanceof TypeMem mt && mt._alias==alias ) return mt._t; + return Type.BOTTOM; } - // Make a memory merge: no longer a Scope really, tracking memory state but - // not related to the parser in any way. - public Node merge() { - // Force default memory to not be lazy - MemMergeNode merge = new MemMergeNode(false); - for( Node n : _inputs ) - merge.addDef(n); + @Override public String label() { return "MemMerge"; } + @Override public boolean isMem() { return true; } + @Override public Type compute() { return TypeMem.BOT; } + @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { + sb.append("MEM["); for( int i=1; i<nIns(); i++ ) - merge._mem(i,null); - return merge.peephole(); + if( in(i)!=null ) { + sb.append(i==1 ? "default:" : i+":"); + in(i)._print0(sb,visited).append(" "); + } + return sb.append("]"); } - - @Override public Type compute() { return TypeMem.BOT; } - @Override public Node idealize() { - if( inProgress() ) return null; - - // Fold defaults into the default boolean progress=false, allDefault=true; for( int i=2; i<nIns(); i++ ) { - if( in(i) instanceof CastNode cast ) - { setDef(i,cast.in(1)); progress=true; } - if( in(1) == in(i) ) { setDef(i,null); progress=true; } - else { allDefault=false; } + if( in(i) instanceof CastNode cast ) { setDef(i,cast.in(1)); progress=true; } + if( in(i)!=null && in(i)==in(1) ) { setDef(i,null); progress=true; } + if( in(i) instanceof MemMergeNode mem ) { + setDef(i,CodeGen.CODE.add(mem.alias(i))); + progress=true; + } + if( in(i)!=null ) allDefault=false; } + if( allDefault && in(1)!=null ) return in(1); - // If not merging any memory (all memory is just the default) - if( allDefault ) - return in(1); // Become default memory - - // Collapse stacked all-mem if( in(1) instanceof MemMergeNode mem ) { - // Goal is to swap my default mem with mem's default mem - for( int i=2; i<mem.nIns(); i++ ) { - if( mem.in(i) != null ) { - // deeper default mem has a non-default - if( i>=nIns() || in(i)==null ) - setDefX(i,mem.in(i)); - } - } + for( int i=2; i<mem.nIns(); i++ ) + if( mem.in(i)!=null && (i>=nIns() || in(i)==null) ) + alias(i,mem.in(i)); + CodeGen.CODE.add(mem.in(1)); setDef(1,mem.in(1)); return this; } - + assert BulkMemPhiNode.checkMerge(this); return progress ? this : null; } - - - public Node in( Var v ) { return in(v._idx); } - - public Node alias( int alias ) { - return alias < nIns() && in(alias)!=null ? in(alias) : in(1); - } - - Node alias( int alias, Node st ) { - while( alias >= nIns() ) addDef(null); - return setDef(alias,st); - } - - // Read or update from memory. - // A shared implementation allows us to create lazy phis both during - // lookups and updates; the lazy phi creation is part of chapter 8. - Node _mem( int alias, Node st ) { - // Memory projections are made lazily; if one does not exist - // then it must be START.proj(1) - Node old = alias(alias); - if( old instanceof ScopeNode loop ) { - MemMergeNode loopmem = loop.mem(); - Node memdef = loopmem.alias(alias); - // Lazy phi! - old = memdef instanceof PhiNode phi && loop.ctrl()==phi.region() - // Loop already has a real Phi, use it - ? memdef - // 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. - : loopmem.alias(alias, new PhiNode(Parser.memName(alias), TypeMem.make(alias,Type.BOTTOM),loop.ctrl(),loopmem._mem(alias,null),null).peephole() ); - alias(alias,old); - } - // Memory projections are made lazily; expand as needed - return st==null ? old : alias(alias,st); // Not lazy, so this is the answer - } - - - void _merge( MemMergeNode that, RegionNode r) { - int len = Math.max(nIns(),that.nIns()); - for( int i = 1; i < len; i++) - if( alias(i) != that.alias(i) ) { // No need for redundant Phis - // If we are in lazy phi mode we need to a lookup - // by alias as it will trigger a phi creation - Node lhs = this._mem(i,null); - Node rhs = that._mem(i,null); - alias(i, new PhiNode(Parser.memName(i), lhs._type.meet(rhs._type).glb(true), r, lhs, rhs).peephole()); - } - } - - // Fill in the backedge of any inserted Phis - void _endLoopMem( ScopeNode scope, MemMergeNode back, MemMergeNode exit ) { - Node exit_def = exit.alias(1); - for( int i=1; i<nIns(); i++ ) { - if( in(i) instanceof PhiNode phi && phi.region()==scope.ctrl() ) { - assert phi.in(2)==null; - phi.setDef(2,back.alias(i)==scope ? phi : back.alias(i)); // Fill backedge - } - if( exit_def == scope ) // Replace a lazy-phi on the exit path also - exit.alias(i,in(i)); - } - } - - // Now one-time do a useless-phi removal - void _useless( ) { - for( int i=2; i<nIns(); i++ ) { - if( in(i) instanceof PhiNode phi ) { - // Do an eager useless-phi removal - Node in = phi.peephole(); - CodeGen.CODE.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 - } - } - } - } - - @Override public boolean eq( Node n ) { - return this==n || !_inProgress; - } } diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/MemOpNode.java index ead82322..65021110 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/MemOpNode.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/MemOpNode.java @@ -6,6 +6,7 @@ import com.seaofnodes.simple.type.TypeMemPtr; import com.seaofnodes.simple.util.Utils; import java.lang.StringBuilder; import java.util.BitSet; +import java.util.ArrayList; /** * Convenience common base for Load and Store. @@ -78,6 +79,8 @@ public abstract class MemOpNode extends Node { static String mlabel(String name) { return "[]".equals(name) ? "ary" : ("#".equals(name) ? "len" : name); } String mlabel() { return mlabel(_name); } + @Override public boolean isMem() { return !_isLoad; } + public Node mem() { return in(1); } public Node ptr() { return in(2); } public Node off() { return in(3); } @@ -86,6 +89,13 @@ public abstract class MemOpNode extends Node { public StringBuilder _printMach( StringBuilder sb, BitSet visited ) { throw Utils.TODO(); } public int log_size() { return _declaredType.log_size(); } + // Extra conditions for factoring matching memory operations through a Phi. + // The caller has already checked the opcode, input shape and control. + boolean canDrop(MemOpNode other, Node dep) { + dep.addDep(ptr()); + return _alias==other._alias && _declaredType==other._declaredType && err()==null; + } + @Override public boolean eq(Node n) { MemOpNode mem = (MemOpNode)n; // Invariant @@ -106,4 +116,29 @@ public abstract class MemOpNode extends Node { return null; return Parser.error( "Might be null accessing '" + _name + "'",_loc); } + // Memory writers which must follow this read. A packaging node is not a + // write, but can hide New's input, so follow this alias through aggregates. + public ArrayList<Node> antiDeps() { + ArrayList<Node> deps = new ArrayList<>(); + antiDeps(mem(),deps,new BitSet()); + return deps; + } + + private void antiDeps(Node mem, ArrayList<Node> deps, BitSet visit) { + if( visit.get(mem._nid) ) return; + visit.set(mem._nid); + for( Node use : mem._outputs ) { + if( use instanceof MemMergeNode merge ) { + Node slice = _alias<merge.nIns() ? merge.in(_alias) : null; + if( (slice==mem) || (slice==null && merge.in(1)==mem) ) + antiDeps(merge,deps,visit); + } else if( use instanceof CallNode || + (use instanceof MemOpNode st && !st._isLoad && st._alias==_alias) || + (use instanceof NewNode nnn && nnn.field(_alias)!=null) || + (use instanceof MemPhiNode phi && phi._alias==_alias) || + (use instanceof BulkMemPhiNode phi && !phi.isSplit(_alias)) ) { + if( !deps.contains(use) ) deps.add(use); + } + } + } } diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java new file mode 100644 index 00000000..7e54f50f --- /dev/null +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java @@ -0,0 +1,44 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.codegen.CodeGen; +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,Type.BOTTOM),inputs); + assert alias > 1; + _alias = alias; + } + + public MemPhiNode(MemPhiNode phi) { super(phi); _alias = phi._alias; } + + @Override public String label() { return "MemPhi_"+_alias; } + + @Override public Type compute() { + assert BulkMemPhiNode.checkMem(this,_alias); + if( !(region() instanceof RegionNode r) ) + return region()._type==Type.XCONTROL ? TypeMem.TOP : _type; + if( r.inProgress() ) return _minType; + Type t = Type.TOP; + for( int i=1; i<nIns(); i++ ) + if( addDep(r.in(i))._type!=Type.XCONTROL ) + t = t.meet(MemMergeNode.contents(in(i),_alias,this)); + return TypeMem.make(_alias,t); + } + + @Override public Node idealize() { + for( int i=1; i<nIns(); i++ ) + if( in(i) instanceof MemMergeNode mem ) { + setDef(i,CodeGen.CODE.add(mem.alias(_alias))); + return this; + } + return super.idealize(); + } + + @Override public boolean eq(Node n) { return _alias==((MemPhiNode)n)._alias && super.eq(n); } + @Override int hash() { return _alias; } +} diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/NewNode.java index b0b4103b..49a82a87 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/NewNode.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/NewNode.java @@ -6,33 +6,35 @@ import com.seaofnodes.simple.util.SB; import java.util.BitSet; -/** - * Allocation! Allocate a chunk of memory, and pre-zero it. - * The inputs include control and size, and ALL aliases being set. - * The output is large tuple, one for every alias plus the created pointer. - * New is expected to be followed by projections for every alias. +/** Allocate a zeroed object. Inputs {ctrl, $mem, size}; + * results {ptr, $mem}. + * The memory input and result cover only the aliases in the allocated struct. */ public class NewNode extends Node implements MultiNode { public final TypeMemPtr _ptr; - public final int _len; public NewNode(TypeMemPtr ptr, Node... nodes) { super(nodes); assert !ptr.nullable(); _ptr = ptr; - _len = ptr._obj._fields.length; - // Control in slot 0 - assert nodes[0]._type==Type.CONTROL || nodes[0]._type == Type.XCONTROL; - // Malloc-length in slot 1 - assert nodes[1]._type instanceof TypeInteger || nodes[1]._type==Type.NIL; - for( int i=0; i<_len; i++ ) - assert ptr._obj._fields[i]._one || nodes[2 + i]._type.isa( TypeMem.BOT ); + assert nodes.length==3; + assert nodes[0]._type==Type.CONTROL || nodes[0]._type==Type.XCONTROL; + assert nodes[1]._type instanceof TypeMem; + assert nodes[2]._type instanceof TypeInteger || nodes[2]._type==Type.NIL; } - public NewNode(NewNode nnn) { super(nnn); _ptr = nnn._ptr; _len = nnn._len; } + public Node mem() { return in(1); } + public Node size() { return in(2); } + + public Field field(int alias) { + for( Field f : _ptr._obj._fields ) + if( !f._one && f._alias==alias ) return f; + return null; + } + + public NewNode(NewNode nnn) { super(nnn); _ptr = nnn._ptr; } - public Node mem (int idx) { return in(idx+2); } @Override public String label() { return "new_"+(_ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._t.str() : _ptr._obj.str()); } @Override @@ -41,38 +43,10 @@ public class NewNode extends Node implements MultiNode { return sb.append(_ptr._obj.str()); } - int findAlias(int alias) { - int idx = _ptr._obj.findAlias(alias); - assert idx!= -1; // Error, caller should be calling - return idx+2; // Skip ctrl, size - } - - // 0 - ctrl - // 1 - byte size - // 2-len+2 - aliases, one per field - // len+2 - 2*len+2 - initial values, one per field - public Node size() { return in(1); } @Override public TypeTuple compute() { - Field[] fs = _ptr._obj._fields; - Type[] ts = new Type[fs.length+2]; - ts[0] = Type.CONTROL; - ts[1] = _ptr; - for( int i=0; i<fs.length; i++ ) { - if( _ptr._obj._fields[i]._one ) { - // Once-only fields use the declared type - ts[i+2] = _ptr._obj._fields[i]._t; - } else { - // Others take from the inputs - Type mt = in(i+2)._type; - TypeMem mem = mt==Type.TOP ? TypeMem.TOP : (TypeMem)mt; - Type tfld = mem._t.meet(mem._t.makeZero()); - Type tfld2 = tfld.join(fs[i]._t ); - ts[i+2] = TypeMem.make(fs[i]._alias,tfld2); - } - } - return TypeTuple.make(ts); + return TypeTuple.make(_ptr,TypeMem.BOT); } @Override @@ -117,9 +91,9 @@ public class NewNode extends Node implements MultiNode { _kills = kills; } public String op() { return "alloc"; } - public RegMask regmap(int i) { return i==1 ? _arg3Mask : null; } + public RegMask regmap(int i) { return i==2 ? _arg3Mask : null; } public RegMask outregmap() { return null; } - public RegMask outregmap(int idx) { return idx==1 ? _retMask : null; } + public RegMask outregmap(int idx) { return idx==0 ? _retMask : null; } public RegMask killmap() { return _kills; } public void asm(CodeGen code, SB sb) { sb.p("#calloc, ").p(code.reg(size())); diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter23/src/main/java/com/seaofnodes/simple/node/Node.java index ab221789..c27270b6 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/Node.java @@ -232,6 +232,7 @@ public abstract class Node implements Cloneable { // Error is 'use' does not exist; ok for 'use' to be null. protected boolean delUse( Node use ) { _outputs.del(_outputs.find(use)); + moveDepsToWorklist(); // User-count and anti-dependence queries can now change. return _outputs.isEmpty(); } @@ -305,6 +306,7 @@ public abstract class Node implements Cloneable { n.unlock(); int idx = n._inputs.find(this); n._inputs.set(idx,nnn); + n.moveDepsToWorklist(); // Rewiring can change a dependent query without changing type. nnn.addUse(n); CODE.add(n); CODE.addAll(n._outputs); @@ -552,7 +554,12 @@ public abstract class Node implements Cloneable { * 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. */ - <N extends Node> N addDep( N dep ) { + <N extends Node> N addDep( N dep ) { return addDep(dep,false); } + + // Keep an explicit dependency when inspecting an immediate user's shape. + <N extends Node> N addDepForwards(N dep) { return addDep(dep,true); } + + private <N extends Node> N addDep(N dep, boolean forwards) { // Running peepholes during the big assert cannot have side effects // like adding dependencies. if( CODE._midAssert ) return dep; @@ -560,8 +567,8 @@ public abstract class Node implements Cloneable { if( obs != null ) obs.dep(this, dep); if( dep._deps==null ) dep._deps = new Ary<>(Node.class); if( dep._deps .find(this) != -1 ) return dep; // Already on list - if( dep._inputs .find(this) != -1 ) return dep; // No need for deps on immediate neighbors - if( dep._outputs.find(this) != -1 ) return dep; + if( !forwards && dep._inputs .find(this) != -1 ) return dep; // No need for deps on immediate neighbors + if( !forwards && dep._outputs.find(this) != -1 ) return dep; dep._deps.add(this); return dep; } diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/PhiNode.java index a711fe81..8ae94260 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -33,6 +33,10 @@ public class PhiNode extends Node { addDef(sample); } + 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_"+MemOpNode.mlabel(_label); } @Override @@ -94,6 +98,8 @@ public class PhiNode extends Node { if( live != null ) { if( live._type.isa(_type) ) return live; + // Let the bulk input split this alias before collapsing a precise Phi. + if( live instanceof BulkMemPhiNode ) return null; // Keep the Phi upcast return new CastNode(_type,null,live); } @@ -103,19 +109,9 @@ public class PhiNode extends Node { if( r.in(i)._type == Type.XCONTROL ) return null; - // Simple Phi-after-MemMerge to a known alias can bypass. Happens when inlining. - if( _type instanceof TypeMem tmem && tmem._alias!=1 ) { - for( int i=1; i<nIns(); i++ ) - if( in(i) instanceof MemMergeNode mem ) { - setDef(i,mem.alias(tmem._alias)); - return this; - } - } - - // Generic "pull down op" - Node progress; - if( same_op() && (progress = drop_same_op()) != null ) - return progress; + // Phi(op(a,b,...),op(c,d,...)) -> op(Phi(a,c),Phi(b,d),...). + Node down; + if( same_op() && (down=drop_same_op())!=null ) return down; // If merging Phi(N, cast(N)) - we are losing the cast JOIN effects, so just remove. if( nIns()==3 ) { @@ -149,50 +145,77 @@ public class PhiNode extends Node { // Same op on all Phi paths; all ops have only the Phi as a use. // None have a control input. private boolean same_op() { + Node op = in(1); + if( op instanceof CFGNode || op instanceof ConstantNode || op instanceof PhiNode || + op instanceof ProjNode || op instanceof NewNode || op instanceof ScopeNode || op instanceof MemMergeNode ) return false; + // A bulk Phi must split its aliases before factoring precise Stores. + if( this instanceof BulkMemPhiNode ) return false; + // Bulk splitting currently identifies parallel slices by Region/alias. + // Factoring can introduce another slice at a different memory point; + // wait until that Region's bulk partitioning has finished. + if( op instanceof MemOpNode ) + for( Node use : region()._outputs ) + if( use instanceof BulkMemPhiNode ) { + addDepForwards(use); + return false; + } + Node busy = null; for( int i=1; i<nIns(); i++ ) { - Node op = in(i); - if( in(1).getClass() != op.getClass() || op.in(0)!=null || in(1).nIns() != op.nIns() ) - return false; // Wrong class or CFG bound or mismatched inputs - if( in(1) instanceof MemOpNode mem && mem._alias != ((MemOpNode)op)._alias ) - return false; - if( op.nOuts() > 1 ) { // Too many users, but addDep in case lose users - for( Node out : op._outputs ) - if( out!=null && out!=this ) - addDep(out); - return false; + Node n = in(i); + if( addDep(region().in(i))._type==Type.XCONTROL || + op.getClass()!=n.getClass() || n.nIns()!=op.nIns() || n.in(0)!=null || !op.eq(n) ) return false; + if( n instanceof MemOpNode mem && !mem.canDrop((MemOpNode)op,this) ) return false; + // Moving a Store must remove the old effect, not duplicate it. + // Allow one shared arm: the other arms disappear into the single + // factored operation, usually reducing the total operation count. + addDepForwards(n); + if( n.nOuts()>1 ) { + for( Node use : n._outputs ) + if( use!=null && use!=this ) addDepForwards(use); + if( n instanceof StoreNode || busy!=null ) return false; + busy=n; } - for( int j=1; j<in(1).nIns(); j++ ) - if( op.in(j) instanceof ScopeNode || (op.in(j)==null ^ in(1).in(j)==null) ) - return false; // Lazy Phi input + for( int j=1; j<n.nIns(); j++ ) + if( n.in(j) instanceof ScopeNode || ((n.in(j)==null) != (op.in(j)==null)) ) return false; } return true; } private Node drop_same_op() { - assert !(in(1) instanceof CFGNode); - Node op = in(1); - Node cp = op.copy(); - cp._type = null; // Fresh type - cp.addDef(null); // No control - + Node op = in(1), cp = op.copyEmpty(); + cp._type = null; + cp.addDef(null); for( int j=1; j<op.nIns(); j++ ) { - boolean needsPhi = false; - Node x = op.in(j); // Jth input from sample #1 + Node x = op.in(j); + boolean different = false; for( int i=2; i<nIns(); i++ ) - if( in(i).in(j) != x ) - { needsPhi=true; break; } - if( needsPhi ) { - x = new PhiNode(_label,op.in(j)._type.glb(false)); - x.addDef(region()); - for( int i=1; i<nIns(); i++ ) - x.addDef(in(i).in(j)); - x = x.peephole(); + if( in(i).in(j)!=x ) different=true; + if( different ) { + Node[] ins = new Node[nIns()]; + ins[0] = region(); + Type t = Type.TOP; + for( int i=1; i<nIns(); i++ ) { + ins[i] = in(i).in(j); + t = t.meet(ins[i]._type); + } + // Not accepts both pointers and integers, but a Phi cannot mix them. + if( t==Type.BOTTOM ) { + for( int i=1; i<ins.length; i++ ) addDep(ins[i]); + cp.kill(); + return null; + } + PhiNode phi = j==1 && op instanceof MemOpNode mem + ? new MemPhiNode(_label,mem._alias,ins) + : PhiNode.make(_label,t.glb(false),ins); + x = phi.peephole(); } cp.addDef(x); } - // Test not running backwards, which can happen for e.g. And's - if( cp.compute().isa(compute()) ) - return cp; + // Factoring must not widen the result (e.g. correlated And operands). + if( cp.compute().isa(compute()) ) return cp; + for( int i=1; i<nIns(); i++ ) + for( int j=1; j<in(i).nIns(); j++ ) + if( in(i).in(j)!=null ) addDep(in(i).in(j)); cp.kill(); return null; } diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/RegionNode.java index 42b23633..14ba493e 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/RegionNode.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/RegionNode.java @@ -48,7 +48,7 @@ public class RegionNode extends CFGNode { if( !hasPhi() && nIns()>3 && // No Phi users, just a control user in(1) instanceof CProjNode p1 && in(2) instanceof CProjNode p2 && - addDep(p1.in(0))==addDep(p2.in(0)) && + addDepForwards(p1).in(0)==addDepForwards(p2).in(0) && p1.in(0) instanceof IfNode iff ) { // Replace with the iff.ctrl directly if( nIns()==3 ) return iff.ctrl(); diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/ReturnNode.java index 95303994..212bb875 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -9,15 +9,7 @@ import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; 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 all preceding memory effects and the return value alive: {ctrl, $mem, value, rpc}. */ public class ReturnNode extends CFGNode { public FunNode _fun; @@ -51,7 +43,8 @@ public class ReturnNode extends CFGNode { @Override public Type compute() { if( inProgress () ) return TypeTuple.RET; // In progress - return TypeTuple.make(ctrl()._type,mem()._type,expr()._type); + // Inlining can delete the entry while callers still use this return. + return TypeTuple.make(ctrl()._type,TypeMem.BOT,expr()._type); } @Override public Node idealize() { diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/ScopeNode.java index 04666956..cddb9f24 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -12,9 +12,9 @@ import java.util.*; /** * The Scope node is purely a parser helper - it tracks names to nodes with a - * stack of variables. + * stack of variable records. */ -public class ScopeNode extends MemMergeNode { +public class ScopeNode extends Node { /** * The control is a name that binds to the currently active control @@ -24,8 +24,12 @@ public class ScopeNode extends MemMergeNode { public static final String ARG0 = "arg"; public static final String MEM0 = "$mem"; - // All active/live variables in all nested scopes, all run together. - // Maps 1-to-1 to the _inputs array. + public Node in(Var v) { return in(v._idx); } + + @Override public Type compute() { return Type.BOTTOM; } + @Override public Node idealize() { return null; } + + // All active/live variables in all nested scopes, all run together public final Ary<Var> _vars; // Lexical scope is typed: @@ -50,10 +54,10 @@ public class ScopeNode extends MemMergeNode { // A new ScopeNode public ScopeNode() { - super(true); - _vars = new Ary<>(Var .class); - _kinds = new Ary<>(Kind.class); - _guards = new Ary<>(Node.class); + _type = Type.BOTTOM; + _vars = new Ary<>(Var .class); + _kinds = new Ary<>(Kind .class); + _guards = new Ary<>(Node .class); } @Override public String label() { return "Scope"; } @@ -85,7 +89,7 @@ public class ScopeNode extends MemMergeNode { public Node ctrl() { return in(0); } - public MemMergeNode mem() { return (MemMergeNode)in(1); } + public Node mem() { return in(lookup(MEM0)); } public Var var(int i) { return _vars.at(i); } /** @@ -99,7 +103,7 @@ public class ScopeNode extends MemMergeNode { * @return Node that was bound */ public <N extends Node> N ctrl(N n) { return setDef(0,n); } - public Node mem(Node n) { return setDef(1,n); } + public Node mem(Node n) { update(MEM0,n); return n; } public void push( Kind kind ) { kind._lexSize = _vars.size(); @@ -206,12 +210,6 @@ public class ScopeNode extends MemMergeNode { return true; } - // Read from memory - public Node mem( int alias ) { return mem()._mem(alias,null); } - // Write to memory - public void mem( int alias, Node st ) { mem()._mem(alias,st); } - - /** * Lookup a name in all scopes starting from most deeply nested. * @@ -247,7 +245,7 @@ public class ScopeNode extends MemMergeNode { // 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(v._idx,new PhiNode(v._name, v.type(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).peephole()); + : loop.setDef(v._idx,PhiNode.make(v._name, v.type(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).peephole()); setDef(v._idx,old); } //assert !v._final || st==null; @@ -285,21 +283,9 @@ public class ScopeNode extends MemMergeNode { n.keep(); dup.addDef(ctrl()); // Control input is just copied - // Memory input is a shallow copy - MemMergeNode memdup = new MemMergeNode(true), mem = mem(); - memdup.addDef(null); - memdup.addDef(loop ? this : mem.in(1)); - for( int i=2; i<mem.nIns(); i++ ) - // For lazy phis on loops we use a sentinel - // that will trigger phi creation on update - memdup.addDef(loop ? this : mem.in(i)); - dup.addDef(memdup); - - // Copy of other inputs - for( int i=2; i<nIns(); i++ ) - // For lazy phis on loops we use a sentinel - // that will trigger phi creation on update - dup.addDef(loop && !var(i)._final ? this : in(i)); + // Memory is one binding, with the same lazy loop Phi as scalar names. + for( int i=1; i<nIns(); i++ ) + dup.addDef(loop && !_vars.at(i)._final ? this : in(i)); return dup; } @@ -313,22 +299,21 @@ public class ScopeNode extends MemMergeNode { */ public RegionNode mergeScopes(ScopeNode that, Parser.Lexer loc) { RegionNode r = ctrl(new RegionNode(loc,null,ctrl(), that.ctrl()).keep()); - mem()._merge(that.mem(),r); - this ._merge(that ,r); + _merge(that,r); that.kill(); // Kill merged scope CodeGen.CODE.add(r); return r.unkeep(); } private void _merge(ScopeNode that, RegionNode r) { - for( int i = 2; i < nIns(); i++) + 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 Var v = var(i); Node lhs = this.in(this.update(v,null)); Node rhs = that.in(that.update(v,null)); - setDef(i, new PhiNode(v._name, v.type(), r, lhs, rhs).peephole()); + setDef(i, PhiNode.make(v._name, v.type(), r, lhs, rhs).peephole()); } } @@ -353,23 +338,17 @@ public class ScopeNode extends MemMergeNode { assert ctrl instanceof LoopNode loop && loop.inProgress(); ctrl.setDef(2,back.ctrl()); - mem()._endLoopMem( this, back.mem(), exit.mem() ); - this ._endLoop ( this, back , exit ); + _endLoop(this,back,exit); back.kill(); // Loop backedge is dead // Now one-time do a useless-phi removal - mem()._useless(); - this ._useless(); - - // The exit mem's lazy default value had been the loop top, - // now it goes back to predating the loop. - exit.mem().setDef(1,mem().in(1)); + _useless(); } // Fill in the backedge of any inserted Phis void _endLoop( ScopeNode scope, Node back, Node exit ) { - for( int i=2; i<nIns(); i++ ) { - if( var(i)._final ) continue; // Final vars did not get modified in the loop - if( var(i).type().isHighOrConst() ) continue; // Cannot lift higher than a constant, so no Phi + for( int i=1; i<nIns(); i++ ) { + if( _vars.at(i)._final ) continue; // Final vars did not get modified in the loop + if( _vars.at(i).type().isHighOrConst() ) continue; // Cannot lift higher than a constant, so no Phi if( back.in(i) != scope ) { PhiNode phi = (PhiNode)in(i); assert phi.region()==scope.ctrl() && phi.in(2)==null; @@ -381,6 +360,23 @@ public class ScopeNode extends MemMergeNode { } + // Now one-time do a useless-phi removal + void _useless( ) { + for( int i=1; i<nIns(); i++ ) { + if( in(i) instanceof PhiNode phi ) { + // Do an eager useless-phi removal + Node in = phi.peephole(); + CodeGen.CODE.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. public void addGuards( Node ctrl, Node pred, boolean invert ) { diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/StartNode.java index 6ed2acd3..186adfb7 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -3,18 +3,11 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.type.*; -import java.util.Arrays; import java.util.BitSet; import java.util.HashSet; import static com.seaofnodes.simple.util.Utils.TODO; -/** - * The Start node represents the start of the function. - * <p> - * 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. - */ +/** Start supplies control, whole memory, and the argument, in that order. */ public class StartNode extends LoopNode implements MultiNode { final Type _arg; @@ -44,7 +37,7 @@ public class StartNode extends LoopNode implements MultiNode { @Override public TypeTuple compute() { - return TypeTuple.make(Type.CONTROL,TypeMem.TOP,_arg); + return TypeTuple.make(Type.CONTROL,TypeMem.BOT,_arg); } @Override public Node idealize() { return null; } diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/StoreNode.java index 74c27dc8..ac437788 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.*; import com.seaofnodes.simple.type.*; import java.util.BitSet; @@ -25,6 +26,10 @@ public class StoreNode extends MemOpNode { } // Debugger label + @Override boolean canDrop(MemOpNode other, Node dep) { + return super.canDrop(other,dep) && _init==((StoreNode)other)._init; + } + @Override public String label() { return "st_"+mlabel(); } @Override public boolean isMem() { return true; } @@ -38,24 +43,18 @@ public class StoreNode extends MemOpNode { @Override public Type compute() { Type val = val()._type; - Type mem0 = mem()._type; - if( mem0 == Type.TOP ) return TypeMem.TOP; - TypeMem mem = (TypeMem)mem0; // Invariant - if( mem == TypeMem.TOP ) return TypeMem.TOP; - Type t = Type.BOTTOM; // No idea on field contents - // Same alias, lift val to the declared type and then meet into other fields - if( mem._alias == _alias ) { - assert !_declaredType.isFRef(); - // Sharpen memory value; required for narrowing stores where the parser inserts - // zero/sign masking and somebody reads the TypeMem type. - val = val.join(_declaredType); - t = val.meet(mem._t); - } + assert !_declaredType.isFRef(); + Type t = val.join(_declaredType).meet(MemMergeNode.contents(mem(),_alias,this)); return TypeMem.make(_alias,t); } @Override public Node idealize() { + if( mem() instanceof MemMergeNode merge ) { + setDef(1,CodeGen.CODE.add(merge.alias(_alias))); + return this; + } + if( mem() instanceof CastNode cast ) { setDef(1,cast.in(1)); @@ -65,8 +64,8 @@ public class StoreNode extends MemOpNode { // 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 - off()==st.off() && // And same offset (could be "same alias" but this handles arrays to same index) + ptr()==st.ptr() && _alias==st._alias && // Must check same object + off()==st.off() && // And same offset 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. @@ -76,12 +75,6 @@ public class StoreNode extends MemOpNode { return this; } - // Simple store-after-MemMerge to a known alias can bypass. Happens when inlining. - if( mem() instanceof MemMergeNode mem ) { - setDef(1,mem.alias(_alias)); - return this; - } - // Value is automatically truncated by narrow store if( val() instanceof AndNode and && and.in(2)._type.isConstant() ) { int log = _declaredType.log_size(); diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java index bca3dfa4..c39c5f74 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java @@ -673,6 +673,8 @@ public class arm extends Machine { case NotNode not -> new NotARM(not); case OrNode or -> or(or); case ParmNode parm -> new ParmARM(parm); + case BulkMemPhiNode phi -> new BulkMemPhiNode(phi); + case MemPhiNode phi -> new MemPhiNode(phi); case PhiNode phi -> new PhiNode(phi); case ProjNode prj -> prj(prj); case ReadOnlyNode read -> new ReadOnlyMach(read); diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java index 48cb5d23..88ec9ebd 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java @@ -345,6 +345,8 @@ public class riscv extends Machine { case NotNode not -> new NotRISC(not); case OrNode or -> or(or); case ParmNode parm -> new ParmRISC(parm); + case BulkMemPhiNode phi -> new BulkMemPhiNode(phi); + case MemPhiNode phi -> new MemPhiNode(phi); case PhiNode phi -> new PhiNode(phi); case ProjNode prj -> prj(prj); case ReadOnlyNode read-> new ReadOnlyMach(read); diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java index 441cf693..d0fb0081 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java @@ -24,6 +24,11 @@ public class LoadX86 extends MemOpX86 { } static void enc( Encoding enc, Type decl, short dst, short ptr, short idx, int off, int scale ) { + // Allocation may put a full-width integer in an XMM register (or a + // floating value in a GPR). Move its bits using the selected register bank. + if( dst>=x86_64_v2.XMM_OFFSET && decl.log_size()==3 ) decl=TypeFloat.F64; + else if( dst<x86_64_v2.XMM_OFFSET && decl instanceof TypeFloat ) + decl = decl==TypeFloat.F32 ? TypeInteger.U32 : TypeInteger.BOT; if( decl == TypeFloat.F32) enc.add1(0xF3); if( decl == TypeFloat.F64) enc.add1(0xF2); diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java index 7ac7fc19..cf9288b8 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java @@ -363,6 +363,8 @@ public class x86_64_v2 extends Machine { case NotNode not -> new NotX86(not); case OrNode or -> or(or); case ParmNode parm -> new ParmX86(parm); + case BulkMemPhiNode phi -> new BulkMemPhiNode(phi); + case MemPhiNode phi -> new MemPhiNode(phi); case PhiNode phi -> new PhiNode(phi); case ProjNode prj -> prj(prj); case ReadOnlyNode read-> new ReadOnlyMach(read); @@ -396,9 +398,8 @@ public class x86_64_v2 extends Machine { if( lhs instanceof LoadNode ld && ld.nOuts() == 1 && ld._declaredType.log_size() >= 3) return new AddMemX86(add, address(ld), ld.ptr(), idx, off, scale, 0, rhs); -// if(rhs instanceof LoadNode ld && ld.nOuts() == 1 && ld._declaredType.log_size() >= 3) { -// throw Utils.TODO(); // Swap load sides -// } + if( rhs instanceof LoadNode ld && ld.nOuts()==1 && ld._declaredType.log_size()>=3 ) + return new AddMemX86(add,address(ld),ld.ptr(),idx,off,scale, 0,lhs); // Attempt a full LEA-style break down. // Returns one of AddX86, AddIX86, LeaX86, or LHS diff --git a/chapter23/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter23/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index 7f7107ec..6f19fa9d 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -67,7 +67,7 @@ public class SimpleGraphAdapter extends GraphAdapter<Node> { return n instanceof CFGNode ? 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( n instanceof MemMergeNode || i == 1 && (n instanceof MemOpNode || n instanceof ReturnNode || n instanceof CallNode) ) return Role.MEM; + if( n instanceof MemMergeNode || (i == 1 && (n instanceof MemOpNode || n instanceof ReturnNode || n instanceof CallNode || n instanceof NewNode)) ) return Role.MEM; Node def = n.in(i); if( def != null && isMem(def) ) return Role.MEM; return Role.DATA; diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter23/src/test/java/com/seaofnodes/simple/Chapter10Test.java index 3c5a2913..121e8a22 100644 --- a/chapter23/src/test/java/com/seaofnodes/simple/Chapter10Test.java +++ b/chapter23/src/test/java/com/seaofnodes/simple/Chapter10Test.java @@ -2,6 +2,8 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.codegen.CodeGen; import org.junit.Test; +import com.seaofnodes.simple.node.*; +import java.util.BitSet; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -468,4 +470,121 @@ return 0; assertEquals("return 0;", code.print()); } + @Test public void testReadBeforeStores() { + var code = new CodeGen(""" + 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().opto(); + StopNode stop = code._stop; + assertEquals("1133",Eval2.eval(code,0)); + assertEquals("2244",Eval2.eval(code,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 code = new CodeGen(NESTED_MEMORY).parse().opto(); + StopNode stop = code._stop; + for (int arg=0; arg<7; arg++) { + assertEquals("S{x="+(5L+arg)+",y="+(7L+arg*(arg+1)-(arg>=2 ? 2 : 0))+ + ",z="+(11L+2*arg-(arg>=2 ? 2 : 0))+"}",Eval2.eval(code,arg)); + } + } + + @Test public void testMemoryAtEarlyReturns() { + var code = new CodeGen(""" + 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().opto(); + StopNode stop = code._stop; + for (int arg=0; arg<2; arg++) { + assertEquals(arg==0 ? "S{x=3,y=17,z=7}" : "S{x=11,y=5,z=13}",Eval2.eval(code,arg)); + } + } + + @Test public void testKeepLoadsAtMerge() { + var code = new CodeGen(""" + struct S { int x; }; + S !a = new S; S !b = new S; + a.x = arg; b.x = arg+1; + S !p = a; S !q = b; + if (arg<0) { p=b; q=a; } + int v; + if (arg>1) v=p.x; else v=q.x; + return v; + """).parse().opto(); + StopNode stop = code._stop; + // The one-step safety check stops at the memory aggregate. + assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet())); + assertEquals("-1",Eval2.eval(code,-1)); + assertEquals("1",Eval2.eval(code,0)); + assertEquals("3",Eval2.eval(code,3)); + } + + @Test public void testDropStores() { + var code = new CodeGen(""" + struct S { int x; }; + S !s = new S; + if (arg) s.x=arg+1; else s.x=arg+2; + return s; + """).parse().opto(); + StopNode stop = code._stop; + assertEquals(1,countMemoryNodes(stop,StoreNode.class,new BitSet())); + assertEquals("S{x=2}",Eval2.eval(code,0)); + assertEquals("S{x=4}",Eval2.eval(code,3)); + } + + @Test public void testKeepReadsBeforeWrites() { + var code = new CodeGen(""" + struct S { int x; int y; }; + S !a = new S; S !b = new S; + a.x=arg; b.x=arg+1; + S !p=a; S !q=b; + if (arg<0) { p=b; q=a; } + int v; + if (arg>1) { v=p.x; p.x=41; p.y=5; } + else { v=q.x; q.x=42; } + return v; + """).parse().opto(); + StopNode stop = code._stop; + assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet())); + assertEquals("-1",Eval2.eval(code,-1)); + assertEquals("1",Eval2.eval(code,0)); + assertEquals("3",Eval2.eval(code,3)); + } + + private static int countMemoryNodes(Node n, Class<?> kind, BitSet seen) { + if (n==null || seen.get(n._nid)) return 0; + seen.set(n._nid); + int cnt=kind.isInstance(n) ? 1 : 0; + for (Node def : n._inputs) cnt+=countMemoryNodes(def,kind,seen); + return cnt; + } } diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Chapter10bTest.java b/chapter23/src/test/java/com/seaofnodes/simple/Chapter10bTest.java new file mode 100644 index 00000000..f2b83cdd --- /dev/null +++ b/chapter23/src/test/java/com/seaofnodes/simple/Chapter10bTest.java @@ -0,0 +1,60 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import org.junit.Test; +import static org.junit.Assert.*; + +public class Chapter10bTest { + @Test public void testIndependentField() { + var code = new CodeGen(""" + 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().opto(); + var stop = code._stop; + assertEquals("return 42;",stop.toString()); + assertEquals("42",Eval2.eval(code,5)); + assertNull(stop.walk(n -> n instanceof LoadNode ? n : null)); + } + + @Test public void testParallelMemoryPhis() { + var code = new CodeGen(""" + 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().opto(); + var stop = code._stop; + 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) && !(n instanceof ParmNode)); + return null; + }); + assertTrue(phis[0]>=3); + assertEquals("S{x=1,y=2,z=3}",Eval2.eval(code,4)); + } + + @Test public void testLateSliceWorklist() { + // 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 code = new CodeGen(Chapter10Test.NESTED_MEMORY,com.seaofnodes.simple.type.TypeInteger.BOT,seed,false).parse(); + code.opto(); + assertEquals("S{x=9,y=25,z=17}",Eval2.eval(code,4)); + } + } +} diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/chapter23/src/test/java/com/seaofnodes/simple/Chapter16Test.java index 310efc45..57ec5508 100644 --- a/chapter23/src/test/java/com/seaofnodes/simple/Chapter16Test.java +++ b/chapter23/src/test/java/com/seaofnodes/simple/Chapter16Test.java @@ -253,4 +253,27 @@ return new Square; assertEquals("Square{side=3.0,diag=1.7320508075688772}", Eval2.eval(code, 3)); assertEquals("Square{side=4.0,diag=2.0}", Eval2.eval(code, 4)); } + static final String CONSTRUCTOR_MEMORY = """ + struct S { int x; int y; }; + struct T { int z=arg+40; }; + T !t = new T; + S !a = new S { x=11; y=7; }; + S !b = new S { x=22; y=9; }; + S !p=a; + if (arg) p=b; + int before=p.x; + S !c = new S { + x=p.x+1; + { int i=0; while (i<2) { p.y=p.y+1; i=i+1; } } + y=p.y; + }; + p.x=33; + return before*10000+c.x*100+c.y+t.z; + """; + + @Test public void testConstructorMemory() { + var code = new CodeGen(CONSTRUCTOR_MEMORY).parse().opto(); + assertEquals("111249",Eval2.eval(code,0)); + assertEquals("222352",Eval2.eval(code,1)); + } } diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Chapter17Test.java b/chapter23/src/test/java/com/seaofnodes/simple/Chapter17Test.java index 53c23ecd..37208308 100644 --- a/chapter23/src/test/java/com/seaofnodes/simple/Chapter17Test.java +++ b/chapter23/src/test/java/com/seaofnodes/simple/Chapter17Test.java @@ -369,7 +369,7 @@ var b = arg ? new Bar { next = (arg==2) ? new Bar{x=2;}; x=1; }; return b ? b.next ? b.next.x : b.x; // parses "b ? (b.next ? b.next.x : b.x) : 0" """); code.parse().opto(); - assertEquals("return Phi(Region,.x,0);", code.print()); + assertEquals("return Phi(Region,.x,0,.x);", code.print()); assertEquals("0", Eval2.eval(code, 0)); assertEquals("1", Eval2.eval(code, 1)); assertEquals("2", Eval2.eval(code, 2)); diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Chapter18Test.java b/chapter23/src/test/java/com/seaofnodes/simple/Chapter18Test.java index 299d31f9..1c1d011e 100644 --- a/chapter23/src/test/java/com/seaofnodes/simple/Chapter18Test.java +++ b/chapter23/src/test/java/com/seaofnodes/simple/Chapter18Test.java @@ -439,4 +439,70 @@ return p.coffee_count; assertEquals("1", Eval2.eval(code, 2)); } + static final String CALL_MEMORY = """ + struct S { int x; int y; }; + val bump = { S s, int d -> int old=s.x; s.x=old+d; return old; }; + S !a = new S { x=10; y=7; }; + S !b = new S { x=20; y=9; }; + S !p=a; if (arg) p=b; + int before=p.x; + int old=bump(p,3); + int after=p.x; + bump(a,5); + return before*1000000+old*10000+after*100+a.x+a.y; + """; + + @Test public void testCallMemory() { + var code = new CodeGen(CALL_MEMORY).parse().opto(); + int[] calls={0}; + code._stop.walk(n -> { if(n instanceof CallNode) calls[0]++; return null; }); + assertEquals(2,calls[0]); // Keep real calls so this checks scheduling across them. + assertEquals("10101325",Eval2.eval(code,0)); + assertEquals("20202322",Eval2.eval(code,1)); + code._stop.walk(n -> { + if(n instanceof CallNode call) { + assertEquals(5,call.nIns()); // No scheduling edges may become extra arguments. + assertTrue(call.fptr()._type instanceof TypeFunPtr); + } + return null; + }); + } + + static final String RECURSIVE_MEMORY = """ + struct S { int x; }; + val rec = { S s, int n -> + if (n==0) return s.x; + int before=s.x; + s.x=before+1; + return before+rec(s,n-1); + }; + S !s=new S { x=10; }; + int value=rec(s,arg); + return value*100+s.x; + """; + + @Test public void testRecursiveMemory() { + var code = new CodeGen(RECURSIVE_MEMORY).parse().opto(); + for(int n=0; n<7; n++) + assertEquals(Long.toString((10L*(n+1)+n*(n+1)/2)*100+10+n),Eval2.eval(code,n)); + } + + static final String INLINE_MEMORY = """ + struct S { int x; int y; }; + val make = { S s -> + S t = new S { x=s.x+1; y=3; }; + s.y=9; + return t; + }; + S !a = new S { x=arg+40; y=7; }; + S t=make(a); + return a.x*10000+a.y*100+t.x+t.y; + """; + + @Test public void testInlineAllocationMemory() { + var code = new CodeGen(INLINE_MEMORY).parse().opto(); + assertNull(code._stop.walk(n -> n instanceof CallNode ? n : null)); + assertEquals("400944",Eval2.eval(code,0)); + assertEquals("420946",Eval2.eval(code,2)); + } } diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Chapter19Test.java b/chapter23/src/test/java/com/seaofnodes/simple/Chapter19Test.java index 93f31e19..7ac16a93 100644 --- a/chapter23/src/test/java/com/seaofnodes/simple/Chapter19Test.java +++ b/chapter23/src/test/java/com/seaofnodes/simple/Chapter19Test.java @@ -351,4 +351,72 @@ return sq(arg) + sq(3); code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("Stop[ return (add,#2,#2); return (mul,Parm_x(sq,i64),x); ]", code.print()); } + + @Test public void testSelectedMemory() { + for( String cpu : new String[]{"x86_64_v2","riscv","arm"} ) + for( String src : new String[]{Chapter10Test.NESTED_MEMORY,Chapter16Test.CONSTRUCTOR_MEMORY, + Chapter18Test.CALL_MEMORY,Chapter18Test.RECURSIVE_MEMORY} ) { + var code = new CodeGen(src).parse().opto().typeCheck(); + var aliases = memoryPhis(code._stop); + code.loopTree().instSelect(cpu,"SystemV"); + assertEquals(aliases,memoryPhis(code._stop)); + code.GCM().localSched(); + code._stop.walk(n -> { + if( !(n instanceof CFGNode) && !(n instanceof ProjNode) ) + assertTrue(n.in(0) instanceof CFGNode); + if( n instanceof NewNode nn ) { + assertEquals(2,((TypeTuple)nn._type)._types.length); + assertTrue(nn.mem() instanceof MemMergeNode); + assertNull(nn.mem().in(1)); // Partial allocation input. + nn.cacheRegs(code); + MachNode mach = (MachNode)nn; + assertNull(mach.regmap(1)); + assertNotNull(mach.regmap(2)); + assertNotNull(mach.outregmap(0)); + assertNull(mach.outregmap(1)); + } + if( n instanceof CallNode call ) { + assertEquals(4,call.nIns()); // ctrl, memory, two args; direct target is embedded. + } + return null; + }); + } + } + + private static java.util.ArrayList<String> memoryPhis(Node stop) { + var phis = new java.util.ArrayList<String>(); + stop.walk(n -> { + if( n instanceof MemPhiNode phi ) phis.add("alias:"+phi._alias); + if( n instanceof BulkMemPhiNode phi ) phis.add("bulk:"+phi._aliases); + assertFalse(n instanceof PhiNode && n.isMem() && + !(n instanceof MemPhiNode) && !(n instanceof BulkMemPhiNode) && !(n instanceof ParmNode)); + return null; + }); + java.util.Collections.sort(phis); + return phis; + } + + @Test public void testFoldedReadBeforeWrites() { + var code = new CodeGen(""" + struct S { int x; }; + S !a = new S { x=11; }; S !b = new S { x=22; }; + S !p = a; if( arg ) p = b; + int before = p.x + arg; + a.x=33; b.x=44; + return before; + """).parse().opto().typeCheck().loopTree().instSelect("x86_64_v2","SystemV").GCM().localSched(); + var read = (MemOpNode)code._stop.walk(n -> + n instanceof com.seaofnodes.simple.node.cpus.x86_64_v2.AddMemX86 ? n : null); + assertNotNull(read); + int writes = 0; + for( Node use : read.antiDeps() ) + if( use instanceof MemOpNode st && st.isMem() ) { + assertSame(read.cfg0(),st.cfg0()); + assertTrue(st._inputs.find(read)>=0); + assertTrue(read.cfg0()._outputs.find(read)<st.cfg0()._outputs.find(st)); + assertNull(((MachNode)st).regmap(st._inputs.find(read))); + writes++; + } + assertEquals(1,writes); // The next Store follows this first clobber through memory. + } } diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Chapter20Test.java b/chapter23/src/test/java/com/seaofnodes/simple/Chapter20Test.java index 044ad4ed..1489b9ac 100644 --- a/chapter23/src/test/java/com/seaofnodes/simple/Chapter20Test.java +++ b/chapter23/src/test/java/com/seaofnodes/simple/Chapter20Test.java @@ -115,7 +115,7 @@ val sqrt = { int x -> }; return sqrt(arg) + sqrt(arg+2); """; - testTarget(src,"x86_64_v2", "SystemV",48,null); + testTarget(src,"x86_64_v2", "SystemV",40,null); testTarget(src,"riscv" , "SystemV",17,null); testTarget(src,"arm" , "SystemV",18,null); } @@ -162,7 +162,7 @@ for( int i=0; i<ary#-1; i++ ) ary[i+1] += ary[i]; return ary[1] * 1000 + ary[3]; // 1 * 1000 + 6 """; - testTarget(src,"x86_64_v2", "SystemV",9,"return .[];"); + testTarget(src,"x86_64_v2", "SystemV",5,"return .[];"); testTarget(src,"riscv" , "SystemV",7,"return (add,.[],(mul,.[],1000));"); testTarget(src,"arm" , "SystemV",5,"return (add,.[],(mul,.[],1000));"); } @@ -207,7 +207,7 @@ hashCode(s); """; testTarget(src,"x86_64_v2", "SystemV",9,null); testTarget(src,"riscv" , "SystemV",4,null); - testTarget(src,"arm" , "SystemV",3,null); + testTarget(src,"arm" , "SystemV",1,null); } @Test @@ -366,8 +366,29 @@ return a; """; testTarget(src,"x86_64_v2", "SystemV",52,null); testTarget(src,"riscv" , "SystemV",44,null); - testTarget(src,"arm" , "SystemV",44,null); + testTarget(src,"arm" , "SystemV",41,null); //assertEquals("int[ 1,2,3,4,5,6,7,8,9,10,11]", Eval2.eval(code, 11)); } + + @Test public void testMemoryAllocation() { + for( String cpu : new String[]{"x86_64_v2","riscv","arm"} ) + for( String src : new String[]{Chapter10Test.NESTED_MEMORY,Chapter16Test.CONSTRUCTOR_MEMORY, + Chapter18Test.CALL_MEMORY,Chapter18Test.RECURSIVE_MEMORY, + "int[] !a = new int[3]; a[arg]=arg; return a[0]+1;"} ) { + var code = new CodeGen(src).parse().opto().typeCheck().loopTree() + .instSelect(cpu,"SystemV").GCM().localSched().regAlloc(); + com.seaofnodes.simple.codegen.RegAllocTestSupport.checkRegisters(code); + code._stop.walk(n -> { + // Whole memory, precise Phis, and memory projections need no register. + if( n._type instanceof com.seaofnodes.simple.type.TypeMem ) + assertEquals(-1,code._regAlloc.regnum(n)); + if( n instanceof com.seaofnodes.simple.node.NewNode nn ) { + assertTrue(code._regAlloc.regnum(nn.proj(0))>=0); + assertEquals(-1,code._regAlloc.regnum(nn.proj(1))); + } + return null; + }); + } + } } diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Chapter21AllocTest.java b/chapter23/src/test/java/com/seaofnodes/simple/Chapter21AllocTest.java index 2d0493e5..bab7a86a 100644 --- a/chapter23/src/test/java/com/seaofnodes/simple/Chapter21AllocTest.java +++ b/chapter23/src/test/java/com/seaofnodes/simple/Chapter21AllocTest.java @@ -21,7 +21,7 @@ val sqrt = { int x -> }; return sqrt(arg) + sqrt(arg+2); """; - Chapter21Test.testCPU(src,"x86_64_v2", "Win64" ,48,null); + Chapter21Test.testCPU(src,"x86_64_v2", "Win64" ,40,null); Chapter21Test.testCPU(src,"riscv" , "SystemV",19,null); Chapter21Test.testCPU(src,"arm" , "SystemV",19,null); } @@ -49,7 +49,7 @@ return sqrt(arg) + sqrt(arg+2); String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/stringHash21.smp")); Chapter21Test.testCPU(src,"x86_64_v2", "SystemV", 9,null); Chapter21Test.testCPU(src,"riscv" , "SystemV", 3,null); - Chapter21Test.testCPU(src,"arm" , "SystemV", 3,null); + Chapter21Test.testCPU(src,"arm" , "SystemV", 1,null); } } diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/chapter23/src/test/java/com/seaofnodes/simple/Chapter21Test.java index 6438801b..14f9fdb8 100644 --- a/chapter23/src/test/java/com/seaofnodes/simple/Chapter21Test.java +++ b/chapter23/src/test/java/com/seaofnodes/simple/Chapter21Test.java @@ -196,6 +196,7 @@ public class Chapter21Test { @Test public void testCoalescing() { com.seaofnodes.simple.codegen.RegAllocTestSupport.coalescing(); } + @Test public void testCopyForward() throws Exception { com.seaofnodes.simple.codegen.RegAllocTestSupport.copyForward(); } @Test public void testNarrowStores() throws IOException { for( String type : new String[]{"i8","u8","i16","u16"} ) { @@ -339,7 +340,7 @@ public class Chapter21Test { String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/stringHash.smp")); testCPU(src,"x86_64_v2", "SystemV", 9,null); testCPU(src,"riscv" , "SystemV", 3,null); - testCPU(src,"arm" , "SystemV", 3,null); + testCPU(src,"arm" , "SystemV", 1,null); } @Test public void testStringExport() throws IOException { diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Chapter23AllocTest.java b/chapter23/src/test/java/com/seaofnodes/simple/Chapter23AllocTest.java index cd8c43cf..7f4b86d5 100644 --- a/chapter23/src/test/java/com/seaofnodes/simple/Chapter23AllocTest.java +++ b/chapter23/src/test/java/com/seaofnodes/simple/Chapter23AllocTest.java @@ -11,7 +11,7 @@ public class Chapter23AllocTest { String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/stringHash.smp")); Chapter23Test.testCPU(src,"x86_64_v2", "SystemV", 9,null); Chapter23Test.testCPU(src,"riscv" , "SystemV", 3,null); - Chapter23Test.testCPU(src,"arm" , "SystemV", 3,null); + Chapter23Test.testCPU(src,"arm" , "SystemV", 1,null); } @Test diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Eval2.java b/chapter23/src/test/java/com/seaofnodes/simple/Eval2.java index f5b78f91..feb67ba9 100644 --- a/chapter23/src/test/java/com/seaofnodes/simple/Eval2.java +++ b/chapter23/src/test/java/com/seaofnodes/simple/Eval2.java @@ -314,6 +314,8 @@ public abstract class Eval2 { case TypeFunPtr tfp -> tfp; case TypeMem mem -> "MEM"; case TypeMemPtr tmp -> { + // Unknown-caller inputs are type placeholders, not heap constants. + if( !tmp.isConstant() ) yield tmp; if( tmp._obj.isAry() ) { // Constant array ptr TypeConAry con = (TypeConAry)tmp._obj.field("[]")._t; Object[] xs = new Object[con.len()]; @@ -341,7 +343,7 @@ public abstract class Eval2 { private static Object alloc(NewNode alloc) { TypeStruct type = alloc._ptr._obj; if( type.isAry() ) { - long sz = (Long)val(alloc.in(1)); + long sz = (Long)val(alloc.size()); long x = offToIdx(sz, type); int n = (int)x; if( n!=x || n<0 ) @@ -462,9 +464,9 @@ public abstract class Eval2 { case TypeRPC rpc -> sb.p(x.toString()); case TypeMem mem -> sb.p("$mem"); case TypeTuple tt -> { - if( tt._types.length>1 && tt._types[1] instanceof TypeMemPtr ) + if( tt._types.length>1 && tt._types[0] instanceof TypeMemPtr ) // Assume a NewNode - yield _print( tt._types[1], x, sb, visit ); + yield _print( tt._types[0], x, sb, visit ); throw Utils.TODO(); } case Type tt -> { diff --git a/chapter23/src/test/java/com/seaofnodes/simple/EvalArm64.java b/chapter23/src/test/java/com/seaofnodes/simple/EvalArm64.java index 1ab3aa6c..e0cbdcff 100644 --- a/chapter23/src/test/java/com/seaofnodes/simple/EvalArm64.java +++ b/chapter23/src/test/java/com/seaofnodes/simple/EvalArm64.java @@ -542,10 +542,13 @@ public class EvalArm64 { break; } - case 0xD2: { + case 0xD2: // MOVZ + case 0xF2: { // MOVK preserves the other halfwords // movz // get immediate - rval = (ir >> 5) & 0x7FFF; + int shift = ((ir >> 21) & 3)*16; + rval = ((ir >> 5) & 0xFFFFL) << shift; + if( opcode1==0xF2 ) rval |= regs[rdid] & ~(0xFFFFL << shift); break; } diff --git a/chapter23/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java b/chapter23/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java new file mode 100644 index 00000000..0f09b7f0 --- /dev/null +++ b/chapter23/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java @@ -0,0 +1,178 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegAllocTestSupport; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.cpus.arm.arm; +import com.seaofnodes.simple.node.cpus.riscv.riscv; +import com.seaofnodes.simple.type.TypeMemPtr; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.Test; +import static org.junit.Assert.*; + +// Execution regressions are separate from the fixed spill-measurement cohorts. +public class MemoryEncodingTest { + @Test public void testLoopLoadSearch() { + String[] sources = { + """ + u8[] !program=new u8[106]; u8[] !out=new u8[0]; + int sum=0; + for(int i=0; i<arg; i++) { + sum+=program#; + if(i==1) out=new u8[i+1]; + else if(i==3) out=new u8[2]; + } + return sum+out#; + """, + // One possibly aliasing arm must reject the unchanged-memory proof. + """ + struct S { int x; }; S !a=new S; S !b=new S; + a.x=10; b.x=20; S !p=a; if(arg&1) p=b; + int sum=0; + for(int i=0; i<6; i++) { + if(i==2) p.x=30; + else if(i==4) b.x=40; + sum+=a.x; + } + return sum; + """, + // Unchanged memory does not imply an unchanged loop-carried pointer. + """ + struct S { int x; }; S !a=new S; S !b=new S; + a.x=10; b.x=20; S !p=a; int sum=0; + for(int i=0; i<6; i++) { + if(i==2) a.x=30; + sum+=p.x; + p=b; if(i&1) p=a; + } + return sum; + """, + // All arms fold, but their values differ: build a value Phi. + """ + struct S { int x; }; S !a=new S; a.x=1; int sum=0; + for(int i=0; i<6; i++) { + sum+=a.x; + if(i&1) a.x=2; + else if(i==2) a.x=3; + else a.x=4; + } + return sum; + """ + }; + for( int p=0; p<sources.length; p++ ) { + CodeGen code=new CodeGen(sources[p]).parse().opto(); + if( p==0 ) assertNull(code._stop.walk(n -> + n instanceof LoadNode ld && ld.ptr() instanceof ProjNode ptr && + ptr.in(0) instanceof NewNode nn && + nn.size()._type==com.seaofnodes.simple.type.TypeInteger.constant(110) ? ld : null)); + for( int arg=0; arg<7; arg++ ) { + long expected=switch(p) { + case 0 -> 106*arg+(arg>=2 ? 2 : 0); + case 1 -> (arg&1)==0 ? 140 : 60; + case 2 -> 130; + default -> 16; + }; + assertEquals(Long.toString(expected),Eval2.eval(code,arg)); + } + } + } + + private static final String[] SOURCES = { + Chapter16Test.CONSTRUCTOR_MEMORY, + Chapter18Test.CALL_MEMORY, + Chapter18Test.RECURSIVE_MEMORY, + Chapter18Test.INLINE_MEMORY, + "int[] !a = new int[7]; a[arg]=arg+3; return a[0]+1;", + """ + int[] !a = new int[7]; + for( int i=0; i<7; i++ ) a[i]=i+arg; + for( int i=0; i<6; i++ ) a[i+1]+=a[i]; + return a[6]; + """ + }; + + private static long expected(int program, long arg) { + return switch(program) { + case 0 -> arg==0 ? 111249 : 222351+arg; + case 1 -> arg==0 ? 10101325 : 20202322; + case 2 -> (10*(arg+1)+arg*(arg+1)/2)*100+10+arg; + case 3 -> 400944+arg*10001; + case 4 -> arg==0 ? 4 : 1; + case 5 -> 21+7*arg; + default -> throw new AssertionError(); + }; + } + + @Test public void testEmulatedMemory() throws IOException { + for( String target : new String[]{"riscv","arm"} ) + for( int p=0; p<SOURCES.length; p++ ) { + CodeGen code = new CodeGen(SOURCES[p]).driver(target,"SystemV",null); + RegAllocTestSupport.checkRegisters(code); + int entry=0; + for( var n : code._start._outputs ) + if( n instanceof FunNode fun && "main".equals(fun._name) ) + entry=code._encoding._opStart[fun._nid]; + for( int arg=0; arg<7; arg++ ) { + byte[] image = new byte[1<<20]; + byte[] bits = code._encoding.bits(); + System.arraycopy(bits,0,image,0,bits.length); + long result; + if( target.equals("riscv") ) { + EvalRisc5 cpu = new EvalRisc5(image,1<<16); + cpu._pc=entry; + cpu.regs[riscv.A0]=arg; + assertEquals(target+" program "+p+" arg "+arg,0,cpu.step(100000)); + result=cpu.regs[riscv.A0]; + } else { + EvalArm64 cpu = new EvalArm64(image,1<<16); + cpu._pc=entry; + cpu.regs[arm.X0]=arg; + assertEquals(target+" program "+p+" arg "+arg,0,cpu.step(100000)); + result=cpu.regs[arm.X0]; + } + assertEquals(target+" program "+p+" arg "+arg,expected(p,arg),result); + } + } + } + + @Test public void testNeverMemory() { + // A synthetic exit must retain both precise slices of a no-exit loop. + // Stop at scheduling: the existing allocator cannot coalesce TOP on + // the synthetic scalar return arm. + for( String target : new String[]{"x86_64_v2","riscv","arm"} ) { + CodeGen code = new CodeGen(""" + struct S { int x; int y; }; + S !a=new S; + if( arg ) while( 1 ) { a.x+=arg; a.y+=a.x; } + return a.x+a.y; + """).driver(CodeGen.Phase.LoopTree); + assertNotNull(code._stop.walk(n -> { + if( n instanceof MemMergeNode mem && + mem.in(0) instanceof CProjNode exit && exit.in(0) instanceof NeverNode ) { + var fields = ((TypeMemPtr)Parser.TYPES.get("S"))._obj._fields; + assertTrue(mem.alias(fields[0]._alias) instanceof MemPhiNode); + assertTrue(mem.alias(fields[1]._alias) instanceof MemPhiNode); + return mem; + } + return null; + })); + code.instSelect(target,"SystemV").GCM().localSched(); + } + } + + @Test public void testNativeMemory() throws IOException { + Files.createDirectories(Path.of("build/objs")); + for( int p=0; p<SOURCES.length; p++ ) { + String file="build/objs/memory"+p; + StringBuilder c = new StringBuilder("extern long long memory(long long);\nint main() {\n"); + for( int arg=0; arg<7; arg++ ) + c.append("if(memory(").append(arg).append(")!=").append(expected(p,arg)).append("LL) return ").append(arg+1).append(";\n"); + c.append("return 0;\n}\n"); + Files.writeString(Path.of(file+".c"),c); + TestC.run("val memory = { int arg -> "+SOURCES[p]+" };", + TestC.CALL_CONVENTION,null,"",file+".c",file,"S","",-1); + } + } +} diff --git a/chapter23/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java b/chapter23/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java index f0830f1b..5b61d3b3 100644 --- a/chapter23/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java +++ b/chapter23/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java @@ -219,6 +219,31 @@ public class RegAllocTestSupport { assertFalse("A fixed definition clobbers even before coloring",uncolored.sameBlockNoClobber(copy)); } + public static void copyForward() throws Exception { + // Forwarding, fixed operand, two-address tie, intervening clobber, + // another user, and a scheduling-only edge. + for( int kind=0; kind<6; kind++ ) { + CodeGen code = graph(); + RegAlloc alloc = new RegAlloc(code); + Op def = new Op(A,null,null,false,code._start); + alloc.newLRG(def)._reg=0; + SplitNode copy = code._mach.split("test",(byte)0,null); + copy.setDef(0,code._start); copy.setDef(1,def); + alloc.newLRG(copy)._reg=1; + if( kind==3 ) new Op(null,null,A,false,code._start); + int twoAddress = kind==2 ? 1 : 0; + RegMask mask = kind==1 ? B : kind==5 ? null : new RegMask(3L); + Op use = new Op(null,mask,null,false,code._start,copy) { + @Override public int twoAddress() { return twoAddress; } + }; + if( kind==4 ) new Op(null,B,null,false,code._start,copy); + var post = RegAlloc.class.getDeclaredMethod("postColor"); + post.setAccessible(true); post.invoke(alloc); + assertSame("copy case "+kind,kind==0 ? def : copy,use.in(1)); + assertEquals(kind==0 ? 0 : 1,alloc._spills); + } + } + public static void coalescing() { // Merge, incompatible masks, interference, capacity, and adjacency remapping. for( int kind=0; kind<5; kind++ ) { diff --git a/chapter23/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/chapter23/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java index 911acc5c..1c116162 100644 --- a/chapter23/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java +++ b/chapter23/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java @@ -184,7 +184,7 @@ public class Evaluator { Object[] body=null; int num; if( type.isAry() ) { - long sz = (Long)val(alloc.in(1)); + long sz = (Long)val(alloc.size()); long n = offToIdx(sz, type); if( n < 0 ) throw new NegativeArraySizeException(""+n); @@ -198,21 +198,17 @@ public class Evaluator { assert elem instanceof TypeMemPtr; } // Length value - body[0] = vall(alloc.in(2+2)); + body[0] = n; } else { body = new Object[num = type._fields.length]; for( int i=0; i<num; i++ ) body[i] = switch( alloc._ptr._obj._fields[i]._t ) { case TypeInteger ti -> 0L; - case TypeFloat tf -> 0; + case TypeFloat tf -> 0D; default -> null; }; } - Object[] mems = new Object[type._fields.length+2]; - // mems[0] is control - mems[1] = new Obj(type,body); // the ref - // mems[2+...] are memory aliases - return mems; + return new Object[]{new Obj(type,body),null}; } private Object load(LoadNode load) { diff --git a/chapter24/README.md b/chapter24/README.md index a1fb6ded..59a893d9 100644 --- a/chapter24/README.md +++ b/chapter24/README.md @@ -5,10 +5,12 @@ where `score` in the middle does not have to be repeated. The main compiler change is Interprocedural Sparse Conditional Constant Propagation (SCCP): an optimistic type solve which also discovers call-graph -edges. This snapshot still uses the earlier parser/type and memory machinery; -Chapter 25 currently contains the larger incomplete-types SSA redesign and -separate compilation. Independent correctness fixes will move backward first; -see the [backport review queue](../docs/chapter-backports.md). +edges. Memory follows Chapter 10b's lazy partitioning: BulkMemPhi represents +whole memory, MemPhi merges precise aliases, and allocations consume and produce +partial memory for their instance fields. During SCCP, memory Phis ignore +unreachable inputs and allocation results stay high until control reaches them. +Chapter 25 retains the larger incomplete-types SSA redesign, private constructor +memory, and separate compilation; see the [backport review queue](../docs/chapter-backports.md). You can also read [this chapter](https://github.com/SeaOfNodes/Simple/tree/linear-chapter24) 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-chapter23...linear-chapter24) it to the previous chapter. @@ -296,19 +298,25 @@ also covers a backedge-only conflict where the first attempt inserts no copy: that attempt must still consume the one permitted delay. A regression checks both the cheaper first attempt and the mandatory fallback. +Chapter 21's adjacent-copy forwarding also carries forward: after coloring, +a sole use in the next instruction can read the source register when its +operand mask permits it and no two-address tie is broken. The table below +includes this cleanup and the lazy memory forward port. Cohort 22 now also includes the two zero-move C return +ABI checks added since the earlier audit (26 entries instead of 24). + Run `make spill-stats`. All rows use **Chapter 24's compiler**, optimizer seed 123, and the same source/target combinations as the earlier cohort tables. These Windows results combine x86 SystemV/Win64 and RISC-V/ARM SystemV, -remeasured on 2026-09-28 after the function-pointer lifetime backport. +remeasured after the lazy memory forward port. -| Program cohort | Compilations | Split/move count | Loop-weighted count | +| Program cohort | Compilations | Retained moves | Loop-weighted moves | |---|---:|---:|---:| -| Chapter 20 | 39 | 323 | 442 | -| Chapter 21 | 52 | 436 | 975 | -| Chapter 22 | 24 | 67 | 67 | -| Chapter 23 | 30 | 84 | 231 | -| Chapter 24 | 67 | 441 | 1,400 | -| **Total** | **212** | **1,351** | **3,115** | +| Chapter 20 | 39 | 320 | 439 | +| Chapter 21 | 52 | 420 | 952 | +| Chapter 22 | 26 | 67 | 67 | +| Chapter 23 | 30 | 78 | 225 | +| Chapter 24 | 67 | 422 | 1,325 | +| **Total** | **214** | **1,307** | **3,008** | `_spills` counts retained SplitNodes, including register moves. `_spillScaled` weights those moves by `8^loopDepth`; it is a cost estimate, not measured memory diff --git a/chapter24/src/main/java/com/seaofnodes/simple/Parser.java b/chapter24/src/main/java/com/seaofnodes/simple/Parser.java index 14af367d..432ec137 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/Parser.java @@ -138,7 +138,7 @@ public class Parser { _scope.define(ScopeNode.ARG0, TypeInteger.BOT, false, null, _lexer); ctrl(XCTRL); - _scope.mem(new MemMergeNode(false)); + _scope.mem(con(TypeMem.BOT)); // Parse the sys import _lexer = new Lexer(sys.SYS); @@ -222,7 +222,7 @@ public class Parser { // Build a multi-exit return point for all function returns RegionNode r = new RegionNode( null,null,null).init(); assert r.inProgress(); - PhiNode rmem = new PhiNode(ScopeNode.MEM0,TypeMem.BOT,r,null).init(); + PhiNode rmem = new BulkMemPhiNode(ScopeNode.MEM0,r,null).init(); PhiNode rrez = new PhiNode(ScopeNode.ARG0,Type.BOTTOM,r,null).init(); ReturnNode ret = new ReturnNode(r, rmem, rrez, rpc, fun).init(); fun.setRet(ret); @@ -235,11 +235,8 @@ public class Parser { // external calls. _scope.push(new Kind.Func()); ctrl(fun); // Scope control from function - // Private mem alias tracking per function - MemMergeNode mem = new MemMergeNode(true); - mem.addDef(null); // Alias#0 - mem.addDef(new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,con(TypeMem.BOT)).peephole()); // All aliases - _scope.mem(mem); + // Unknown callers supply whole memory; field aliases are discovered lazily. + _scope.mem(new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,con(TypeMem.BOT)).peephole()); // All args, "as-if" called externally for( int i=0; i<ids.length; i++ ) { Type t = sig.arg(i); @@ -254,7 +251,7 @@ public class Parser { // Last expression is the return except for the top-level main if( ctrl()._type==Type.CONTROL ) if( fun.sig() != _code._main ) - fun.addReturn(ctrl(), _scope.mem().merge(), last); + fun.addReturn(ctrl(), _scope.mem(), last); else { fun.setDef(1,XCTRL); // Kill default main _code.addAll(fun._outputs); @@ -634,9 +631,8 @@ public class Parser { */ private Node parseReturn() { var expr = require(parseAsgn(), ";"); - // Need default memory, since it can be lazy, need to force - // a non-lazy Phi - _fun.addReturn(ctrl(), _scope.mem().merge(), expr); + // Force the lazy whole-memory Phi before closing this exit. + _fun.addReturn(ctrl(), _scope.mem(), expr); ctrl(XCTRL); // Kill control return expr; } @@ -1438,31 +1434,31 @@ public class Parser { Field[] fs = obj._fields; if( fs==null ) throw error("Unknown struct type '" + obj._name + "'"); - int len = fs.length; - Node[] ns = new Node[2+len]; - ns[0] = ctrl(); // Control in slot 0 - // Total allocated length in bytes - ns[1] = size; - // Memory aliases for every field - for( int i = 0; i < len; i++ ) - if( !fs[i]._one ) - ns[2+i] = memAlias(fs[i]._alias); - Node nnn = new NewNode(TypeMemPtr.make(obj), ns).peephole().keep(); - for( int i = 0; i < len; i++ ) - if( !fs[i]._one ) - memAlias(fs[i]._alias, new ProjNode(nnn,i+2,memName(fs[i]._alias)).peephole()); - Node ptr = new ProjNode(nnn.unkeep(),1,obj._name).peephole().keep(); - - // Initial nonzero values for every field - for( int i = 0; i < len; i++ ) { - Node val = init.get( i + idx ); + Node prior = mem().keep(); + MemMergeNode input = new MemMergeNode((Node)null); + for( Field f : fs ) if( !f._one ) input.alias(f._alias,prior); + // Allocation zeroes its fields; nonzero initializers become explicit Stores. + Node[] ns = new Node[3]; + ns[0] = ctrl(); + ns[1] = input.peephole(); + ns[2] = size; + Node nnn = new NewNode(TypeMemPtr.make(obj),ns).peephole().keep(); + Node ptr = new ProjNode(nnn,0,obj._name).peephole().keep(); + Node out = new ProjNode(nnn,1,"$mem").peephole().keep(); + MemMergeNode after = new MemMergeNode(prior); + for( Field f : fs ) if( !f._one ) after.alias(f._alias,out); + mem(after.peephole()); + out.unkeep(); + nnn.unkeep(); + prior.unkeep(); + for( int i=0; i<fs.length; i++ ) { + Node val = init.get(idx+i); if( !fs[i]._one && val._type != val._type.makeZero() ) { - Node mem = memAlias(fs[i]._alias); - Node st = new StoreNode(loc(),fs[i]._fname,fs[i]._alias,fs[i]._t,mem,ptr,off(obj,fs[i]._fname),val,true).peephole(); - memAlias(fs[i]._alias,st); + Field f = fs[i]; + Node st = new StoreNode(loc(),f._fname,f._alias,f._t,mem(),ptr,off(obj,f._fname),val,true).peephole(); + mem(new MemMergeNode(mem(),f._alias,st).peephole()); } } - return ptr.unkeep(); } @@ -1488,13 +1484,18 @@ public class Parser { return con(str); } - // 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.mem(alias ); } - private void memAlias(int alias, Node st) { _scope.mem(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.mem(); } + private void mem(Node n) { _scope.mem(n); } + + private void store(String name, int alias, Type glb, Node ptr, Node off, Node val, boolean init, Node ctrl) { + Node prior = mem().keep(); + Node st = new StoreNode(loc(),name,alias,glb,prior,ptr,off,val,init); + st.setDef(0,ctrl); + st = st.peephole(); + mem(new MemMergeNode(prior,alias,st).peephole()); + prior.unkeep(); + } /** * Parse postfix expression; this can be a field expression, an array @@ -1554,6 +1555,7 @@ public class Parser { if( base.isAry() && tf instanceof TypeConAry con ) tf = con.elem(); + expr.keep(); // Field offset; fixed for structs, computed for arrays Node off = (name.equals("[]") // If field is an array body // Array index math @@ -1566,21 +1568,14 @@ public class Parser { Node val = parseAsgn().keep(); Node lift = liftExpr( val, tf, f._final, false ); - Node st = new StoreNode(loc(), name, f._alias, tf, memAlias(f._alias), expr, off.unkeep(), lift, false); - // Arrays include control, as a proxy for a safety range check. - // Structs don't need this; they only need a NPE check which is - // done via the type system. - if( base.isAry() ) st.setDef(0,ctrl()); - memAlias(f._alias, st.peephole()); + store(name,f._alias,tf,expr.unkeep(),off.unkeep(),lift,false,base.isAry() ? ctrl() : null); return val.unkeep(); // "obj.a = expr" returns the expression while updating memory } - // Keep expr for possible store update - expr.keep(); // Load field Node load = f._one && INITS.containsKey( base._name ) ? INITS.get(base._name).in(base.find(name)) - : new LoadNode(loc(),name, f._alias, tf, memAlias(f._alias), expr, off); + : new LoadNode(loc(),name, f._alias, tf, mem(), expr, off); // Arrays include control, as a proxy for a safety range check // Structs don't need this; they only need a NPE check which is // done via the type system. @@ -1590,13 +1585,8 @@ public class Parser { // Check for assign-update, "ptr.fld += expr" or "ary[idx]++" char ch = _lexer.matchOperAssign(); if( ch!=0 ) { - Node op = opAssign(ch,load, tf ); - Node st = new StoreNode(loc(), name, f._alias, tf, memAlias(f._alias), expr.unkeep(), off.unkeep(), op, false); - // Arrays include control, as a proxy for a safety range check. - // Structs don't need this; they only need a NPE check which is - // done via the type system. - if( base.isAry() ) st.setDef(0,ctrl()); - memAlias(f._alias, peep(st)); + Node op = opAssign(ch,load,tf); + store(name,f._alias,tf,expr.unkeep(),off.unkeep(),op,false,base.isAry() ? ctrl() : null); load = postfix(ch) ? load.unkeep() : op; // And use the original loaded value as the result return load; @@ -1707,7 +1697,7 @@ public class Parser { } // Control & memory after parsing args args.set(0,ctrl().keep()); - args.set(1,_scope.mem().merge().keep()); + args.set(1,_scope.mem().keep()); args.push(fcn); // Function pointer // Unkeep them all for( Node arg : args ) @@ -1727,11 +1717,8 @@ public class Parser { call.peephole(); // Rerun peeps after CallEnd, allows early inlining // Control from CallEnd ctrl(new CProjNode(cend,0,ScopeNode.CTRL).peephole()); - // Memory from CallEnd - MemMergeNode mem = new MemMergeNode(true); - mem.addDef(null); // Alias#0 - mem.addDef(new ProjNode(cend,1,ScopeNode.MEM0).peephole()); - _scope.mem(mem); + // A call may update any alias, so its memory result is the new whole state. + _scope.mem(new ProjNode(cend,1,ScopeNode.MEM0).peephole()); // Call result return new ProjNode(cend,2,"#2").peephole(); } diff --git a/chapter24/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java b/chapter24/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java index b8a8d2b3..1d802cae 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java @@ -50,7 +50,7 @@ public abstract class GlobalCodeMotion { var cons = new ArrayList<Node>(); for( Node con : start._outputs ) if( con!=null && !(con instanceof CFGNode) && - (con.isConst() || con instanceof MachNode mach && mach.outregmap()!=null) ) { + (con.isConst() || (con instanceof MachNode mach && mach.outregmap()!=null)) ) { globals.put(con,true); cons.add(con.keep()); // Preserve original inputs while rewiring users. } @@ -172,16 +172,8 @@ public abstract class GlobalCodeMotion { // Loads need their memory inputs' uses also done if( n instanceof MemOpNode ld && ld._isLoad ) - for( Node memuse : ld.mem()._outputs ) - if( late[memuse._nid]==null && - // New makes new memory, never crushes load memory - !(memuse instanceof NewNode) && - // Load-use directly defines memory - (memuse._type instanceof TypeMem || - // Load-use directly defines memory - memuse instanceof CallNode || - // Load-use indirectly defines memory - (memuse._type instanceof TypeTuple tt && tt._types[ld._alias] instanceof TypeMem)) ) + for( Node memuse : ld.antiDeps() ) + if( late[memuse._nid]==null ) continue outer; // All uses done, schedule @@ -193,9 +185,7 @@ public abstract class GlobalCodeMotion { for( Node def : n._inputs ) { if( def==null ) continue; if( late[def._nid]==null ) work.push(def); - for( Node out : def._outputs ) - if( out instanceof MemOpNode ld && ld._isLoad && late[ld._nid]==null ) - work.push(ld); + wakeLoads(def,late,work,new BitSet()); } if( n instanceof LoopNode loop ) for( Node phi : loop._outputs ) @@ -255,23 +245,32 @@ public abstract class GlobalCodeMotion { best instanceof IfNode; } + // An allocation may consume a load's memory through a partial aggregate. + private static void wakeLoads(Node def, CFGNode[] late, WorkList<Node> work, BitSet visit) { + if( visit.get(def._nid) ) return; + visit.set(def._nid); + for( Node out : def._outputs ) + if( out instanceof MemOpNode ld && ld._isLoad && late[ld._nid]==null ) work.push(ld); + if( def instanceof MemMergeNode ) + for( int i=1; i<def.nIns(); i++ ) + if( def.in(i)!=null ) wakeLoads(def.in(i),late,work,visit); + } + private static CFGNode find_anti_dep(CFGNode lca, MemOpNode load, CFGNode early, CFGNode[] late, int[] anti) { // We could skip final-field loads here. // Walk LCA->early, flagging Load's block location choices for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() ) anti[cfg._nid] = load._nid; // Walk load->mem uses, looking for Stores causing an anti-dep - for( Node mem : load.mem()._outputs ) { + for( Node mem : load.antiDeps() ) { switch( mem ) { case MemOpNode st: - if( !st._isLoad && load._alias == st._alias ) { - assert late[mem._nid] != null; - lca = anti_dep(load,late[mem._nid],lca,st,anti); - } - break; // Loads do not cause anti-deps on other loads - case CallNode call: - assert late[call._nid]!=null; - lca = anti_dep(load,late[call._nid],lca,call,anti); + assert late[st._nid]!=null; + lca = anti_dep(load,late[st._nid],lca,st,anti); + break; + case CallNode st: + assert late[st._nid]!=null; + lca = anti_dep(load,late[st._nid],lca,st,anti); break; case PhiNode phi: // Repeat anti-dep for matching Phi inputs. @@ -280,10 +279,10 @@ public abstract class GlobalCodeMotion { if( phi.in(i)==load.mem() ) lca = anti_dep(load,phi.region().cfg(i),lca,null,anti); break; - case NewNode st: break; - case ReturnNode ret: break; // Load must already be ahead of Return - case MemMergeNode ret: break; // Mem uses now on ScopeMin - case NeverNode never: break; + case NewNode st: + assert late[st._nid]!=null; + lca = anti_dep(load,late[st._nid],lca,st,anti); + break; default: throw Utils.TODO(); } } @@ -295,7 +294,7 @@ public abstract class GlobalCodeMotion { // Stores are already placed. Constrain the load at that block only. if( anti[stblk._nid]==load._nid ) { lca = stblk.domLCA(lca,null); - if( lca==stblk && st!=null && st._inputs.find(load) == -1 ) + if( lca==stblk && st!=null && !(st instanceof CallNode) && st._inputs.find(load) == -1 ) st.addDef(load); // Same-block load must precede the store. } return lca; diff --git a/chapter24/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java b/chapter24/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java index 4bf4ac35..b2b861e2 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java @@ -618,8 +618,8 @@ public class RegAlloc { if( defreg != usereg && splitBypass(bb,j,n,defreg) ) usereg = lrg(n.in(1))._reg; - // Split has same reg? Useless! Can remove it! - if( defreg == usereg ) { + // Same register, or the next instruction can use the source directly. + if( defreg == usereg || splitForward(bb,j,n,usereg) ) { n.removeSplit(); j--; continue; @@ -632,6 +632,18 @@ public class RegAlloc { } } + // A sole, adjacent use can read the source register directly. Nothing can + // clobber it in between; only the operand mask and two-address tie remain. + private boolean splitForward( CFGNode bb, int j, Node split, int reg ) { + if( split.nOuts()!=1 || j+1==bb.nOuts() ) return false; + Node use = split.out(0); + if( use!=bb.out(j+1) || use instanceof PhiNode || use instanceof CFGNode || + !(use instanceof MachNode mach) ) return false; + int i = use._inputs.find(split); + RegMask mask = mach.regmap(i); + return mask!=null && mask.test(reg) && mach.twoAddress()!=i; + } + private boolean splitBypass( CFGNode bb, int j, Node lo, int defreg ) { // Attempt to bypass split Node hi = lo.in(1); diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java new file mode 100644 index 00000000..7d4f1fb8 --- /dev/null +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java @@ -0,0 +1,240 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.codegen.CodeGen; +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); } + + public BulkMemPhiNode(BulkMemPhiNode phi) { super(phi); _aliases = phi._aliases; } + + @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,CodeGen.CODE.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 mmm.in(1)!=null && missingAlias(mmm)==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 ParmNode parm -> 0; + case ConstantNode con -> 0; + case MemMergeNode mmm -> missingAlias(mmm); + 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. + addDepForwards(use); + return switch(use) { + case ScopeNode scope -> 0; + case MemMergeNode mmm -> missingAlias(mmm); + case BulkMemPhiNode bulk -> missingAlias(bulk); + case MemOpNode mem -> unsplit(mem._alias); + case MemPhiNode phi -> unsplit(phi._alias); + default -> { + assert use instanceof ReturnNode || use instanceof CallNode || use instanceof ParmNode + : "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; + } + + // Every explicit slot requests that alias, including a partial allocation + // input whose slot still points to this bulk Phi. + private int missingAlias(MemMergeNode mmm) { + for( int alias=2; alias<mmm.nIns(); alias++ ) + if( mmm.in(alias)!=null && + mmm.alias(alias)!=mmm.in(1) && + !_aliases.get(alias) ) + 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++ ) + CodeGen.CODE.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; + CodeGen.CODE.add(bulk); + CodeGen.CODE.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,Type.BOTTOM)); + for( int i=1; i<nIns(); i++ ) + mphi.addDef(CodeGen.CODE.add(preciseInput(in(i),alias))); + return CodeGen.CODE.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/chapter24/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/LoadNode.java index 5f28a39b..12095868 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -1,7 +1,7 @@ package com.seaofnodes.simple.node; -import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.type.*; import com.seaofnodes.simple.util.Utils; import java.util.BitSet; @@ -24,6 +24,25 @@ public class LoadNode extends MemOpNode { super(loc, name, alias, true, glb, mem, ptr, off); } + @Override boolean canDrop(MemOpNode other, Node dep) { + return super.canDrop(other,dep) && !clobbered(dep); + } + + // Check only immediate memory users. Writers clobber memory; Phis and + // aggregates might lead to a clobber, so stop rather than search further. + private boolean clobbered(Node dep) { + Node mem = mem(); + dep.addDepForwards(mem); + for( Node use : mem._outputs ) { + if( use==null ) continue; + dep.addDepForwards(use); + if( use instanceof StoreNode || use instanceof PhiNode || + use instanceof MemMergeNode || + use instanceof NewNode || use instanceof CallNode ) return true; + } + return false; + } + // Debugger label @Override public String label() { return "ld_"+mlabel(); } @Override @@ -52,7 +71,7 @@ public class LoadNode extends MemOpNode { if( t instanceof TypeConAry ary ) t = ary.elem(); // TODO: if offset is known, can peek the constant // Lift from declared type and memory input - t = t.join(mem._t); + t = t.join(MemMergeNode.contents(mem(),_alias,this)); if( _declaredType.isFinal() ) t = t.makeRO(); // Deep final applied // Pinch between declared type @@ -62,35 +81,19 @@ public class LoadNode extends MemOpNode { @Override public Node idealize() { - Node ptr = ptr(); - Node mem = mem(); - - if( mem instanceof CastNode cast ) { + if( mem() instanceof CastNode cast ) { setDef(1,cast.in(1)); return this; } - - // Simple Load-after-Store on same address. - if( mem instanceof StoreNode st && - ptr == st.ptr() && off() == st.off() ) { // Must check same object - assert _name.equals(st._name); // Equiv class aliasing is perfect - return extend(st.val()); - } - - // Simple load-after-MemMerge to a known alias can bypass. Happens when inlining. - if( mem instanceof MemMergeNode mem2 ) { - Node memA = mem2.alias(_alias); - for( Node ld : memA._outputs ) - if( ld instanceof LoadNode ) - CodeGen.CODE.add(ld); - setDef(1,memA); + if( mem() instanceof MemMergeNode merge ) { + setDef(1,CodeGen.CODE.add(merge.alias(_alias))); return this; } + Node ptr = ptr(); + Node mem = mem(); - // Simple Load-after-New on same address. - if( mem instanceof ProjNode p && p.in(0) instanceof NewNode nnn && - ptr == nnn.proj(1) ) // Must check same object - return zero(nnn); // Load zero from new + Node win = find(mem,ptr,null,null); + if( win!=null ) return folded(win); // Uplift control to a prior dominating load. for( Node memuse : mem._outputs ) @@ -99,50 +102,7 @@ public class LoadNode extends MemOpNode { cfg0()!=null && cfg0().domLCA(ld.cfg0(),this) == ld.cfg0() ) // Higher control means load is legal earlier return ld; - // Load-after-Store on same address, but bypassing provably unrelated - // stores. This is a more complex superset of the above two peeps. - // "Provably unrelated" is really weak. - if( ptr instanceof ReadOnlyNode ro ) - ptr = ro.in(1); - outer: - while( true ) { - switch( mem ) { - case StoreNode st: - if( ptr == addDep(st.ptr()) && off() == st.off() ) - return extend(castRO(st.val())); // Proved equal - // Can we prove unequal? Offsets do not overlap? - if( !off()._type.join(st.off()._type).isHigh() && // Offsets overlap - !neverAlias(ptr,st.ptr()) ) // And might alias - break outer; // Cannot tell, stop trying - // Pointers cannot overlap - mem = st.mem(); // Proved never equal - break; - case PhiNode phi: - // Assume related - addDep(phi); - break outer; - case ConstantNode top: break outer; // Assume shortly dead - case ProjNode mproj: // Memory projection - switch( mproj.in(0) ) { - case NewNode nnn1: - if( ptr instanceof ProjNode pproj && pproj.in(0) == mproj.in(0) ) - return zero(nnn1); - if( !(ptr instanceof ProjNode pproj && pproj.in(0) instanceof NewNode) ) - break outer; // Cannot tell, ptr not related to New - mem = nnn1.in(nnn1.findAlias(_alias));// Bypass unrelated New - break; - case StartNode start: break outer; - case CallEndNode cend: break outer; // TODO: Bypass no-alias call - default: throw Utils.TODO(); - } - break; - case CastNode cast: mem = cast.in(1); break; - case MemMergeNode merge: mem = merge.alias(_alias); break; - - default: - throw Utils.TODO(); - } - } + if( ptr instanceof ReadOnlyNode ro ) ptr=ro.in(1); // 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 @@ -151,19 +111,27 @@ 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 && + // Array access control must not be moved across the merge. + in(0)==null && // Offset can be hoisted off() instanceof ConstantNode && // Pointer can be hoisted hoistPtr(ptr,memphi) ) { - // 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 = ld(1); - Node ld2 = ld(2); - return new PhiNode(_name,_declaredType,memphi.region(),ld1,ld2); + // Returning to this memory means no change only if the pointer + // is also loop invariant. A pointer Phi may select another object. + Node stop = memphi.region() instanceof LoopNode && + !(ptr instanceof PhiNode p && p.region()==memphi.region()) ? memphi : null; + if( find(memphi.in(2),ptr,stop,new BitSet())!=null || + (!(memphi.region() instanceof LoopNode) && find(memphi.in(1),ptr,null,new BitSet())!=null) ) { + PhiNode phi = new PhiNode(_name,_declaredType,memphi.region()).keep(); + phi.setType(_type); // Backedge searches may return this value Phi. + for( int i=1; i<memphi.nIns(); i++ ) { + Node p = ptr instanceof PhiNode pp && pp.region()==memphi.region() ? pp.in(i) : ptr; + phi.addDef(load(memphi.in(i),p,stop,phi)); + } + return CodeGen.CODE.add(phi.unkeep()); } } @@ -178,9 +146,67 @@ public class LoadNode extends MemOpNode { return castRO(new ConstantNode(zero).peephole()); } - private Node ld( int idx ) { - Node mem = mem(), ptr = ptr(); - return new LoadNode(_loc,_name,_alias,_declaredType,mem.in(idx),ptr instanceof PhiNode && ptr.in(0)==mem.in(0) ? ptr.in(idx) : ptr, off()).peephole(); + // Search only; no new nodes or rewiring. Return a Store/New that folds, + // the unchanged loop memory, or a merge whose every arm folds. Null fails. + // Register dependencies so a later pointer/offset rewrite retries the query. + private Node find(Node mem, Node ptr, Node stop, BitSet visit) { + if( ptr instanceof ReadOnlyNode ro ) ptr=ro.in(1); + while( mem!=null ) { + addDep(mem); + if( mem==stop ) return mem; + switch( mem ) { + case MemMergeNode merge: mem=merge.alias(_alias); break; + case CastNode cast: mem=cast.in(1); break; + case StoreNode st: + addDep(st.ptr()); + if( _alias==st._alias && ptr==st.ptr() && off()==st.off() ) return st; + if( _alias==st._alias && !neverAlias(ptr,st.ptr()) && + !addDep(off())._type.join(addDep(st.off())._type).isHigh() ) return null; + mem=st.mem(); + break; + case ProjNode proj: + if( !(proj.in(0) instanceof NewNode nnn) ) return null; + if( ptr instanceof ProjNode p && p.in(0)==nnn ) return nnn; + if( !(ptr instanceof ProjNode p && p.in(0) instanceof NewNode) ) return null; + mem=nnn.mem(); // Distinct allocations cannot overlap. + break; + case MemPhiNode phi: + addDep(phi.region()); + // Only the original loop closes a successful cycle. Another + // loop or an unfinished merge needs a separate proof. + if( visit==null || !(phi.region() instanceof RegionNode r) || r instanceof LoopNode || + r.inProgress() || phi.inProgress() || r._type!=Type.CONTROL ) return null; + if( visit.get(phi._nid) ) return phi; // Already proved every arm. + for( int i=1; i<phi.nIns(); i++ ) + if( find(phi.in(i),ptr,stop,visit)==null ) return null; + visit.set(phi._nid); + return phi; + default: return null; + } + } + return null; + } + + private Node folded(Node win) { + return win instanceof StoreNode st ? extend(castRO(st.val())) : zero((NewNode)win); + } + + // Materialize only after the profitability search succeeds. The value Phi + // stands for a load which comes back around the loop with unchanged memory. + private Node load(Node mem, Node ptr, Node stop, PhiNode value) { + Node win=find(mem,ptr,stop,new BitSet()); + if( win!=null ) { + if( win==stop ) return value; + if( win instanceof MemPhiNode mp ) { + PhiNode phi=new PhiNode(_name,_declaredType,mp.region()).keep(); + for( int i=1; i<mp.nIns(); i++ ) phi.addDef(load(mp.in(i),ptr,stop,value)); + return phi.unkeep().peephole(); + } + return folded(win).peephole(); + } + LoadNode ld=new LoadNode(_loc,_name,_alias,_declaredType,mem,ptr,off()); + ld.setDef(0,in(0)); + return ld.peephole(); } private static boolean neverAlias( Node ptr1, Node ptr2 ) { @@ -209,16 +235,6 @@ public class LoadNode extends MemOpNode { return false; } - // Profitable if we find a matching Store on this Phi arm. - private boolean profit(PhiNode phi, int idx) { - Node px = phi.in(idx); - if( px==null ) return false; - if( px._type instanceof TypeMem mem && mem._t.isHighOrConst() ) return true; - if( px instanceof StoreNode st1 && ptr()==addDep(st1.ptr() )&& off()==st1.off() ) return true; - addDep(px); - return false; - } - // Read-Only is a deep property, and cannot be cast-away private Node castRO(Node rez) { if( ptr()._type.isFinal() && !rez._type.isFinal() ) @@ -240,4 +256,5 @@ public class LoadNode extends MemOpNode { Node shl = new ShlNode(null,val,shf.keep()).peephole(); return new SarNode(null,shl,shf.unkeep()); } + } diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/LoopNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/LoopNode.java index 6c69a56e..db52c156 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/LoopNode.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/LoopNode.java @@ -57,11 +57,12 @@ public class LoopNode extends RegionNode { // Now fold control into the exit. Might have 1 valid exit, or an // XCtrl or a bunch of prior NeverNode exits. Node top = new ConstantNode(Type.TOP).peephole(); - Node memout = new MemMergeNode(false); - memout.addDef(f); // placeholder for control + MemMergeNode memout = new MemMergeNode(new ConstantNode(TypeMem.BOT).peephole()); + memout.setDef(0,f); for( Node u : _outputs ) - if( u instanceof PhiNode phi && phi._type.isa(TypeMem.BOT) ) - memout.addDef(phi); + if( u instanceof BulkMemPhiNode ) memout.setDef(1,u); + else if( u instanceof MemPhiNode phi ) memout.alias(phi._alias,phi); + memout.init(); Node ctrl = ret.ctrl(), mem = ret.mem(), expr = ret.expr(); if( ctrl!=null && ctrl._type != Type.XCONTROL ) { @@ -72,7 +73,7 @@ public class LoopNode extends RegionNode { // Nope, insert an aligned exit layer RegionNode r = new RegionNode(_loc,null,ctrl).init(); ctrl = r; r._ltree = stop._ltree; - mem = new PhiNode(r,mem ).init(); + mem = new BulkMemPhiNode(ScopeNode.MEM0,r,mem).init(); expr = new PhiNode(r,expr).init(); } // Append new Never exit diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java index 2a399b97..2894556c 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java @@ -5,58 +5,62 @@ import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.type.*; import java.util.*; -/** - * Memory Merge - a merge of many aliases into a "fat memory". All aliases - * are here, but most will be lazy - take the default fat memory. +/** A default slice and explicit alias overrides. A null default means partial + * memory: absent aliases are not covered, rather than having unknown contents. */ public class MemMergeNode extends Node { + public MemMergeNode(MemMergeNode mem) { super(mem); } - /* - * In-Progress means this is being used by the Parser to track memory - * aliases. No optimizations are allowed. When no longer "in progress" - * normal peeps work. - */ - public final boolean _inProgress; - - public MemMergeNode( boolean inProgress) { _type = TypeMem.BOT; _inProgress = inProgress; } - public MemMergeNode(MemMergeNode mem) { super(mem); _inProgress = false; } - - - // If being used by a Scope, this is "in progress" from the Parser. - // Otherwise, it's a memory merge - boolean inProgress() { return _inProgress; } + 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); + n = n==null ? in(1) : n; + assert n!=null : "Alias not covered: "+alias; + return n; + } - @Override public String label() { return "ALLMEM"; } - @Override public boolean isMem() { return true; } + public void alias(int alias, Node mem) { + assert alias > 1; + while( nIns()<=alias ) addDef(null); + setDef(alias,mem); + } - @Override - public StringBuilder _print1(StringBuilder sb, BitSet visited) { - sb.append(_inProgress ? "Merge[" : "MEM[ "); - for( int j=2; j<nIns(); j++ ) { - sb.append(j); - sb.append(":"); - Node n = in(j); - while( n instanceof ScopeNode loop ) { - sb.append("Lazy_"); - n = loop.mem().alias(j); - } - if( n==null ) sb.append("___ "); - else n._print0(sb, visited).append(" "); + // Scalar contents of one alias. New zeroes the allocated fields; + // join it with the incoming contents for all previously allocated objects. + // Phis use their cached types, so this query does not recurse around loops. + static Type contents(Node mem, int alias, Node dep) { + dep.addDep(mem); + if( mem instanceof CastNode cast ) return contents(cast.in(1),alias,dep); + if( mem instanceof MemMergeNode merge ) + return contents(merge.alias(alias),alias,dep); + if( mem instanceof ProjNode proj && proj.in(0) instanceof NewNode nnn ) { + assert proj._idx==1 && nnn.field(alias)!=null; + dep.addDep(nnn); + if( dep.addDep(nnn.in(0))._type.isHigh() ) return Type.TOP; + return contents(nnn.mem(),alias,dep).meet(nnn.field(alias)._t.makeZero()); } - sb.setLength(sb.length()-1); - return sb.append("]"); + // Function parameters and call results may contain arbitrary heap values. + // Their bulk memory types do not imply empty or zero-filled storage. + if( mem._type==Type.TOP || mem._type==TypeMem.TOP ) return Type.TOP; + if( mem._type instanceof TypeMem mt && mt._alias==alias ) return mt._t; + return Type.BOTTOM; } - // Make a memory merge: no longer a Scope really, tracking memory state but - // not related to the parser in any way. - public Node merge() { - // Force default memory to not be lazy - MemMergeNode merge = new MemMergeNode(false); - for( Node n : _inputs ) - merge.addDef(n); + @Override public String label() { return "MemMerge"; } + @Override public boolean isMem() { return true; } + @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { + sb.append("MEM["); for( int i=1; i<nIns(); i++ ) - merge._mem(i,null); - return merge.peephole(); + if( in(i)!=null ) { + sb.append(i==1 ? "default:" : i+":"); + in(i)._print0(sb,visited).append(" "); + } + return sb.append("]"); } @@ -68,118 +72,27 @@ public class MemMergeNode extends Node { } @Override public Node idealize() { - if( inProgress() ) return null; - - // Fold defaults into the default boolean progress=false, allDefault=true; for( int i=2; i<nIns(); i++ ) { - if( in(i) instanceof CastNode cast ) - { setDef(i,cast.in(1)); progress=true; } - if( in(1) == in(i) ) { setDef(i,null); progress=true; } - else { allDefault=false; } + if( in(i) instanceof CastNode cast ) { setDef(i,cast.in(1)); progress=true; } + if( in(i)!=null && in(i)==in(1) ) { setDef(i,null); progress=true; } + if( in(i) instanceof MemMergeNode mem ) { + setDef(i,CodeGen.CODE.add(mem.alias(i))); + progress=true; + } + if( in(i)!=null ) allDefault=false; } + if( allDefault && in(1)!=null ) return in(1); - // If not merging any memory (all memory is just the default) - if( allDefault ) - return in(1); // Become default memory - - // Collapse stacked all-mem if( in(1) instanceof MemMergeNode mem ) { - // Goal is to swap my default mem with mem's default mem - for( int i=2; i<mem.nIns(); i++ ) { - if( mem.in(i) != null ) { - // deeper default mem has a non-default - if( i>=nIns() || in(i)==null ) - setDefX(i,mem.in(i)); - } - } + for( int i=2; i<mem.nIns(); i++ ) + if( mem.in(i)!=null && (i>=nIns() || in(i)==null) ) + alias(i,mem.in(i)); + CodeGen.CODE.add(mem.in(1)); setDef(1,mem.in(1)); return this; } - + assert BulkMemPhiNode.checkMerge(this); return progress ? this : null; } - - - public Node in( Var v ) { return in(v._idx); } - - public Node alias( int alias ) { - return alias < nIns() && in(alias)!=null ? in(alias) : in(1); - } - - Node alias( int alias, Node st ) { - while( alias >= nIns() ) addDef(null); - return setDef(alias,st); - } - - // Read or update from memory. - // A shared implementation allows us to create lazy phis both during - // lookups and updates; the lazy phi creation is part of chapter 8. - Node _mem( int alias, Node st ) { - // Memory projections are made lazily; if one does not exist - // then it must be START.proj(1) - Node old = alias(alias); - if( old instanceof ScopeNode loop ) { - MemMergeNode loopmem = loop.mem(); - Node memdef = loopmem.alias(alias); - // Lazy phi! - old = memdef instanceof PhiNode phi && loop.ctrl()==phi.region() - // Loop already has a real Phi, use it - ? memdef - // 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. - : loopmem.alias(alias, new PhiNode(Parser.memName(alias), TypeMem.make(alias,Type.BOTTOM),loop.ctrl(),loopmem._mem(alias,null),null).peephole() ); - alias(alias,old); - } - // Memory projections are made lazily; expand as needed - return st==null ? old : alias(alias,st); // Not lazy, so this is the answer - } - - - void _merge( MemMergeNode that, RegionNode r) { - int len = Math.max(nIns(),that.nIns()); - for( int i = 1; i < len; i++) - if( alias(i) != that.alias(i) ) { // No need for redundant Phis - // If we are in lazy phi mode we need to a lookup - // by alias as it will trigger a phi creation - Node lhs = this._mem(i,null); - Node rhs = that._mem(i,null); - alias(i, new PhiNode(Parser.memName(i), lhs._type.meet(rhs._type).glb(true), r, lhs, rhs).peephole()); - } - } - - // Fill in the backedge of any inserted Phis - void _endLoopMem( ScopeNode scope, MemMergeNode back, MemMergeNode exit ) { - Node exit_def = exit.alias(1); - for( int i=1; i<nIns(); i++ ) { - if( in(i) instanceof PhiNode phi && phi.region()==scope.ctrl() ) { - assert phi.in(2)==null; - phi.setDef(2,back.alias(i)==scope ? phi : back.alias(i)); // Fill backedge - } - if( exit_def == scope ) // Replace a lazy-phi on the exit path also - exit.alias(i,in(i)); - } - } - - // Now one-time do a useless-phi removal - void _useless( ) { - for( int i=2; i<nIns(); i++ ) { - if( in(i) instanceof PhiNode phi ) { - // Do an eager useless-phi removal - Node in = phi.peephole(); - CodeGen.CODE.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 - } - } - } - } - - @Override public boolean eq( Node n ) { - return this==n || !_inProgress; - } } diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/MemOpNode.java index ead82322..65021110 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/MemOpNode.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/MemOpNode.java @@ -6,6 +6,7 @@ import com.seaofnodes.simple.type.TypeMemPtr; import com.seaofnodes.simple.util.Utils; import java.lang.StringBuilder; import java.util.BitSet; +import java.util.ArrayList; /** * Convenience common base for Load and Store. @@ -78,6 +79,8 @@ public abstract class MemOpNode extends Node { static String mlabel(String name) { return "[]".equals(name) ? "ary" : ("#".equals(name) ? "len" : name); } String mlabel() { return mlabel(_name); } + @Override public boolean isMem() { return !_isLoad; } + public Node mem() { return in(1); } public Node ptr() { return in(2); } public Node off() { return in(3); } @@ -86,6 +89,13 @@ public abstract class MemOpNode extends Node { public StringBuilder _printMach( StringBuilder sb, BitSet visited ) { throw Utils.TODO(); } public int log_size() { return _declaredType.log_size(); } + // Extra conditions for factoring matching memory operations through a Phi. + // The caller has already checked the opcode, input shape and control. + boolean canDrop(MemOpNode other, Node dep) { + dep.addDep(ptr()); + return _alias==other._alias && _declaredType==other._declaredType && err()==null; + } + @Override public boolean eq(Node n) { MemOpNode mem = (MemOpNode)n; // Invariant @@ -106,4 +116,29 @@ public abstract class MemOpNode extends Node { return null; return Parser.error( "Might be null accessing '" + _name + "'",_loc); } + // Memory writers which must follow this read. A packaging node is not a + // write, but can hide New's input, so follow this alias through aggregates. + public ArrayList<Node> antiDeps() { + ArrayList<Node> deps = new ArrayList<>(); + antiDeps(mem(),deps,new BitSet()); + return deps; + } + + private void antiDeps(Node mem, ArrayList<Node> deps, BitSet visit) { + if( visit.get(mem._nid) ) return; + visit.set(mem._nid); + for( Node use : mem._outputs ) { + if( use instanceof MemMergeNode merge ) { + Node slice = _alias<merge.nIns() ? merge.in(_alias) : null; + if( (slice==mem) || (slice==null && merge.in(1)==mem) ) + antiDeps(merge,deps,visit); + } else if( use instanceof CallNode || + (use instanceof MemOpNode st && !st._isLoad && st._alias==_alias) || + (use instanceof NewNode nnn && nnn.field(_alias)!=null) || + (use instanceof MemPhiNode phi && phi._alias==_alias) || + (use instanceof BulkMemPhiNode phi && !phi.isSplit(_alias)) ) { + if( !deps.contains(use) ) deps.add(use); + } + } + } } diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java new file mode 100644 index 00000000..1c354d5c --- /dev/null +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java @@ -0,0 +1,44 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.codegen.CodeGen; +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,Type.BOTTOM),inputs); + assert alias > 1; + _alias = alias; + } + + public MemPhiNode(MemPhiNode phi) { super(phi); _alias = phi._alias; } + + @Override public String label() { return "MemPhi_"+_alias; } + + @Override public Type compute() { + assert BulkMemPhiNode.checkMem(this,_alias); + if( !(region() instanceof RegionNode r) ) + return (region()._type==Type.XCONTROL || region()._type==Type.TOP) ? TypeMem.TOP : _type; + if( r.inProgress() ) return r instanceof LoopNode ? _minType : TypeMem.BOT; + Type t = Type.TOP; + for( int i=1; i<nIns(); i++ ) + if( addDep(r.in(i))._type!=Type.XCONTROL && r.in(i)._type!=Type.TOP ) + t = t.meet(MemMergeNode.contents(in(i),_alias,this)); + return TypeMem.make(_alias,t); + } + + @Override public Node idealize() { + for( int i=1; i<nIns(); i++ ) + if( in(i) instanceof MemMergeNode mem ) { + setDef(i,CodeGen.CODE.add(mem.alias(_alias))); + return this; + } + return super.idealize(); + } + + @Override public boolean eq(Node n) { return _alias==((MemPhiNode)n)._alias && super.eq(n); } + @Override int hash() { return _alias; } +} diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/NewNode.java index 1a15439e..5d4d5bf0 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/NewNode.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/NewNode.java @@ -7,33 +7,35 @@ import com.seaofnodes.simple.util.Utils; import java.util.BitSet; -/** - * Allocation! Allocate a chunk of memory, and pre-zero it. - * The inputs include control and size, and ALL aliases being set. - * The output is large tuple, one for every alias plus the created pointer. - * New is expected to be followed by projections for every alias. +/** Allocate a zeroed object. Inputs {ctrl, $mem, size}; + * results {ptr, $mem}. + * The memory input and result cover only the aliases in the allocated struct. */ public class NewNode extends Node implements MultiNode { public final TypeMemPtr _ptr; - public final int _len; public NewNode(TypeMemPtr ptr, Node... nodes) { super(nodes); assert !ptr.nullable(); _ptr = ptr; - _len = ptr._obj._fields.length; - // Control in slot 0 - assert nodes[0]._type==Type.CONTROL || nodes[0]._type == Type.XCONTROL; - // Malloc-length in slot 1 - assert nodes[1]._type instanceof TypeInteger || nodes[1]._type==Type.NIL; - for( int i=0; i<_len; i++ ) - assert ptr._obj._fields[i]._one || nodes[2 + i]._type.isa( TypeMem.BOT ); + assert nodes.length==3; + assert nodes[0]._type==Type.CONTROL || nodes[0]._type==Type.XCONTROL; + assert nodes[1]._type instanceof TypeMem; + assert nodes[2]._type instanceof TypeInteger || nodes[2]._type==Type.NIL; } - public NewNode(NewNode nnn) { super(nnn); _ptr = nnn._ptr; _len = nnn._len; } + public Node mem() { return in(1); } + public Node size() { return in(2); } + + public Field field(int alias) { + for( Field f : _ptr._obj._fields ) + if( !f._one && f._alias==alias ) return f; + return null; + } + + public NewNode(NewNode nnn) { super(nnn); _ptr = nnn._ptr; } - public Node mem (int idx) { return in(idx+2); } @Override public String label() { return "new_"+(_ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._t.str() : _ptr._obj.str()); } @Override @@ -42,46 +44,12 @@ public class NewNode extends Node implements MultiNode { return sb.append(_ptr._obj.str()); } - int findAlias(int alias) { - int idx = _ptr._obj.findAlias(alias); - assert idx!= -1; // Error, caller should be calling - return idx+2; // Skip ctrl, size - } - - // 0 - ctrl - // 1 - byte size - // 2-len+2 - aliases, one per field - // len+2 - 2*len+2 - initial values, one per field - public Node size() { return in(1); } @Override public TypeTuple compute() { - Field[] fs = _ptr._obj._fields; - Type[] ts = new Type[fs.length+2]; - if( in(0)._type.isHigh() ) { - ts[0] = Type.XCONTROL; - ts[1] = _ptr.makeHigh(); - for( int i=0; i<fs.length; i++ ) - ts[i+2] = Type.TOP; - } else { - ts[0] = Type.CONTROL; - ts[1] = _ptr; - for( int i=0; i<fs.length; i++ ) { - if( _ptr._obj._fields[i]._one ) { - // Once-only fields use the declared type - ts[i+2] = _ptr._obj._fields[i]._t; - } else { - // Others take from the inputs - Type mt = in(i+2)._type; - TypeMem mem = mt==Type.TOP ? TypeMem.TOP : (TypeMem)mt; - //Type tfld = mem._t.meet(mem._t.makeZero()); - Type tfld = mem._t; - Type tfld2 = tfld.join(fs[i]._t ); - ts[i+2] = TypeMem.make(fs[i]._alias,tfld2); - } - } - } - return TypeTuple.make(ts); + return in(0)._type.isHigh() + ? TypeTuple.make(_ptr.makeHigh(),TypeMem.TOP) + : TypeTuple.make(_ptr,TypeMem.BOT); } @Override @@ -126,9 +94,9 @@ public class NewNode extends Node implements MultiNode { _kills = kills; } public String op() { return "alloc"; } - public RegMask regmap(int i) { return i==1 ? _arg3Mask : null; } + public RegMask regmap(int i) { return i==2 ? _arg3Mask : null; } public RegMask outregmap() { return null; } - public RegMask outregmap(int idx) { return idx==1 ? _retMask : null; } + public RegMask outregmap(int idx) { return idx==0 ? _retMask : null; } public RegMask killmap() { return _kills; } public void asm(CodeGen code, SB sb) { sb.p("#calloc, ").p(code.reg(size())); diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter24/src/main/java/com/seaofnodes/simple/node/Node.java index c746d7d5..48e3d63f 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/Node.java @@ -233,6 +233,7 @@ public abstract class Node implements Cloneable { // Error is 'use' does not exist; ok for 'use' to be null. public boolean delUse( Node use ) { _outputs.del(_outputs.find(use)); + moveDepsToWorklist(); // User-count and anti-dependence queries can now change. return _outputs.isEmpty(); } @@ -309,6 +310,7 @@ public abstract class Node implements Cloneable { n.unlock(); int idx = n._inputs.find(this); n._inputs.set(idx,nnn); + n.moveDepsToWorklist(); // Rewiring can change a dependent query without changing type. nnn.addUse(n); CODE.add(n); CODE.addAll(n._outputs); @@ -556,7 +558,12 @@ public abstract class Node implements Cloneable { * 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. */ - <N extends Node> N addDep( N dep ) { + <N extends Node> N addDep( N dep ) { return addDep(dep,false); } + + // Keep an explicit dependency when inspecting an immediate user's shape. + <N extends Node> N addDepForwards(N dep) { return addDep(dep,true); } + + private <N extends Node> N addDep(N dep, boolean forwards) { // Running peepholes during the big assert cannot have side effects // like adding dependencies. if( CODE._midAssert ) return dep; @@ -564,8 +571,8 @@ public abstract class Node implements Cloneable { if( obs != null ) obs.dep(this, dep); if( dep._deps==null ) dep._deps = new Ary<>(Node.class); if( dep._deps .find(this) != -1 ) return dep; // Already on list - if( dep._inputs .find(this) != -1 ) return dep; // No need for deps on immediate neighbors - if( dep._outputs.find(this) != -1 ) return dep; + if( !forwards && dep._inputs .find(this) != -1 ) return dep; // No need for deps on immediate neighbors + if( !forwards && dep._outputs.find(this) != -1 ) return dep; dep._deps.add(this); return dep; } diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/PhiNode.java index cfeb1404..8d8a276c 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -35,6 +35,10 @@ public class PhiNode extends Node { addDef(sample); } + 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_"+MemOpNode.mlabel(_label); } @Override @@ -114,6 +118,8 @@ public class PhiNode extends Node { if( live != null ) { if( live._type.isa(_type) ) return live; + // Let the bulk input split this alias before collapsing a precise Phi. + if( live instanceof BulkMemPhiNode ) return null; // Keep the Phi upcast return new CastNode(_type,null,live); } @@ -123,19 +129,9 @@ public class PhiNode extends Node { if( r.in(i)._type == Type.XCONTROL ) return null; - // Simple Phi-after-MemMerge to a known alias can bypass. Happens when inlining. - if( _type instanceof TypeMem tmem && tmem._alias!=1 ) { - for( int i=1; i<nIns(); i++ ) - if( in(i) instanceof MemMergeNode mem ) { - setDef(i,mem.alias(tmem._alias)); - return this; - } - } - - // Generic "pull down op" - Node progress; - if( same_op() && (progress = drop_same_op()) != null ) - return progress; + // Phi(op(a,b,...),op(c,d,...)) -> op(Phi(a,c),Phi(b,d),...). + Node down; + if( same_op() && (down=drop_same_op())!=null ) return down; // If merging Phi(N, cast(N)) - we are losing the cast JOIN effects, so just remove. if( nIns()==3 ) { @@ -170,50 +166,77 @@ public class PhiNode extends Node { // Same op on all Phi paths; all ops have only the Phi as a use. // None have a control input. private boolean same_op() { + Node op = in(1); + if( op instanceof CFGNode || op instanceof ConstantNode || op instanceof PhiNode || + op instanceof ProjNode || op instanceof NewNode || op instanceof ScopeNode || op instanceof MemMergeNode ) return false; + // A bulk Phi must split its aliases before factoring precise Stores. + if( this instanceof BulkMemPhiNode ) return false; + // Bulk splitting currently identifies parallel slices by Region/alias. + // Factoring can introduce another slice at a different memory point; + // wait until that Region's bulk partitioning has finished. + if( op instanceof MemOpNode ) + for( Node use : region()._outputs ) + if( use instanceof BulkMemPhiNode ) { + addDepForwards(use); + return false; + } + Node busy = null; for( int i=1; i<nIns(); i++ ) { - Node op = in(i); - if( in(1).getClass() != op.getClass() || op.in(0)!=null || in(1).nIns() != op.nIns() ) - return false; // Wrong class or CFG bound or mismatched inputs - if( in(1) instanceof MemOpNode mem && mem._alias != ((MemOpNode)op)._alias ) - return false; - if( op.nOuts() > 1 ) { // Too many users, but addDep in case lose users - for( Node out : op._outputs ) - if( out!=null && out!=this ) - addDep(out); - return false; + Node n = in(i); + if( addDep(region().in(i))._type==Type.XCONTROL || + op.getClass()!=n.getClass() || n.nIns()!=op.nIns() || n.in(0)!=null || !op.eq(n) ) return false; + if( n instanceof MemOpNode mem && !mem.canDrop((MemOpNode)op,this) ) return false; + // Moving a Store must remove the old effect, not duplicate it. + // Allow one shared arm: the other arms disappear into the single + // factored operation, usually reducing the total operation count. + addDepForwards(n); + if( n.nOuts()>1 ) { + for( Node use : n._outputs ) + if( use!=null && use!=this ) addDepForwards(use); + if( n instanceof StoreNode || busy!=null ) return false; + busy=n; } - for( int j=1; j<in(1).nIns(); j++ ) - if( op.in(j) instanceof ScopeNode || (op.in(j)==null ^ in(1).in(j)==null) ) - return false; // Lazy Phi input + for( int j=1; j<n.nIns(); j++ ) + if( n.in(j) instanceof ScopeNode || ((n.in(j)==null) != (op.in(j)==null)) ) return false; } return true; } private Node drop_same_op() { - assert !(in(1) instanceof CFGNode); - Node op = in(1); - Node cp = op.copy(); - cp._type = null; // Fresh type - cp.addDef(null); // No control - + Node op = in(1), cp = op.copyEmpty(); + cp._type = null; + cp.addDef(null); for( int j=1; j<op.nIns(); j++ ) { - boolean needsPhi = false; - Node x = op.in(j); // Jth input from sample #1 + Node x = op.in(j); + boolean different = false; for( int i=2; i<nIns(); i++ ) - if( in(i).in(j) != x ) - { needsPhi=true; break; } - if( needsPhi ) { - x = new PhiNode(_label,op.in(j)._type.glb(false)); - x.addDef(region()); - for( int i=1; i<nIns(); i++ ) - x.addDef(in(i).in(j)); - x = x.peephole(); + if( in(i).in(j)!=x ) different=true; + if( different ) { + Node[] ins = new Node[nIns()]; + ins[0] = region(); + Type t = Type.TOP; + for( int i=1; i<nIns(); i++ ) { + ins[i] = in(i).in(j); + t = t.meet(ins[i]._type); + } + // Not accepts both pointers and integers, but a Phi cannot mix them. + if( t==Type.BOTTOM ) { + for( int i=1; i<ins.length; i++ ) addDep(ins[i]); + cp.kill(); + return null; + } + PhiNode phi = j==1 && op instanceof MemOpNode mem + ? new MemPhiNode(_label,mem._alias,ins) + : PhiNode.make(_label,t.glb(false),ins); + x = phi.peephole(); } cp.addDef(x); } - // Test not running backwards, which can happen for e.g. And's - if( cp.compute().isa(compute()) ) - return cp; + // Factoring must not widen the result (e.g. correlated And operands). + if( cp.compute().isa(compute()) ) return cp; + for( int i=1; i<nIns(); i++ ) + for( int j=1; j<in(i).nIns(); j++ ) + if( in(i).in(j)!=null ) addDep(in(i).in(j)); cp.kill(); return null; } diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/RegionNode.java index e9c958ac..1ffbcd60 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/RegionNode.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/RegionNode.java @@ -46,10 +46,10 @@ public class RegionNode extends CFGNode { // If a CFG diamond with no merging, delete: "if( pred ) {} else {};" if( !hasPhi() && nIns()>3 && // No Phi users, just a control user - in(1) instanceof CProjNode p1 && - in(2) instanceof CProjNode p2 && - addDep(p1.in(0))==addDep(p2.in(0)) && - p1.in(0) instanceof IfNode iff ) { + in(1) instanceof CProjNode p1 && + in(2) instanceof CProjNode p2 && + addDepForwards(p1).in(0)==addDepForwards(p2).in(0) && + p1.in(0) instanceof IfNode iff ) { // Replace with the iff.ctrl directly if( nIns()==3 ) return iff.ctrl(); // Just delete the path for fat Regions diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/ReturnNode.java index 80b3fe4f..ce6d15bb 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/ReturnNode.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/ReturnNode.java @@ -9,15 +9,7 @@ import com.seaofnodes.simple.util.SB; import com.seaofnodes.simple.util.Utils; 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 all preceding memory effects and the return value alive: {ctrl, $mem, value, rpc}. */ public class ReturnNode extends CFGNode { public FunNode _fun; @@ -51,7 +43,8 @@ public class ReturnNode extends CFGNode { @Override public Type compute() { if( inProgress () ) return TypeTuple.RET; // In progress - return TypeTuple.make(ctrl()._type,mem()._type,expr()._type); + // Inlining can delete the entry while callers still use this return. + return TypeTuple.make(ctrl()._type,TypeMem.BOT,expr()._type); } @Override public Node idealize() { diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/ScopeNode.java index 04666956..cddb9f24 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/ScopeNode.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/ScopeNode.java @@ -12,9 +12,9 @@ import java.util.*; /** * The Scope node is purely a parser helper - it tracks names to nodes with a - * stack of variables. + * stack of variable records. */ -public class ScopeNode extends MemMergeNode { +public class ScopeNode extends Node { /** * The control is a name that binds to the currently active control @@ -24,8 +24,12 @@ public class ScopeNode extends MemMergeNode { public static final String ARG0 = "arg"; public static final String MEM0 = "$mem"; - // All active/live variables in all nested scopes, all run together. - // Maps 1-to-1 to the _inputs array. + public Node in(Var v) { return in(v._idx); } + + @Override public Type compute() { return Type.BOTTOM; } + @Override public Node idealize() { return null; } + + // All active/live variables in all nested scopes, all run together public final Ary<Var> _vars; // Lexical scope is typed: @@ -50,10 +54,10 @@ public class ScopeNode extends MemMergeNode { // A new ScopeNode public ScopeNode() { - super(true); - _vars = new Ary<>(Var .class); - _kinds = new Ary<>(Kind.class); - _guards = new Ary<>(Node.class); + _type = Type.BOTTOM; + _vars = new Ary<>(Var .class); + _kinds = new Ary<>(Kind .class); + _guards = new Ary<>(Node .class); } @Override public String label() { return "Scope"; } @@ -85,7 +89,7 @@ public class ScopeNode extends MemMergeNode { public Node ctrl() { return in(0); } - public MemMergeNode mem() { return (MemMergeNode)in(1); } + public Node mem() { return in(lookup(MEM0)); } public Var var(int i) { return _vars.at(i); } /** @@ -99,7 +103,7 @@ public class ScopeNode extends MemMergeNode { * @return Node that was bound */ public <N extends Node> N ctrl(N n) { return setDef(0,n); } - public Node mem(Node n) { return setDef(1,n); } + public Node mem(Node n) { update(MEM0,n); return n; } public void push( Kind kind ) { kind._lexSize = _vars.size(); @@ -206,12 +210,6 @@ public class ScopeNode extends MemMergeNode { return true; } - // Read from memory - public Node mem( int alias ) { return mem()._mem(alias,null); } - // Write to memory - public void mem( int alias, Node st ) { mem()._mem(alias,st); } - - /** * Lookup a name in all scopes starting from most deeply nested. * @@ -247,7 +245,7 @@ public class ScopeNode extends MemMergeNode { // 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(v._idx,new PhiNode(v._name, v.type(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).peephole()); + : loop.setDef(v._idx,PhiNode.make(v._name, v.type(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).peephole()); setDef(v._idx,old); } //assert !v._final || st==null; @@ -285,21 +283,9 @@ public class ScopeNode extends MemMergeNode { n.keep(); dup.addDef(ctrl()); // Control input is just copied - // Memory input is a shallow copy - MemMergeNode memdup = new MemMergeNode(true), mem = mem(); - memdup.addDef(null); - memdup.addDef(loop ? this : mem.in(1)); - for( int i=2; i<mem.nIns(); i++ ) - // For lazy phis on loops we use a sentinel - // that will trigger phi creation on update - memdup.addDef(loop ? this : mem.in(i)); - dup.addDef(memdup); - - // Copy of other inputs - for( int i=2; i<nIns(); i++ ) - // For lazy phis on loops we use a sentinel - // that will trigger phi creation on update - dup.addDef(loop && !var(i)._final ? this : in(i)); + // Memory is one binding, with the same lazy loop Phi as scalar names. + for( int i=1; i<nIns(); i++ ) + dup.addDef(loop && !_vars.at(i)._final ? this : in(i)); return dup; } @@ -313,22 +299,21 @@ public class ScopeNode extends MemMergeNode { */ public RegionNode mergeScopes(ScopeNode that, Parser.Lexer loc) { RegionNode r = ctrl(new RegionNode(loc,null,ctrl(), that.ctrl()).keep()); - mem()._merge(that.mem(),r); - this ._merge(that ,r); + _merge(that,r); that.kill(); // Kill merged scope CodeGen.CODE.add(r); return r.unkeep(); } private void _merge(ScopeNode that, RegionNode r) { - for( int i = 2; i < nIns(); i++) + 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 Var v = var(i); Node lhs = this.in(this.update(v,null)); Node rhs = that.in(that.update(v,null)); - setDef(i, new PhiNode(v._name, v.type(), r, lhs, rhs).peephole()); + setDef(i, PhiNode.make(v._name, v.type(), r, lhs, rhs).peephole()); } } @@ -353,23 +338,17 @@ public class ScopeNode extends MemMergeNode { assert ctrl instanceof LoopNode loop && loop.inProgress(); ctrl.setDef(2,back.ctrl()); - mem()._endLoopMem( this, back.mem(), exit.mem() ); - this ._endLoop ( this, back , exit ); + _endLoop(this,back,exit); back.kill(); // Loop backedge is dead // Now one-time do a useless-phi removal - mem()._useless(); - this ._useless(); - - // The exit mem's lazy default value had been the loop top, - // now it goes back to predating the loop. - exit.mem().setDef(1,mem().in(1)); + _useless(); } // Fill in the backedge of any inserted Phis void _endLoop( ScopeNode scope, Node back, Node exit ) { - for( int i=2; i<nIns(); i++ ) { - if( var(i)._final ) continue; // Final vars did not get modified in the loop - if( var(i).type().isHighOrConst() ) continue; // Cannot lift higher than a constant, so no Phi + for( int i=1; i<nIns(); i++ ) { + if( _vars.at(i)._final ) continue; // Final vars did not get modified in the loop + if( _vars.at(i).type().isHighOrConst() ) continue; // Cannot lift higher than a constant, so no Phi if( back.in(i) != scope ) { PhiNode phi = (PhiNode)in(i); assert phi.region()==scope.ctrl() && phi.in(2)==null; @@ -381,6 +360,23 @@ public class ScopeNode extends MemMergeNode { } + // Now one-time do a useless-phi removal + void _useless( ) { + for( int i=1; i<nIns(); i++ ) { + if( in(i) instanceof PhiNode phi ) { + // Do an eager useless-phi removal + Node in = phi.peephole(); + CodeGen.CODE.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. public void addGuards( Node ctrl, Node pred, boolean invert ) { diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/StartNode.java index 6ed2acd3..186adfb7 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/StartNode.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/StartNode.java @@ -3,18 +3,11 @@ package com.seaofnodes.simple.node; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.Parser; import com.seaofnodes.simple.type.*; -import java.util.Arrays; import java.util.BitSet; import java.util.HashSet; import static com.seaofnodes.simple.util.Utils.TODO; -/** - * The Start node represents the start of the function. - * <p> - * 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. - */ +/** Start supplies control, whole memory, and the argument, in that order. */ public class StartNode extends LoopNode implements MultiNode { final Type _arg; @@ -44,7 +37,7 @@ public class StartNode extends LoopNode implements MultiNode { @Override public TypeTuple compute() { - return TypeTuple.make(Type.CONTROL,TypeMem.TOP,_arg); + return TypeTuple.make(Type.CONTROL,TypeMem.BOT,_arg); } @Override public Node idealize() { return null; } diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/StoreNode.java index 09257730..7a75688c 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple.node; +import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.*; import com.seaofnodes.simple.type.*; import java.util.BitSet; @@ -25,6 +26,10 @@ public class StoreNode extends MemOpNode { } // Debugger label + @Override boolean canDrop(MemOpNode other, Node dep) { + return super.canDrop(other,dep) && _init==((StoreNode)other)._init; + } + @Override public String label() { return "st_"+mlabel(); } @Override public boolean isMem() { return true; } @@ -38,24 +43,18 @@ public class StoreNode extends MemOpNode { @Override public Type compute() { Type val = val()._type; - Type mem0 = mem()._type; - if( mem0 == Type.TOP ) return TypeMem.TOP; - TypeMem mem = (TypeMem)mem0; // Invariant - if( mem == TypeMem.TOP ) return TypeMem.TOP; - Type t = Type.BOTTOM; // No idea on field contents - // Same alias, lift val to the declared type and then meet into other fields - if( mem._alias == _alias ) { - assert !_declaredType.isFRef(); - // Sharpen memory value; required for narrowing stores where the parser inserts - // zero/sign masking and somebody reads the TypeMem type. - val = val.join(_declaredType); - t = val.meet(mem._t); - } + assert !_declaredType.isFRef(); + Type t = val.join(_declaredType).meet(MemMergeNode.contents(mem(),_alias,this)); return TypeMem.make(_alias,t); } @Override public Node idealize() { + if( mem() instanceof MemMergeNode merge ) { + setDef(1,CodeGen.CODE.add(merge.alias(_alias))); + return this; + } + if( mem() instanceof CastNode cast ) { setDef(1,cast.in(1)); @@ -65,8 +64,8 @@ public class StoreNode extends MemOpNode { // 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 - off()==st.off() && // And same offset (could be "same alias" but this handles arrays to same index) + ptr()==st.ptr() && _alias==st._alias && // Must check same object + off()==st.off() && // And same offset 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. @@ -76,12 +75,6 @@ public class StoreNode extends MemOpNode { return this; } - // Simple store-after-MemMerge to a known alias can bypass. Happens when inlining. - if( mem() instanceof MemMergeNode mem ) { - setDef(1,mem.alias(_alias)); - return this; - } - // Value is automatically truncated by narrow store if( val() instanceof AndNode and && and.in(2)._type.isConstant() ) { int log = _declaredType.log_size(); diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java index 70034227..eab72947 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java @@ -677,6 +677,8 @@ public class arm extends Machine { case NotNode not -> new NotARM(not); case OrNode or -> or(or); case ParmNode parm -> new ParmARM(parm); + case BulkMemPhiNode phi -> new BulkMemPhiNode(phi); + case MemPhiNode phi -> new MemPhiNode(phi); case PhiNode phi -> new PhiNode(phi); case ProjNode prj -> prj(prj); case ReadOnlyNode read -> new ReadOnlyMach(read); diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java index 48cb5d23..88ec9ebd 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java @@ -345,6 +345,8 @@ public class riscv extends Machine { case NotNode not -> new NotRISC(not); case OrNode or -> or(or); case ParmNode parm -> new ParmRISC(parm); + case BulkMemPhiNode phi -> new BulkMemPhiNode(phi); + case MemPhiNode phi -> new MemPhiNode(phi); case PhiNode phi -> new PhiNode(phi); case ProjNode prj -> prj(prj); case ReadOnlyNode read-> new ReadOnlyMach(read); diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java index 441cf693..d0fb0081 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java @@ -24,6 +24,11 @@ public class LoadX86 extends MemOpX86 { } static void enc( Encoding enc, Type decl, short dst, short ptr, short idx, int off, int scale ) { + // Allocation may put a full-width integer in an XMM register (or a + // floating value in a GPR). Move its bits using the selected register bank. + if( dst>=x86_64_v2.XMM_OFFSET && decl.log_size()==3 ) decl=TypeFloat.F64; + else if( dst<x86_64_v2.XMM_OFFSET && decl instanceof TypeFloat ) + decl = decl==TypeFloat.F32 ? TypeInteger.U32 : TypeInteger.BOT; if( decl == TypeFloat.F32) enc.add1(0xF3); if( decl == TypeFloat.F64) enc.add1(0xF2); diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java index 7ac7fc19..cf9288b8 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java @@ -363,6 +363,8 @@ public class x86_64_v2 extends Machine { case NotNode not -> new NotX86(not); case OrNode or -> or(or); case ParmNode parm -> new ParmX86(parm); + case BulkMemPhiNode phi -> new BulkMemPhiNode(phi); + case MemPhiNode phi -> new MemPhiNode(phi); case PhiNode phi -> new PhiNode(phi); case ProjNode prj -> prj(prj); case ReadOnlyNode read-> new ReadOnlyMach(read); @@ -396,9 +398,8 @@ public class x86_64_v2 extends Machine { if( lhs instanceof LoadNode ld && ld.nOuts() == 1 && ld._declaredType.log_size() >= 3) return new AddMemX86(add, address(ld), ld.ptr(), idx, off, scale, 0, rhs); -// if(rhs instanceof LoadNode ld && ld.nOuts() == 1 && ld._declaredType.log_size() >= 3) { -// throw Utils.TODO(); // Swap load sides -// } + if( rhs instanceof LoadNode ld && ld.nOuts()==1 && ld._declaredType.log_size()>=3 ) + return new AddMemX86(add,address(ld),ld.ptr(),idx,off,scale, 0,lhs); // Attempt a full LEA-style break down. // Returns one of AddX86, AddIX86, LeaX86, or LHS diff --git a/chapter24/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter24/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java index c895a0cb..4417ccd1 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java @@ -67,7 +67,7 @@ public class SimpleGraphAdapter extends GraphAdapter<Node> { return n instanceof CFGNode ? 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( n instanceof MemMergeNode || i == 1 && (n instanceof MemOpNode || n instanceof ReturnNode || n instanceof CallNode) ) return Role.MEM; + if( n instanceof MemMergeNode || (i == 1 && (n instanceof MemOpNode || n instanceof ReturnNode || n instanceof CallNode || n instanceof NewNode)) ) return Role.MEM; Node def = n.in(i); if( def != null && isMem(def) ) return Role.MEM; return Role.DATA; diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter24/src/test/java/com/seaofnodes/simple/Chapter10Test.java index 3c5a2913..121e8a22 100644 --- a/chapter24/src/test/java/com/seaofnodes/simple/Chapter10Test.java +++ b/chapter24/src/test/java/com/seaofnodes/simple/Chapter10Test.java @@ -2,6 +2,8 @@ package com.seaofnodes.simple; import com.seaofnodes.simple.codegen.CodeGen; import org.junit.Test; +import com.seaofnodes.simple.node.*; +import java.util.BitSet; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -468,4 +470,121 @@ return 0; assertEquals("return 0;", code.print()); } + @Test public void testReadBeforeStores() { + var code = new CodeGen(""" + 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().opto(); + StopNode stop = code._stop; + assertEquals("1133",Eval2.eval(code,0)); + assertEquals("2244",Eval2.eval(code,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 code = new CodeGen(NESTED_MEMORY).parse().opto(); + StopNode stop = code._stop; + for (int arg=0; arg<7; arg++) { + assertEquals("S{x="+(5L+arg)+",y="+(7L+arg*(arg+1)-(arg>=2 ? 2 : 0))+ + ",z="+(11L+2*arg-(arg>=2 ? 2 : 0))+"}",Eval2.eval(code,arg)); + } + } + + @Test public void testMemoryAtEarlyReturns() { + var code = new CodeGen(""" + 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().opto(); + StopNode stop = code._stop; + for (int arg=0; arg<2; arg++) { + assertEquals(arg==0 ? "S{x=3,y=17,z=7}" : "S{x=11,y=5,z=13}",Eval2.eval(code,arg)); + } + } + + @Test public void testKeepLoadsAtMerge() { + var code = new CodeGen(""" + struct S { int x; }; + S !a = new S; S !b = new S; + a.x = arg; b.x = arg+1; + S !p = a; S !q = b; + if (arg<0) { p=b; q=a; } + int v; + if (arg>1) v=p.x; else v=q.x; + return v; + """).parse().opto(); + StopNode stop = code._stop; + // The one-step safety check stops at the memory aggregate. + assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet())); + assertEquals("-1",Eval2.eval(code,-1)); + assertEquals("1",Eval2.eval(code,0)); + assertEquals("3",Eval2.eval(code,3)); + } + + @Test public void testDropStores() { + var code = new CodeGen(""" + struct S { int x; }; + S !s = new S; + if (arg) s.x=arg+1; else s.x=arg+2; + return s; + """).parse().opto(); + StopNode stop = code._stop; + assertEquals(1,countMemoryNodes(stop,StoreNode.class,new BitSet())); + assertEquals("S{x=2}",Eval2.eval(code,0)); + assertEquals("S{x=4}",Eval2.eval(code,3)); + } + + @Test public void testKeepReadsBeforeWrites() { + var code = new CodeGen(""" + struct S { int x; int y; }; + S !a = new S; S !b = new S; + a.x=arg; b.x=arg+1; + S !p=a; S !q=b; + if (arg<0) { p=b; q=a; } + int v; + if (arg>1) { v=p.x; p.x=41; p.y=5; } + else { v=q.x; q.x=42; } + return v; + """).parse().opto(); + StopNode stop = code._stop; + assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet())); + assertEquals("-1",Eval2.eval(code,-1)); + assertEquals("1",Eval2.eval(code,0)); + assertEquals("3",Eval2.eval(code,3)); + } + + private static int countMemoryNodes(Node n, Class<?> kind, BitSet seen) { + if (n==null || seen.get(n._nid)) return 0; + seen.set(n._nid); + int cnt=kind.isInstance(n) ? 1 : 0; + for (Node def : n._inputs) cnt+=countMemoryNodes(def,kind,seen); + return cnt; + } } diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Chapter10bTest.java b/chapter24/src/test/java/com/seaofnodes/simple/Chapter10bTest.java new file mode 100644 index 00000000..f2b83cdd --- /dev/null +++ b/chapter24/src/test/java/com/seaofnodes/simple/Chapter10bTest.java @@ -0,0 +1,60 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; +import org.junit.Test; +import static org.junit.Assert.*; + +public class Chapter10bTest { + @Test public void testIndependentField() { + var code = new CodeGen(""" + 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().opto(); + var stop = code._stop; + assertEquals("return 42;",stop.toString()); + assertEquals("42",Eval2.eval(code,5)); + assertNull(stop.walk(n -> n instanceof LoadNode ? n : null)); + } + + @Test public void testParallelMemoryPhis() { + var code = new CodeGen(""" + 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().opto(); + var stop = code._stop; + 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) && !(n instanceof ParmNode)); + return null; + }); + assertTrue(phis[0]>=3); + assertEquals("S{x=1,y=2,z=3}",Eval2.eval(code,4)); + } + + @Test public void testLateSliceWorklist() { + // 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 code = new CodeGen(Chapter10Test.NESTED_MEMORY,com.seaofnodes.simple.type.TypeInteger.BOT,seed,false).parse(); + code.opto(); + assertEquals("S{x=9,y=25,z=17}",Eval2.eval(code,4)); + } + } +} diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/chapter24/src/test/java/com/seaofnodes/simple/Chapter16Test.java index 310efc45..57ec5508 100644 --- a/chapter24/src/test/java/com/seaofnodes/simple/Chapter16Test.java +++ b/chapter24/src/test/java/com/seaofnodes/simple/Chapter16Test.java @@ -253,4 +253,27 @@ return new Square; assertEquals("Square{side=3.0,diag=1.7320508075688772}", Eval2.eval(code, 3)); assertEquals("Square{side=4.0,diag=2.0}", Eval2.eval(code, 4)); } + static final String CONSTRUCTOR_MEMORY = """ + struct S { int x; int y; }; + struct T { int z=arg+40; }; + T !t = new T; + S !a = new S { x=11; y=7; }; + S !b = new S { x=22; y=9; }; + S !p=a; + if (arg) p=b; + int before=p.x; + S !c = new S { + x=p.x+1; + { int i=0; while (i<2) { p.y=p.y+1; i=i+1; } } + y=p.y; + }; + p.x=33; + return before*10000+c.x*100+c.y+t.z; + """; + + @Test public void testConstructorMemory() { + var code = new CodeGen(CONSTRUCTOR_MEMORY).parse().opto(); + assertEquals("111249",Eval2.eval(code,0)); + assertEquals("222352",Eval2.eval(code,1)); + } } diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Chapter17Test.java b/chapter24/src/test/java/com/seaofnodes/simple/Chapter17Test.java index 53c23ecd..37208308 100644 --- a/chapter24/src/test/java/com/seaofnodes/simple/Chapter17Test.java +++ b/chapter24/src/test/java/com/seaofnodes/simple/Chapter17Test.java @@ -369,7 +369,7 @@ var b = arg ? new Bar { next = (arg==2) ? new Bar{x=2;}; x=1; }; return b ? b.next ? b.next.x : b.x; // parses "b ? (b.next ? b.next.x : b.x) : 0" """); code.parse().opto(); - assertEquals("return Phi(Region,.x,0);", code.print()); + assertEquals("return Phi(Region,.x,0,.x);", code.print()); assertEquals("0", Eval2.eval(code, 0)); assertEquals("1", Eval2.eval(code, 1)); assertEquals("2", Eval2.eval(code, 2)); diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Chapter18Test.java b/chapter24/src/test/java/com/seaofnodes/simple/Chapter18Test.java index 479e9e62..6e027312 100644 --- a/chapter24/src/test/java/com/seaofnodes/simple/Chapter18Test.java +++ b/chapter24/src/test/java/com/seaofnodes/simple/Chapter18Test.java @@ -439,4 +439,70 @@ return p.coffee_count; assertEquals("1", Eval2.eval(code, 2)); } + static final String CALL_MEMORY = """ + struct S { int x; int y; }; + val bump = { S s, int d -> int old=s.x; s.x=old+d; return old; }; + S !a = new S { x=10; y=7; }; + S !b = new S { x=20; y=9; }; + S !p=a; if (arg) p=b; + int before=p.x; + int old=bump(p,3); + int after=p.x; + bump(a,5); + return before*1000000+old*10000+after*100+a.x+a.y; + """; + + @Test public void testCallMemory() { + var code = new CodeGen(CALL_MEMORY).parse().opto(); + int[] calls={0}; + code._stop.walk(n -> { if(n instanceof CallNode) calls[0]++; return null; }); + assertEquals(2,calls[0]); // Keep real calls so this checks scheduling across them. + assertEquals("10101325",Eval2.eval(code,0)); + assertEquals("20202322",Eval2.eval(code,1)); + code._stop.walk(n -> { + if(n instanceof CallNode call) { + assertEquals(5,call.nIns()); // No scheduling edges may become extra arguments. + assertTrue(call.fptr()._type instanceof TypeFunPtr); + } + return null; + }); + } + + static final String RECURSIVE_MEMORY = """ + struct S { int x; }; + val rec = { S s, int n -> + if (n==0) return s.x; + int before=s.x; + s.x=before+1; + return before+rec(s,n-1); + }; + S !s=new S { x=10; }; + int value=rec(s,arg); + return value*100+s.x; + """; + + @Test public void testRecursiveMemory() { + var code = new CodeGen(RECURSIVE_MEMORY).parse().opto(); + for(int n=0; n<7; n++) + assertEquals(Long.toString((10L*(n+1)+n*(n+1)/2)*100+10+n),Eval2.eval(code,n)); + } + + static final String INLINE_MEMORY = """ + struct S { int x; int y; }; + val make = { S s -> + S t = new S { x=s.x+1; y=3; }; + s.y=9; + return t; + }; + S !a = new S { x=arg+40; y=7; }; + S t=make(a); + return a.x*10000+a.y*100+t.x+t.y; + """; + + @Test public void testInlineAllocationMemory() { + var code = new CodeGen(INLINE_MEMORY).parse().opto(); + assertNull(code._stop.walk(n -> n instanceof CallNode ? n : null)); + assertEquals("400944",Eval2.eval(code,0)); + assertEquals("420946",Eval2.eval(code,2)); + } } diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Chapter19Test.java b/chapter24/src/test/java/com/seaofnodes/simple/Chapter19Test.java index df94e97c..841d9298 100644 --- a/chapter24/src/test/java/com/seaofnodes/simple/Chapter19Test.java +++ b/chapter24/src/test/java/com/seaofnodes/simple/Chapter19Test.java @@ -426,4 +426,72 @@ return sq(arg) + sq(3); code.driver(Phase.LocalSched,"x86_64_v2", "SystemV"); assertEquals("Stop[ return (add,#2,#2); return (mul,Parm_x(sq,i64),x); ]", code.print()); } + + @Test public void testSelectedMemory() { + for( String cpu : new String[]{"x86_64_v2","riscv","arm"} ) + for( String src : new String[]{Chapter10Test.NESTED_MEMORY,Chapter16Test.CONSTRUCTOR_MEMORY, + Chapter18Test.CALL_MEMORY,Chapter18Test.RECURSIVE_MEMORY} ) { + var code = new CodeGen(src).parse().opto().typeCheck(); + var aliases = memoryPhis(code._stop); + code.loopTree().instSelect(cpu,"SystemV"); + assertEquals(aliases,memoryPhis(code._stop)); + code.GCM().localSched(); + code._stop.walk(n -> { + if( !(n instanceof CFGNode) && !(n instanceof ProjNode) ) + assertTrue(n.in(0) instanceof CFGNode); + if( n instanceof NewNode nn ) { + assertEquals(2,((TypeTuple)nn._type)._types.length); + assertTrue(nn.mem() instanceof MemMergeNode); + assertNull(nn.mem().in(1)); // Partial allocation input. + nn.cacheRegs(code); + MachNode mach = (MachNode)nn; + assertNull(mach.regmap(1)); + assertNotNull(mach.regmap(2)); + assertNotNull(mach.outregmap(0)); + assertNull(mach.outregmap(1)); + } + if( n instanceof CallNode call ) { + assertEquals(4,call.nIns()); // ctrl, memory, two args; direct target is embedded. + } + return null; + }); + } + } + + private static java.util.ArrayList<String> memoryPhis(Node stop) { + var phis = new java.util.ArrayList<String>(); + stop.walk(n -> { + if( n instanceof MemPhiNode phi ) phis.add("alias:"+phi._alias); + if( n instanceof BulkMemPhiNode phi ) phis.add("bulk:"+phi._aliases); + assertFalse(n instanceof PhiNode && n.isMem() && + !(n instanceof MemPhiNode) && !(n instanceof BulkMemPhiNode) && !(n instanceof ParmNode)); + return null; + }); + java.util.Collections.sort(phis); + return phis; + } + + @Test public void testFoldedReadBeforeWrites() { + var code = new CodeGen(""" + struct S { int x; }; + S !a = new S { x=11; }; S !b = new S { x=22; }; + S !p = a; if( arg ) p = b; + int before = p.x + arg; + a.x=33; b.x=44; + return before; + """).parse().opto().typeCheck().loopTree().instSelect("x86_64_v2","SystemV").GCM().localSched(); + var read = (MemOpNode)code._stop.walk(n -> + n instanceof com.seaofnodes.simple.node.cpus.x86_64_v2.AddMemX86 ? n : null); + assertNotNull(read); + int writes = 0; + for( Node use : read.antiDeps() ) + if( use instanceof MemOpNode st && st.isMem() ) { + assertSame(read.cfg0(),st.cfg0()); + assertTrue(st._inputs.find(read)>=0); + assertTrue(read.cfg0()._outputs.find(read)<st.cfg0()._outputs.find(st)); + assertNull(((MachNode)st).regmap(st._inputs.find(read))); + writes++; + } + assertEquals(1,writes); // The next Store follows this first clobber through memory. + } } diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Chapter20Test.java b/chapter24/src/test/java/com/seaofnodes/simple/Chapter20Test.java index 4e7cb156..2035ef9b 100644 --- a/chapter24/src/test/java/com/seaofnodes/simple/Chapter20Test.java +++ b/chapter24/src/test/java/com/seaofnodes/simple/Chapter20Test.java @@ -115,7 +115,7 @@ val sqrt = { int x -> }; return sqrt(arg) + sqrt(arg+2); """; - testTarget(src,"x86_64_v2", "SystemV",48,null); + testTarget(src,"x86_64_v2", "SystemV",40,null); testTarget(src,"riscv" , "SystemV",17,null); testTarget(src,"arm" , "SystemV",18,null); } @@ -162,7 +162,7 @@ for( int i=0; i<ary#-1; i++ ) ary[i+1] += ary[i]; return ary[1] * 1000 + ary[3]; // 1 * 1000 + 6 """; - testTarget(src,"x86_64_v2", "SystemV",9,"return .[];"); + testTarget(src,"x86_64_v2", "SystemV",5,"return .[];"); testTarget(src,"riscv" , "SystemV",7,"return (add,.[],(mul,.[],1000));"); testTarget(src,"arm" , "SystemV",5,"return (add,.[],(mul,.[],1000));"); } @@ -205,7 +205,7 @@ s.cs[0] = 67; // C s.cs[1] = 108; // l hashCode(s); """; - testTarget(src,"x86_64_v2", "SystemV",0,null); + testTarget(src,"x86_64_v2", "SystemV",9,null); testTarget(src,"riscv" , "SystemV",0,null); testTarget(src,"arm" , "SystemV",0,null); } @@ -366,8 +366,29 @@ return a; """; testTarget(src,"x86_64_v2", "SystemV",52,null); testTarget(src,"riscv" , "SystemV",44,null); - testTarget(src,"arm" , "SystemV",44,null); + testTarget(src,"arm" , "SystemV",41,null); //assertEquals("int[ 1,2,3,4,5,6,7,8,9,10,11]", Eval2.eval(code, 11)); } + + @Test public void testMemoryAllocation() { + for( String cpu : new String[]{"x86_64_v2","riscv","arm"} ) + for( String src : new String[]{Chapter10Test.NESTED_MEMORY,Chapter16Test.CONSTRUCTOR_MEMORY, + Chapter18Test.CALL_MEMORY,Chapter18Test.RECURSIVE_MEMORY, + "int[] !a = new int[3]; a[arg]=arg; return a[0]+1;"} ) { + var code = new CodeGen(src).parse().opto().typeCheck().loopTree() + .instSelect(cpu,"SystemV").GCM().localSched().regAlloc(); + com.seaofnodes.simple.codegen.RegAllocTestSupport.checkRegisters(code); + code._stop.walk(n -> { + // Whole memory, precise Phis, and memory projections need no register. + if( n._type instanceof com.seaofnodes.simple.type.TypeMem ) + assertEquals(-1,code._regAlloc.regnum(n)); + if( n instanceof com.seaofnodes.simple.node.NewNode nn ) { + assertTrue(code._regAlloc.regnum(nn.proj(0))>=0); + assertEquals(-1,code._regAlloc.regnum(nn.proj(1))); + } + return null; + }); + } + } } diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Chapter21AllocTest.java b/chapter24/src/test/java/com/seaofnodes/simple/Chapter21AllocTest.java index 2d0493e5..bab7a86a 100644 --- a/chapter24/src/test/java/com/seaofnodes/simple/Chapter21AllocTest.java +++ b/chapter24/src/test/java/com/seaofnodes/simple/Chapter21AllocTest.java @@ -21,7 +21,7 @@ val sqrt = { int x -> }; return sqrt(arg) + sqrt(arg+2); """; - Chapter21Test.testCPU(src,"x86_64_v2", "Win64" ,48,null); + Chapter21Test.testCPU(src,"x86_64_v2", "Win64" ,40,null); Chapter21Test.testCPU(src,"riscv" , "SystemV",19,null); Chapter21Test.testCPU(src,"arm" , "SystemV",19,null); } @@ -49,7 +49,7 @@ return sqrt(arg) + sqrt(arg+2); String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/stringHash21.smp")); Chapter21Test.testCPU(src,"x86_64_v2", "SystemV", 9,null); Chapter21Test.testCPU(src,"riscv" , "SystemV", 3,null); - Chapter21Test.testCPU(src,"arm" , "SystemV", 3,null); + Chapter21Test.testCPU(src,"arm" , "SystemV", 1,null); } } diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/chapter24/src/test/java/com/seaofnodes/simple/Chapter21Test.java index dcdda33b..b57c7ddd 100644 --- a/chapter24/src/test/java/com/seaofnodes/simple/Chapter21Test.java +++ b/chapter24/src/test/java/com/seaofnodes/simple/Chapter21Test.java @@ -196,6 +196,7 @@ public class Chapter21Test { @Test public void testCoalescing() { com.seaofnodes.simple.codegen.RegAllocTestSupport.coalescing(); } + @Test public void testCopyForward() throws Exception { com.seaofnodes.simple.codegen.RegAllocTestSupport.copyForward(); } @Test public void testNarrowStores() throws IOException { for( String type : new String[]{"i8","u8","i16","u16"} ) { @@ -392,7 +393,7 @@ val _hashCodeString = { String self -> """; testCPU(src,"x86_64_v2", "SystemV", 9,null); testCPU(src,"riscv" , "SystemV", 3,null); - testCPU(src,"arm" , "SystemV", 3,null); + testCPU(src,"arm" , "SystemV", 1,null); } @Test public void testStringExport() throws IOException { diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Chapter23AllocTest.java b/chapter24/src/test/java/com/seaofnodes/simple/Chapter23AllocTest.java index cd8c43cf..7f4b86d5 100644 --- a/chapter24/src/test/java/com/seaofnodes/simple/Chapter23AllocTest.java +++ b/chapter24/src/test/java/com/seaofnodes/simple/Chapter23AllocTest.java @@ -11,7 +11,7 @@ public class Chapter23AllocTest { String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/stringHash.smp")); Chapter23Test.testCPU(src,"x86_64_v2", "SystemV", 9,null); Chapter23Test.testCPU(src,"riscv" , "SystemV", 3,null); - Chapter23Test.testCPU(src,"arm" , "SystemV", 3,null); + Chapter23Test.testCPU(src,"arm" , "SystemV", 1,null); } @Test diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Chapter24AllocTest.java b/chapter24/src/test/java/com/seaofnodes/simple/Chapter24AllocTest.java index a320e21f..f8c88fd7 100644 --- a/chapter24/src/test/java/com/seaofnodes/simple/Chapter24AllocTest.java +++ b/chapter24/src/test/java/com/seaofnodes/simple/Chapter24AllocTest.java @@ -25,7 +25,7 @@ val sqrt = { int x -> int cast_int = arg+2; return sqrt(arg) + sqrt(cast_int); """; - Chapter24Test.testCPU(src,"x86_64_v2", "Win64" ,48,null); + Chapter24Test.testCPU(src,"x86_64_v2", "Win64" ,40,null); Chapter24Test.testCPU(src,"riscv" , "SystemV",19,null); Chapter24Test.testCPU(src,"arm" , "SystemV",19,null); } diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Chapter24Test.java b/chapter24/src/test/java/com/seaofnodes/simple/Chapter24Test.java index 93c778c8..b98a95b7 100644 --- a/chapter24/src/test/java/com/seaofnodes/simple/Chapter24Test.java +++ b/chapter24/src/test/java/com/seaofnodes/simple/Chapter24Test.java @@ -516,7 +516,7 @@ var stack11 = { int x -> """ return 0 < arg < arg+1 < 4; """; - TestC.run(src, "stacked_r_11x", null, stack11,0); + TestC.run(src, "stacked_r_11x", null, stack11,2); // Evaluate on RISC5 emulator EvalRisc5 R5 = TestRisc5.build("stacked_r_11", src2, 1, 2, false); @@ -525,7 +525,7 @@ return 0 < arg < arg+1 < 4; assertEquals(1,R5.regs[riscv.A0]); // Evaluate on ARM emulator - EvalArm64 arm = TestArm64.build("stacked_r_11", src2, 1, 2, false); + EvalArm64 arm = TestArm64.build("stacked_r_11", src2, 1, 0, false); trap = arm.step(100); assertEquals(0,trap); assertEquals(1,arm.regs[0]); diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Eval2.java b/chapter24/src/test/java/com/seaofnodes/simple/Eval2.java index fc826e04..e0d56399 100644 --- a/chapter24/src/test/java/com/seaofnodes/simple/Eval2.java +++ b/chapter24/src/test/java/com/seaofnodes/simple/Eval2.java @@ -314,6 +314,8 @@ public abstract class Eval2 { case TypeFunPtr tfp -> tfp; case TypeMem mem -> "MEM"; case TypeMemPtr tmp -> { + // Unknown-caller inputs are type placeholders, not heap constants. + if( !tmp.isConstant() ) yield tmp; if( tmp._obj.isAry() ) { // Constant array ptr Type elem = tmp._obj.field("[]")._t; if( elem instanceof TypeConAry con ) { @@ -350,7 +352,7 @@ public abstract class Eval2 { private static Object alloc(NewNode alloc) { TypeStruct type = alloc._ptr._obj; if( type.isAry() ) { - long sz = (Long)val(alloc.in(1)); + long sz = (Long)val(alloc.size()); long x = offToIdx(sz, type); int n = (int)x; if( n!=x || n<0 ) @@ -471,9 +473,9 @@ public abstract class Eval2 { case TypeRPC rpc -> sb.p(x.toString()); case TypeMem mem -> sb.p("$mem"); case TypeTuple tt -> { - if( tt._types.length>1 && tt._types[1] instanceof TypeMemPtr ) + if( tt._types.length>1 && tt._types[0] instanceof TypeMemPtr ) // Assume a NewNode - yield _print( tt._types[1], x, sb, visit ); + yield _print( tt._types[0], x, sb, visit ); throw Utils.TODO(); } case Type tt -> { diff --git a/chapter24/src/test/java/com/seaofnodes/simple/EvalArm64.java b/chapter24/src/test/java/com/seaofnodes/simple/EvalArm64.java index bd425908..c24b47bb 100644 --- a/chapter24/src/test/java/com/seaofnodes/simple/EvalArm64.java +++ b/chapter24/src/test/java/com/seaofnodes/simple/EvalArm64.java @@ -549,10 +549,13 @@ public class EvalArm64 { break; } - case 0xD2: { + case 0xD2: // MOVZ + case 0xF2: { // MOVK preserves the other halfwords // movz // get immediate - rval = (ir >> 5) & 0x7FFF; + int shift = ((ir >> 21) & 3)*16; + rval = ((ir >> 5) & 0xFFFFL) << shift; + if( opcode1==0xF2 ) rval |= regs[rdid] & ~(0xFFFFL << shift); break; } diff --git a/chapter24/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java b/chapter24/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java new file mode 100644 index 00000000..cd1c972d --- /dev/null +++ b/chapter24/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java @@ -0,0 +1,177 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegAllocTestSupport; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.cpus.arm.arm; +import com.seaofnodes.simple.node.cpus.riscv.riscv; +import com.seaofnodes.simple.type.TypeMemPtr; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.Test; +import static org.junit.Assert.*; + +// Execution regressions are separate from the fixed spill-measurement cohorts. +public class MemoryEncodingTest { + @Test public void testLoopLoadSearch() { + String[] sources = { + """ + u8[] !program=new u8[106]; u8[] !out=new u8[0]; + int sum=0; + for(int i=0; i<arg; i++) { + sum+=program#; + if(i==1) out=new u8[i+1]; + else if(i==3) out=new u8[2]; + } + return sum+out#; + """, + // One possibly aliasing arm must reject the unchanged-memory proof. + """ + struct S { int x; }; S !a=new S; S !b=new S; + a.x=10; b.x=20; S !p=a; if(arg&1) p=b; + int sum=0; + for(int i=0; i<6; i++) { + if(i==2) p.x=30; + else if(i==4) b.x=40; + sum+=a.x; + } + return sum; + """, + // Unchanged memory does not imply an unchanged loop-carried pointer. + """ + struct S { int x; }; S !a=new S; S !b=new S; + a.x=10; b.x=20; S !p=a; int sum=0; + for(int i=0; i<6; i++) { + if(i==2) a.x=30; + sum+=p.x; + p=b; if(i&1) p=a; + } + return sum; + """, + // All arms fold, but their values differ: build a value Phi. + """ + struct S { int x; }; S !a=new S; a.x=1; int sum=0; + for(int i=0; i<6; i++) { + sum+=a.x; + if(i&1) a.x=2; + else if(i==2) a.x=3; + else a.x=4; + } + return sum; + """ + }; + for( int p=0; p<sources.length; p++ ) { + CodeGen code=new CodeGen(sources[p]).parse().opto(); + if( p==0 ) assertNull(code._stop.walk(n -> + n instanceof LoadNode ld && ld.ptr() instanceof ProjNode ptr && + ptr.in(0) instanceof NewNode nn && + nn.size()._type==com.seaofnodes.simple.type.TypeInteger.constant(110) ? ld : null)); + for( int arg=0; arg<7; arg++ ) { + long expected=switch(p) { + case 0 -> 106*arg+(arg>=2 ? 2 : 0); + case 1 -> (arg&1)==0 ? 140 : 60; + case 2 -> 130; + default -> 16; + }; + assertEquals(Long.toString(expected),Eval2.eval(code,arg)); + } + } + } + + private static final String[] SOURCES = { + Chapter16Test.CONSTRUCTOR_MEMORY, + Chapter18Test.CALL_MEMORY, + Chapter18Test.RECURSIVE_MEMORY, + Chapter18Test.INLINE_MEMORY, + "int[] !a = new int[7]; a[arg]=arg+3; return a[0]+1;", + """ + int[] !a = new int[7]; + for( int i=0; i<7; i++ ) a[i]=i+arg; + for( int i=0; i<6; i++ ) a[i+1]+=a[i]; + return a[6]; + """ + }; + + private static long expected(int program, long arg) { + return switch(program) { + case 0 -> arg==0 ? 111249 : 222351+arg; + case 1 -> arg==0 ? 10101325 : 20202322; + case 2 -> (10*(arg+1)+arg*(arg+1)/2)*100+10+arg; + case 3 -> 400944+arg*10001; + case 4 -> arg==0 ? 4 : 1; + case 5 -> 21+7*arg; + default -> throw new AssertionError(); + }; + } + + @Test public void testEmulatedMemory() throws IOException { + for( String target : new String[]{"riscv","arm"} ) + for( int p=0; p<SOURCES.length; p++ ) { + CodeGen code = new RegAllocTestSupport.CheckedCodeGen(SOURCES[p]).driver(target,"SystemV",null); + int entry=0; + for( var n : code._start._outputs ) + if( n instanceof FunNode fun && "main".equals(fun._name) ) + entry=code._encoding._opStart[fun._nid]; + for( int arg=0; arg<7; arg++ ) { + byte[] image = new byte[1<<20]; + byte[] bits = code._encoding.bits(); + System.arraycopy(bits,0,image,0,bits.length); + long result; + if( target.equals("riscv") ) { + EvalRisc5 cpu = new EvalRisc5(image,1<<16); + cpu._pc=entry; + cpu.regs[riscv.A0]=arg; + assertEquals(target+" program "+p+" arg "+arg,0,cpu.step(100000)); + result=cpu.regs[riscv.A0]; + } else { + EvalArm64 cpu = new EvalArm64(image,1<<16); + cpu._pc=entry; + cpu.regs[arm.X0]=arg; + assertEquals(target+" program "+p+" arg "+arg,0,cpu.step(100000)); + result=cpu.regs[arm.X0]; + } + assertEquals(target+" program "+p+" arg "+arg,expected(p,arg),result); + } + } + } + + @Test public void testNeverMemory() { + // A synthetic exit must retain both precise slices of a no-exit loop. + // Stop at scheduling: the existing allocator cannot coalesce TOP on + // the synthetic scalar return arm. + for( String target : new String[]{"x86_64_v2","riscv","arm"} ) { + CodeGen code = new CodeGen(""" + struct S { int x; int y; }; + S !a=new S; + if( arg ) while( 1 ) { a.x+=arg; a.y+=a.x; } + return a.x+a.y; + """).driver(CodeGen.Phase.LoopTree); + assertNotNull(code._stop.walk(n -> { + if( n instanceof MemMergeNode mem && + mem.in(0) instanceof CProjNode exit && exit.in(0) instanceof NeverNode ) { + var fields = ((TypeMemPtr)Parser.TYPES.get("S"))._obj._fields; + assertTrue(mem.alias(fields[0]._alias) instanceof MemPhiNode); + assertTrue(mem.alias(fields[1]._alias) instanceof MemPhiNode); + return mem; + } + return null; + })); + code.instSelect(target,"SystemV").GCM().localSched(); + } + } + + @Test public void testNativeMemory() throws IOException { + Files.createDirectories(Path.of("build/objs")); + for( int p=0; p<SOURCES.length; p++ ) { + String file="build/objs/memory"+p; + StringBuilder c = new StringBuilder("extern long long memory(long long);\nint main() {\n"); + for( int arg=0; arg<7; arg++ ) + c.append("if(memory(").append(arg).append(")!=").append(expected(p,arg)).append("LL) return ").append(arg+1).append(";\n"); + c.append("return 0;\n}\n"); + Files.writeString(Path.of(file+".c"),c); + TestC.run("val memory = { int arg -> "+SOURCES[p]+" };", + TestC.CALL_CONVENTION,null,"",file+".c",file,"S","",-1); + } + } +} diff --git a/chapter24/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java b/chapter24/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java index c0a26d60..0ccd9741 100644 --- a/chapter24/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java +++ b/chapter24/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java @@ -246,6 +246,31 @@ public class RegAllocTestSupport { } } + public static void copyForward() throws Exception { + // Forwarding, fixed operand, two-address tie, intervening clobber, + // another user, and a scheduling-only edge. + for( int kind=0; kind<6; kind++ ) { + CodeGen code = graph(); + RegAlloc alloc = new RegAlloc(code); + Op def = new Op(A,null,null,false,code._start); + alloc.newLRG(def)._reg=0; + SplitNode copy = code._mach.split("test",(byte)0,null); + copy.setDef(0,code._start); copy.setDef(1,def); + alloc.newLRG(copy)._reg=1; + if( kind==3 ) new Op(null,null,A,false,code._start); + int twoAddress = kind==2 ? 1 : 0; + RegMask mask = kind==1 ? B : kind==5 ? null : new RegMask(3L); + Op use = new Op(null,mask,null,false,code._start,copy) { + @Override public int twoAddress() { return twoAddress; } + }; + if( kind==4 ) new Op(null,B,null,false,code._start,copy); + var post = RegAlloc.class.getDeclaredMethod("postColor"); + post.setAccessible(true); post.invoke(alloc); + assertSame("copy case "+kind,kind==0 ? def : copy,use.in(1)); + assertEquals(kind==0 ? 0 : 1,alloc._spills); + } + } + public static void coalescing() { // Merge, incompatible masks, interference, capacity, and adjacency remapping. for( int kind=0; kind<5; kind++ ) { diff --git a/chapter24/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/chapter24/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java index 911acc5c..1c116162 100644 --- a/chapter24/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java +++ b/chapter24/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java @@ -184,7 +184,7 @@ public class Evaluator { Object[] body=null; int num; if( type.isAry() ) { - long sz = (Long)val(alloc.in(1)); + long sz = (Long)val(alloc.size()); long n = offToIdx(sz, type); if( n < 0 ) throw new NegativeArraySizeException(""+n); @@ -198,21 +198,17 @@ public class Evaluator { assert elem instanceof TypeMemPtr; } // Length value - body[0] = vall(alloc.in(2+2)); + body[0] = n; } else { body = new Object[num = type._fields.length]; for( int i=0; i<num; i++ ) body[i] = switch( alloc._ptr._obj._fields[i]._t ) { case TypeInteger ti -> 0L; - case TypeFloat tf -> 0; + case TypeFloat tf -> 0D; default -> null; }; } - Object[] mems = new Object[type._fields.length+2]; - // mems[0] is control - mems[1] = new Obj(type,body); // the ref - // mems[2+...] are memory aliases - return mems; + return new Object[]{new Obj(type,body),null}; } private Object load(LoadNode load) { diff --git a/chapter25/README.md b/chapter25/README.md index 6d1d5e99..b7925a1f 100644 --- a/chapter25/README.md +++ b/chapter25/README.md @@ -9,6 +9,16 @@ These are the contents of the current snapshot. Independent correctness fixes will move to earlier chapters first; moving the larger SSA redesign backward, or splitting this chapter, is deferred. See the [backport queue](../docs/chapter-backports.md). +The memory work from Chapters 21-24 carries forward here: Load search proves +all arms of a merge before moving a read out of a loop, Phi factoring checks +memory clobbers, and scheduling follows aliases through memory aggregates. +The search follows `EscapeNode` to private or public memory and waits for +unresolved Store aliases. Array-length reads have no element bounds check; +array-element reads retain control. Integer value Phis use the existing typed +conversion machinery to preserve the load's width across loop widening. +New still produces private constructor memory, with publication handled by +Escape; the earlier chapters' allocation-input design does not replace it. + You can also read [this chapter](https://github.com/SeaOfNodes/Simple/tree/linear-chapter25) in the [linear history](https://github.com/SeaOfNodes/Simple/tree/linear) and [compare it with Chapter 24](https://github.com/SeaOfNodes/Simple/compare/linear-chapter24...linear-chapter25). @@ -163,6 +173,22 @@ first allocator. Shared legality fixes still start in the earliest affected chapter. The final audit corrected fixed-neighbor color bias in 20-25, narrow store masks in 20-21, and ARM register-bank/float-memory encoding in 21-25. +Chapter 21's adjacent-copy forwarding carries forward here as well: a copy +used only by the next instruction is removed when the operand accepts its +source register and is not tied to the instruction's result. + +The table below is the **earlier audit**, before adjacent-copy forwarding. +The replay has ten String-constructor parse failures, also reproduced +with the original allocator. On the identical 216 successful allocations, +adjacent-copy forwarding reduced 2,563 / 5,496 moves / weighted moves to +2,494 / 5,399. The subsequent memory corrections produce 2,488 / 5,379 on +those same 216 successful allocations. These are partial comparisons, not +replacement complete-suite totals. The +current cohort 25 has 14 allocations, including later C return ABI checks; +the historical table has ten. Native cohort-25 checks and fresh system-library +encoding pass in both runs. See the [backport record](../docs/chapter-backports.md) +for the pending fixture failures. + Run `make spill-stats`. Each row uses **Chapter 25's compiler**. The first five rows preserve the earlier 212 program/target entries, adapted to this chapter's syntax and library organization; the [fixture notes](src/test/java/com/seaofnodes/simple/spill/README.md) diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java index ecd23c1e..484bcaa9 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java @@ -172,17 +172,9 @@ public abstract class GlobalCodeMotion { // Loads need their memory inputs' uses also done if( n instanceof MemOpNode ld && ld._isLoad ) - for( Node memuse : ld.mem()._outputs ) - if( late[memuse._nid]==null && - // New makes new memory, never crushes load memory - !(memuse instanceof NewNode) && - // Load-use directly defines memory - (memuse._type instanceof TypeMem || - // Load-use directly defines memory - memuse instanceof CallNode) ) { - assert !( memuse._type instanceof TypeTuple tt && tt._types[ld._alias] instanceof TypeMem ); + for( Node memuse : ld.antiDeps() ) + if( late[memuse._nid]==null ) continue outer; - } // All uses done, schedule _doSchedLate(n,ns,late,anti); @@ -193,9 +185,7 @@ public abstract class GlobalCodeMotion { for( Node def : n._inputs ) { if( def==null ) continue; if( late[def._nid]==null ) work.push(def); - for( Node out : def._outputs ) - if( out instanceof MemOpNode ld && ld._isLoad && late[ld._nid]==null ) - work.push(ld); + wakeLoads(def,late,work,new BitSet()); } if( n instanceof LoopNode loop ) for( Node phi : loop._outputs ) @@ -255,25 +245,32 @@ public abstract class GlobalCodeMotion { best instanceof IfNode; } + // A writer may consume a load's memory through an aggregate. + private static void wakeLoads(Node def, CFGNode[] late, WorkList<Node> work, BitSet visit) { + if( visit.get(def._nid) ) return; + visit.set(def._nid); + for( Node out : def._outputs ) + if( out instanceof MemOpNode ld && ld._isLoad && late[ld._nid]==null ) work.push(ld); + if( def instanceof MemMergeNode ) + for( int i=1; i<def.nIns(); i++ ) + if( def.in(i)!=null ) wakeLoads(def.in(i),late,work,visit); + } + private static CFGNode find_anti_dep(CFGNode lca, MemOpNode load, CFGNode early, CFGNode[] late, int[] anti) { // We could skip final-field loads here. // Walk LCA->early, flagging Load's block location choices for( CFGNode cfg=lca; cfg!=early.idom(); cfg = cfg.idom() ) anti[cfg._nid] = load._nid; // Walk load->mem uses, looking for Stores causing an anti-dep - for( Node mem : load.mem()._outputs ) { + for( Node mem : load.antiDeps() ) { switch( mem ) { case MemOpNode st: - if( !st._isLoad && load._alias == st._alias ) { - assert late[mem._nid] != null; - lca = anti_dep(load,late[mem._nid],lca,st,anti); - } - break; // Loads do not cause anti-deps on other loads - case EscapeNode esc: - if( load._alias == esc.fld()._alias ) { - assert late[mem._nid] != null; - lca = anti_dep(load,late[mem._nid],lca,mem,anti); - } + assert late[st._nid]!=null; + lca = anti_dep(load,late[st._nid],lca,st,anti); + break; + case EscapeNode st: + assert late[st._nid]!=null; + lca = anti_dep(load,late[st._nid],lca,st,anti); break; case CallNode call: assert late[call._nid]!=null; @@ -286,8 +283,6 @@ public abstract class GlobalCodeMotion { if( phi.in(i)==load.mem() ) lca = anti_dep(load,phi.region().cfg(i),lca,null,anti); break; - case ReturnNode ret: break; // Load must already be ahead of Return - case MemMergeNode ret: break; // Mem uses now on ScopeMin default: throw Utils.TODO("Should not reach here"); } } @@ -299,7 +294,7 @@ public abstract class GlobalCodeMotion { // Stores are already placed. Constrain the load at that block only. if( anti[stblk._nid]==load._nid ) { lca = stblk.domLCA(lca,null); - if( lca==stblk && st!=null && st._inputs.find(load) == -1 ) + if( lca==stblk && st!=null && !(st instanceof CallNode) && st._inputs.find(load) == -1 ) st.addDef(load); // Same-block load must precede the store. } return lca; diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java index 196a1142..97bde2c9 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java @@ -694,8 +694,8 @@ public class RegAlloc { if( defreg != usereg && splitBypass(bb,j,n,defreg) ) usereg = lrg(n.in(1))._reg; - // Split has same reg? Useless! Can remove it! - if( defreg == usereg ) { + // Same register, or the next instruction can use the source directly. + if( defreg == usereg || splitForward(bb,j,n,usereg) ) { n.removeSplit(); j--; continue; @@ -708,6 +708,18 @@ public class RegAlloc { } } + // A sole, adjacent use can read the source register directly. Nothing can + // clobber it in between; only the operand mask and two-address tie remain. + private boolean splitForward( CFGNode bb, int j, Node split, int reg ) { + if( split.nOuts()!=1 || j+1==bb.nOuts() ) return false; + Node use = split.out(0); + if( use!=bb.out(j+1) || use instanceof PhiNode || use instanceof CFGNode || + !(use instanceof MachNode mach) ) return false; + int i = use._inputs.find(split); + RegMask mask = mach.regmap(i); + return mask!=null && mask.test(reg) && mach.twoAddress()!=i; + } + private boolean splitBypass( CFGNode bb, int j, Node lo, int defreg ) { // Attempt to bypass split Node hi = lo.in(1); diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java index cabf7d69..82c4af10 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java @@ -253,7 +253,7 @@ public class BulkMemPhiNode extends PhiNode { mphi.setType(TypeMem.BOT.makeFrom(alias)); for( int i=1; i<nIns(); i++ ) mphi.addDef(CodeGen.CODE.add(preciseInput(in(i),alias))); - return (MemPhiNode)mphi.peephole(); + return CodeGen.CODE.add(mphi); } MemPhiNode _findPhi(int alias) { diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java index 193fbe17..85ca42cd 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -31,6 +31,25 @@ public class LoadNode extends MemOpNode { } @Override public Tag serialTag() { return Tag.Load; } + @Override boolean canDrop(MemOpNode other, Node dep) { + return super.canDrop(other,dep) && !clobbered(dep); + } + + // Check only immediate memory users. Writers clobber memory; Phis and + // aggregates might lead to a clobber, so stop rather than search further. + private boolean clobbered(Node dep) { + Node mem = mem(); + dep.addDepForwards(mem); + for( Node use : mem._outputs ) { + if( use==null ) continue; + dep.addDepForwards(use); + if( use instanceof StoreNode || use instanceof PhiNode || + use instanceof MemMergeNode || + use instanceof NewNode || use instanceof EscapeNode || use instanceof CallNode ) return true; + } + return false; + } + // Debugger label @Override public String label() { return "ld_"+mlabel(); } @Override @@ -89,10 +108,10 @@ public class LoadNode extends MemOpNode { Node mem = mem(); Node ptr = ptr(); - // Loads into structs do not need a ctrl edge, as null-ptr checking is - // baked into the type system. Loads into arrays DO need the ctrl - // edge, at least until proper range-checking is in place. - if( in(0)!=null && ptr._type instanceof TypeMemPtr tmp && !tmp._obj.isAry() ) { + // Null checks are handled by types. Array elements retain control for + // range checks, but reading an array's length has no index to check. + if( in(0)!=null && ptr._type instanceof TypeMemPtr tmp && + (!tmp._obj.isAry() || _name.equals("#")) ) { setDef(0,null); return this; } @@ -112,19 +131,6 @@ public class LoadNode extends MemOpNode { if( _alias == 1 ) return null; - // External storage is shared even through different class namespaces. - boolean external = ptr._type instanceof TypeMemPtr tmp && - (fld=tmp._obj.field(_name)) != null && fld._extern; - - // Simple Load-after-Store on same address. - if( mem instanceof StoreNode st && - ptr == st.nnptr() && - off() == st.off() && // Must check same object - st.val()._type.isa(_type) ) { - assert _name.equals(st._name); // Equiv class aliasing is perfect - return extend(st.val()); - } - if( mem instanceof MemMergeNode merge ) { setDef(1,merge.alias(_alias)); // In support of uplifting control to a prior dominating load, if @@ -144,67 +150,9 @@ public class LoadNode extends MemOpNode { addDep(ld)._type.isa(_type) ) // and not rolling backwards return ld; - // Load-after-Store on same address, but bypassing provably unrelated - // stores. This is a more complex superset of the above two peeps. - // "Provably unrelated" is really weak. - if( ptr instanceof ReadOnlyNode ro ) - ptr = ro.in(1); - outer: - while( true ) { - switch( mem ) { - case StoreNode st: - if( ptr == addDep(st.ptr()) && off() == st.off() ) - return extend(castRO(st.val())); // Proved equal - // Can we prove unequal? Offsets do not overlap? - if( !off()._type.join(st.off()._type).isHigh() && // Offsets overlap - (external || !neverAlias(ptr,st.ptr())) ) { // And might alias - addDep( off()); // Offsets can fold, proving unequal - addDep(st.off()); // Offsets can fold, proving unequal - break outer; // Cannot tell, stop trying - } - // Pointers cannot overlap - mem = st.mem(); // Proved never equal - break; - case PhiNode phi: - // Assume related - addDep(phi); - break outer; - case ConstantNode con: - // Load from constant memory - if( con._con instanceof TypeMem tmem ) - throw Utils.TODO("need to see a test case"); - // return ConstantNode.make(tmem._t); - break outer; // Assume shortly dead - case ProjNode mproj: // Memory projection - switch( mproj.in(0) ) { - case NewNode nnn1: - if( external ) break outer; // Class allocation does not initialize C storage - // Direct load from fresh zero/default-filled allocation. - Type decl = declaredType(); - assert decl!=Type.BOTTOM; - Type zero = decl.makeZero(); - assert zero.isa(_type); // Catch bug uninitialized null field - return ConstantNode.make(zero); - case CallEndNode cend: addDep(mproj); break outer; // TODO: Bypass no-alias call - default: throw Utils.TODO("Should not reach here"); - } - case MemMergeNode merge: mem = merge.alias(_alias); break; - case EscapeNode esc: - if( esc.self()==ptr ) // Proved equal - { mem = esc.priv(); break; } - // Mal-formed, dying - if( !(esc.self().in(0) instanceof NewNode) ) - break outer; - // Two NewNodes are always unequal - if( ptr.in(0) instanceof NewNode ) - { mem = esc.pub(); break; } - // TODO: Can we prove unequal? - break outer; - - default: - throw Utils.TODO(); - } - } + Node win = find(mem,ptr,null,null); + if( win!=null ) return folded(win); + if( ptr instanceof ReadOnlyNode ro ) ptr=ro.in(1); // 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 @@ -213,34 +161,119 @@ 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 && + // Array access control must not be moved across the merge. + in(0)==null && // Offset can be hoisted off() instanceof ConstantNode && - // Not control dependent - in(0)==null && // Pointer can be hoisted hoistPtr(ptr,memphi) ) { - // 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 = ld(1); - Node ld2 = ld(2); - PhiNode phi = new PhiNode(_name, memphi.region(),ld1,ld2); - phi.setType(phi.compute().join(_type)); - return phi; + // Returning to this memory means no change only if the pointer + // is also loop invariant. A pointer Phi may select another object. + Node stop = memphi.region() instanceof LoopNode && + !(ptr instanceof PhiNode p && p.region()==memphi.region()) ? memphi : null; + if( find(memphi.in(2),ptr,stop,new BitSet())!=null || + (!(memphi.region() instanceof LoopNode) && find(memphi.in(1),ptr,null,new BitSet())!=null) ) { + // Integer loop Phis widen to i64. Keep the load's declared + // width at the result, as for ordinary typed loop variables. + if( _type instanceof TypeInteger && !declaredType().isa(_type) ) return null; + PhiNode phi = new PhiNode(_name,memphi.region()).keep(); + phi.setType(_type instanceof TypeInteger ? TypeInteger.BOT : _type); + for( int i=1; i<memphi.nIns(); i++ ) { + Node p = ptr instanceof PhiNode pp && pp.region()==memphi.region() ? pp.in(i) : ptr; + phi.addDef(load(memphi.in(i),p,stop,phi)); + } + CodeGen.CODE.add(phi.unkeep()); + return _type instanceof TypeInteger ? new ConvertNode(declaredType(),phi) : phi; } } return null; } - private Node ld( int idx ) { - Node mem = mem(), ptr = ptr(); - assert in(0)==null; - assert !(ptr instanceof PhiNode && ptr.in(0)==mem.in(0)); // If fails, need to use pre-merged ptr at same phi - return new LoadNode(_loc,_name,_alias,null,mem.in(idx),ptr, off()).peephole(); + // Load a flavored zero from a New + private Node zero(NewNode nnn) { + Type zero = declaredType().makeZero(); + assert zero.isa(_type); // Catch an uninitialized non-null field + return castRO(new ConstantNode(zero).peephole()); + } + + // Search only; no new nodes or rewiring. Return a Store/New that folds, + // the unchanged loop memory, or a merge whose every arm folds. Null fails. + // Register dependencies so a later pointer/offset rewrite retries the query. + private Node find(Node mem, Node ptr, Node stop, BitSet visit) { + if( ptr instanceof ReadOnlyNode ro ) ptr=ro.in(1); + Field fld; + boolean external = ptr._type instanceof TypeMemPtr tmp && + (fld=tmp._obj.field(_name))!=null && fld._extern; + while( mem!=null ) { + addDep(mem); + if( mem==stop ) return mem; + switch( mem ) { + case MemMergeNode merge: mem=merge.alias(_alias); break; + case StoreNode st: + // Alias 1 is still unresolved, not a proven different field. + if( st._alias==1 ) return null; + addDep(st.ptr()); + if( _alias==st._alias && (ptr==st.ptr() || ptr==st.nnptr()) && off()==st.off() ) { + // A full-width load cannot narrow the forwarded value. + if( declaredType() instanceof TypeInteger ti && ti.log_size()==3 && + !addDep(st.val())._type.isa(_type) ) return null; + return st; + } + if( (external || _alias==st._alias) && (external || !neverAlias(ptr,st.ptr())) && + !addDep(off())._type.join(addDep(st.off())._type).isHigh() ) return null; + mem=st.mem(); + break; + case ProjNode proj: + // New's memory is private; public memory is joined at Escape. + if( external || !(proj.in(0) instanceof NewNode nnn) ) return null; + if( declaredType()==Type.BOTTOM ) return null; + return nnn; + case EscapeNode esc: + if( external ) return null; + addDep(esc.self()); + if( esc.self()==ptr ) { mem=esc.priv(); break; } + if( neverAlias(ptr,esc.self()) ) { mem=esc.pub(); break; } + return null; + case MemPhiNode phi: + addDep(phi.region()); + // Only the original loop closes a successful cycle. Another + // loop or an unfinished merge needs a separate proof. + if( visit==null || !(phi.region() instanceof RegionNode r) || r instanceof LoopNode || + r.inProgress() || phi.inProgress() || r._type!=Type.CONTROL ) return null; + if( visit.get(phi._nid) ) return phi; // Already proved every arm. + for( int i=1; i<phi.nIns(); i++ ) + if( find(phi.in(i),ptr,stop,visit)==null ) return null; + visit.set(phi._nid); + return phi; + default: return null; + } + } + return null; + } + + private Node folded(Node win) { + return win instanceof StoreNode st ? extend(castRO(st.val())) : zero((NewNode)win); + } + + // Materialize only after the profitability search succeeds. The value Phi + // stands for a load which comes back around the loop with unchanged memory. + private Node load(Node mem, Node ptr, Node stop, PhiNode value) { + Node win=find(mem,ptr,stop,new BitSet()); + if( win!=null ) { + if( win==stop ) return value; + if( win instanceof MemPhiNode mp ) { + PhiNode phi=new PhiNode(_name,mp.region()).keep(); + for( int i=1; i<mp.nIns(); i++ ) phi.addDef(load(mp.in(i),ptr,stop,value)); + return CodeGen.CODE.add(phi.unkeep().peephole()); + } + Node fold=folded(win); + if( fold!=null ) return fold.peephole(); + } + LoadNode ld=new LoadNode(_loc,_name,_alias,in(0),mem,ptr,off()); + return ld.peephole(); } private static boolean neverAlias( Node ptr1, Node ptr2 ) { @@ -269,21 +302,6 @@ public class LoadNode extends MemOpNode { return false; } - // Profitable if we find a matching Store on this Phi arm. - private boolean profit(PhiNode memphi, int idx) { - Node px = memphi.in(idx); - if( px==null ) return false; - // Memory is collapsing, no bother - if( px._type instanceof TypeMem mem && mem._t.isHighOrConst() ) - return false; - if( px instanceof StoreNode st1 && ptr()==addDep(st1.nnptr() ) && off()==st1.off() && addDep(st1.val())._type.isa(_type) ) - // To avoid cyclic pushing a Load up then down, getting here means - // the load *must* match against the Store - return true; - addDep(px); - return false; - } - // Read-Only is a deep property, and cannot be cast-away private Node castRO(Node rez) { if( ptr()._type.isFinal() && !rez._type.isFinal() ) diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MemOpNode.java index 441edc3e..de7adede 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/MemOpNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/MemOpNode.java @@ -8,6 +8,7 @@ import com.seaofnodes.simple.util.BAOS; import com.seaofnodes.simple.util.Utils; import java.lang.StringBuilder; import java.util.BitSet; +import java.util.ArrayList; import java.util.HashMap; import java.util.IdentityHashMap; @@ -101,6 +102,13 @@ public abstract class MemOpNode extends TypeNode { public StringBuilder _printMach( StringBuilder sb, BitSet visited ) { throw Utils.TODO(); } + // Extra conditions for factoring matching memory operations through a Phi. + // The caller has already checked the opcode, input shape and control. + boolean canDrop(MemOpNode other, Node dep) { + dep.addDep(ptr()); + return _alias>1 && _alias==other._alias && declaredType()==other.declaredType() && err()==null; + } + @Override public boolean eq(Node n) { MemOpNode mem = (MemOpNode)n; // Invariant @@ -128,4 +136,30 @@ public abstract class MemOpNode extends TypeNode { return null; } + + // Memory writers which must follow this read. A packaging node is not a + // write; follow this alias through aggregates to its writers. + public ArrayList<Node> antiDeps() { + ArrayList<Node> deps = new ArrayList<>(); + antiDeps(mem(),deps,new BitSet()); + return deps; + } + + private void antiDeps(Node mem, ArrayList<Node> deps, BitSet visit) { + if( visit.get(mem._nid) ) return; + visit.set(mem._nid); + for( Node use : mem._outputs ) { + if( use instanceof MemMergeNode merge ) { + Node slice = _alias<merge.nIns() ? merge.in(_alias) : null; + if( (slice==mem) || (slice==null && merge.in(1)==mem) ) + antiDeps(merge,deps,visit); + } else if( use instanceof CallNode || + (use instanceof MemOpNode st && !st._isLoad && st._alias==_alias) || + (use instanceof EscapeNode esc && esc.fld()._alias==_alias) || + (use instanceof MemPhiNode phi && phi._alias==_alias) || + (use instanceof BulkMemPhiNode phi && !phi.isSplit(_alias)) ) { + if( !deps.contains(use) ) deps.add(use); + } + } + } } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter25/src/main/java/com/seaofnodes/simple/node/Node.java index 59c9f8e3..8842db78 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/Node.java @@ -306,6 +306,7 @@ public abstract class Node implements Cloneable { // Error is 'use' does not exist; ok for 'use' to be null. public boolean delUse( Node use ) { _outputs.del(_outputs.find(use)); + moveDepsToWorklist(); // User-count and anti-dependence queries can now change. return _outputs.isEmpty(); } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/PhiNode.java index a4c73472..9d8adb15 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/PhiNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/PhiNode.java @@ -154,67 +154,77 @@ public class PhiNode extends Node { // Same op on all Phi paths; all ops have only the Phi as a use. // None have a control input. private boolean same_op() { - Node busy=null; - if( in(1) instanceof ConstantNode ) - return false; - for( int i=1; i<nIns(); i++ ) { - Node op = in(i); - if( in(1).getClass() != op.getClass() || op.in(0)!=null || in(1).nIns() != op.nIns() ) - return false; // Wrong class or CFG bound or mismatched inputs - if( in(1) instanceof MemOpNode mem ) { - // Mismatched aliases - if( mem._alias != ((MemOpNode)op)._alias ) return false; - // Load is clobbered somewhere, and can not be pulled forward past the Phi? - if( mem instanceof LoadNode ) - for( Node use : op.outs() ) - if( use instanceof StoreNode ) - return false; - } - if( in(1) instanceof EscapeNode ) - return false; - if( in(1) instanceof MemMergeNode ) - return false; // Have to keep aliases straight - if( op.nOuts() > 1 ) { - if( busy==null ) busy = op; - else { // Too many users, but addDep in case lose users - for( Node out : op._outputs ) - if( out!=null && out!=this ) - addDep(out); - for( Node out : busy._outputs ) - if( out!=null && out!=this ) - addDep(out); + Node op = in(1); + if( op instanceof EscapeNode || op instanceof CFGNode || op instanceof ConstantNode || op instanceof PhiNode || + op instanceof ProjNode || op instanceof NewNode || op instanceof ScopeNode || op instanceof MemMergeNode ) return false; + // A bulk Phi must split its aliases before factoring precise Stores. + if( this instanceof BulkMemPhiNode ) return false; + // Bulk splitting currently identifies parallel slices by Region/alias. + // Factoring can introduce another slice at a different memory point; + // wait until that Region's bulk partitioning has finished. + if( op instanceof MemOpNode ) + for( Node use : region()._outputs ) + if( use instanceof BulkMemPhiNode ) { + addDepForwards(use); return false; } + Node busy = null; + for( int i=1; i<nIns(); i++ ) { + Node n = in(i); + if( addDep(region().in(i))._type==Type.XCONTROL || + op.getClass()!=n.getClass() || n.nIns()!=op.nIns() || n.in(0)!=null || !op.eq(n) ) return false; + if( n instanceof MemOpNode mem && !mem.canDrop((MemOpNode)op,this) ) return false; + // Moving a Store must remove the old effect, not duplicate it. + // Allow one shared arm: the other arms disappear into the single + // factored operation, usually reducing the total operation count. + addDepForwards(n); + if( n.nOuts()>1 ) { + for( Node use : n._outputs ) + if( use!=null && use!=this ) addDepForwards(use); + if( n instanceof StoreNode || busy!=null ) return false; + busy=n; } + for( int j=1; j<n.nIns(); j++ ) + if( n.in(j) instanceof ScopeNode || ((n.in(j)==null) != (op.in(j)==null)) ) return false; } return true; } private Node drop_same_op() { - assert !(in(1) instanceof CFGNode); - Node op = in(1); - Node cp = op.copy(); - cp._type = null; // Fresh type - cp.addDef(null); // No control - + Node op = in(1), cp = op.copyEmpty(); + cp._type = null; + cp.addDef(null); for( int j=1; j<op.nIns(); j++ ) { - boolean needsPhi = false; - Node x = op.in(j); // Jth input from sample #1 + Node x = op.in(j); + boolean different = false; for( int i=2; i<nIns(); i++ ) - if( in(i).in(j) != x ) - { needsPhi=true; break; } - if( needsPhi ) { - x = make(_label,op.in(j)._type); - x.addDef(region()); - for( int i=1; i<nIns(); i++ ) - x.addDef(in(i).in(j)); - x = x.peephole(); + if( in(i).in(j)!=x ) different=true; + if( different ) { + Node[] ins = new Node[nIns()]; + ins[0] = region(); + Type t = Type.TOP; + for( int i=1; i<nIns(); i++ ) { + ins[i] = in(i).in(j); + t = t.meet(ins[i]._type); + } + // Do not introduce an operand Phi mixing incompatible type families. + if( t==Type.BOTTOM ) { + for( int i=1; i<ins.length; i++ ) addDep(ins[i]); + cp.kill(); + return null; + } + PhiNode phi = j==1 && op instanceof MemOpNode mem + ? new MemPhiNode(_label,mem._alias,ins) + : PhiNode.make(_label,t,ins); + x = phi.peephole(); } cp.addDef(x); } - // Test not running backwards, which can happen for e.g. And's - if( cp.compute().isa(compute()) ) - return cp; + // Factoring must not widen the result (e.g. correlated And operands). + if( cp.compute().isa(compute()) ) return cp; + for( int i=1; i<nIns(); i++ ) + for( int j=1; j<in(i).nIns(); j++ ) + if( in(i).in(j)!=null ) addDep(in(i).in(j)); cp.kill(); return null; } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/RegionNode.java index 32434850..a886183e 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/RegionNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/RegionNode.java @@ -48,7 +48,7 @@ public class RegionNode extends CFGNode { if( !hasPhi() && nIns()>3 && // No Phi users, just a control user in(1) instanceof CProjNode p1 && in(2) instanceof CProjNode p2 && - addDep(p1.in(0))==addDep(p2.in(0)) && + addDepForwards(p1).in(0)==addDepForwards(p2).in(0) && p1.in(0) instanceof IfNode iff ) { // Replace with the iff.ctrl directly if( nIns()==3 ) return iff.ctrl(); diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java index 2683ddf5..fe379c80 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java @@ -49,6 +49,10 @@ public class StoreNode extends MemOpNode { baos.write(_size); } + @Override boolean canDrop(MemOpNode other, Node dep) { + return super.canDrop(other,dep) && _init==((StoreNode)other)._init; + } + // Debugger label @Override public String label() { return "st_"+mlabel(); } @Override public boolean isMem() { return true; } diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java index c55002c1..aca0eea1 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java @@ -24,6 +24,11 @@ public class LoadX86 extends MemOpX86 { } static void enc( Encoding enc, Type decl, short dst, short ptr, short idx, int off, int scale ) { + // Allocation may put a full-width integer in an XMM register (or a + // floating value in a GPR). Move its bits using the selected register bank. + if( dst>=x86_64_v2.XMM_OFFSET && decl.log_size()==3 ) decl=TypeFloat.F64; + else if( dst<x86_64_v2.XMM_OFFSET && decl instanceof TypeFloat ) + decl = decl==TypeFloat.F32 ? TypeInteger.U32 : TypeInteger.BOT; if( decl == TypeFloat.F32) enc.add1(0xF3); if( decl == TypeFloat.F64) enc.add1(0xF2); diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter11Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter11Test.java index 2e661901..d515df8d 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter11Test.java +++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter11Test.java @@ -152,6 +152,8 @@ return v.f; """); code.parse().opto(); assertEquals("return Phi(Loop,0,2);", code.print()); + for( int arg : new int[]{-1,0,1,3,100} ) + assertEquals("0",Eval2.eval(code,arg)); } @Test diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter20Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter20Test.java index 5ea198ef..e39c09e7 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter20Test.java +++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter20Test.java @@ -183,7 +183,7 @@ for( int i=0; i<ary#-1; i++ ) return ary[1] * 1000 + ary[3]; // 1 * 1000 + 6 """; testCPU(src,"x86_64_v2", "SystemV",-1,"return mov(.[]);"); - testCPU(src,"riscv" , "SystemV", 7,"return (add,.[],(mul,.[],1000));"); + testCPU(src,"riscv" , "SystemV", 9,"return (add,.[],(mul,.[],1000));"); testCPU(src,"arm" , "SystemV", 5,"return (add,.[],(mul,.[],1000));"); } @@ -225,7 +225,7 @@ val _hashCodeString = { String self -> """; testCPU(src,"x86_64_v2", "SystemV",21,null); testCPU(src,"riscv" , "SystemV",10,null); - testCPU(src,"arm" , "SystemV",10,null); + testCPU(src,"arm" , "SystemV",8,null); } @Test diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java index 88e963c0..08da0654 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java +++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java @@ -169,6 +169,7 @@ public class Chapter21Test { @Test public void testCoalescing() { com.seaofnodes.simple.codegen.RegAllocTestSupport.coalescing(); } + @Test public void testCopyForward() throws Exception { com.seaofnodes.simple.codegen.RegAllocTestSupport.copyForward(); } @Test public void testArmSubtractRegisters() { EvalArm64 cpu = new EvalArm64(new byte[16],16); @@ -278,7 +279,7 @@ for( int i=0; i<ary#-1; i++ ) return ary[1] * 1000 + ary[3]; // 1 * 1000 + 6 """; testCPU(src,"x86_64_v2", "SystemV",-1,"return mov(.[]);"); - testCPU(src,"riscv" , "SystemV", 7,"return (add,.[],(mul,.[],1000));"); + testCPU(src,"riscv" , "SystemV", 9,"return (add,.[],(mul,.[],1000));"); testCPU(src,"arm" , "SystemV", 5,"return (add,.[],(mul,.[],1000));"); } @@ -339,7 +340,7 @@ val _hashCodeString = { String self -> """; testCPU(src,"x86_64_v2", "SystemV",21,null); testCPU(src,"riscv" , "SystemV",10,null); - testCPU(src,"arm" , "SystemV",10,null); + testCPU(src,"arm" , "SystemV",8,null); } @Test public void testStringExport() throws IOException { @@ -499,7 +500,7 @@ val sieve = { int N -> // Evaluate on ARM5 emulator; expect return of an array of primes in // the simulated heap. - EvalArm64 A5 = TestArm64.build("sieve", src, 100, 105, false); + EvalArm64 A5 = TestArm64.build("sieve", src, 100, 88, false); int trap_arm = A5.step(10000); assertEquals(0, trap_arm); int ary_arm = (int)A5.regs[arm.X0]; diff --git a/chapter25/src/test/java/com/seaofnodes/simple/EvalArm64.java b/chapter25/src/test/java/com/seaofnodes/simple/EvalArm64.java index 86fcf96f..1503adb5 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/EvalArm64.java +++ b/chapter25/src/test/java/com/seaofnodes/simple/EvalArm64.java @@ -544,10 +544,13 @@ public class EvalArm64 { break; } - case 0xD2: { + case 0xD2: // MOVZ + case 0xF2: { // MOVK preserves the other halfwords // movz // get immediate - rval = (ir >> 5) & 0x7FFF; + int shift = ((ir >> 21) & 3)*16; + rval = ((ir >> 5) & 0xFFFFL) << shift; + if( opcode1==0xF2 ) rval |= regs[rdid] & ~(0xFFFFL << shift); break; } diff --git a/chapter25/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java b/chapter25/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java new file mode 100644 index 00000000..2786b403 --- /dev/null +++ b/chapter25/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java @@ -0,0 +1,104 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.node.*; +import java.util.BitSet; +import org.junit.Test; +import static org.junit.Assert.*; + +// Execution regressions are separate from the fixed spill-measurement cohorts. +public class MemoryEncodingTest { + @Test public void testLoopLoadSearch() { + String[] sources = { + """ + u8[] !program=new u8[106]; u8[] !out=new u8[0]; + int sum=0; + for(int i=0; i<arg; i++) { + sum+=program#; + if(i==1) out=new u8[i+1]; + else if(i==3) out=new u8[2]; + } + return sum+out#; + """, + // One possibly aliasing arm must reject the unchanged-memory proof. + """ + struct S { int x; }; S !a=new S; S !b=new S; + a.x=10; b.x=20; S !p=a; if(arg&1) p=b; + int sum=0; + for(int i=0; i<6; i++) { + if(i==2) p.x=30; + else if(i==4) b.x=40; + sum+=a.x; + } + return sum; + """, + // Unchanged memory does not imply an unchanged loop-carried pointer. + """ + struct S { int x; }; S !a=new S; S !b=new S; + a.x=10; b.x=20; S !p=a; int sum=0; + for(int i=0; i<6; i++) { + if(i==2) a.x=30; + sum+=p.x; + p=b; if(i&1) p=a; + } + return sum; + """, + // All arms fold, but their values differ: build a value Phi. + """ + struct S { int x; }; S !a=new S; a.x=1; int sum=0; + for(int i=0; i<6; i++) { + sum+=a.x; + if(i&1) a.x=2; + else if(i==2) a.x=3; + else a.x=4; + } + return sum; + """ + }; + // Exercise both unresolved Store aliases and nested value-Phi cleanup. + for( long seed : new long[]{0,9,126} ) + for( int p=0; p<sources.length; p++ ) { + CodeGen code=new CodeGen(sources[p],seed,true).parse().opto(); + if( p==0 ) assertNull(code._stop.walk(n -> + n instanceof LoadNode ld && ld.ptr() instanceof ProjNode ptr && + ptr.in(0) instanceof NewNode nn && + nn.size()._type==com.seaofnodes.simple.type.TypeInteger.constant(110) ? ld : null)); + for( int arg=0; arg<7; arg++ ) { + long expected=switch(p) { + case 0 -> 106*arg+(arg>=2 ? 2 : 0); + case 1 -> (arg&1)==0 ? 140 : 60; + case 2 -> 130; + default -> 16; + }; + assertEquals(Long.toString(expected),Eval2.eval(code,arg)); + } + } + } + + @Test public void testKeepReadsBeforeWrites() { + var code = new CodeGen(""" + struct S { int x; int y; }; + S !a = new S; S !b = new S; + a.x=arg; b.x=arg+1; + S !p=a; S !q=b; + if (arg<0) { p=b; q=a; } + int v; + if (arg>1) { v=p.x; p.x=41; p.y=5; } + else { v=q.x; q.x=42; } + return v; + """).parse().opto(); + StopNode stop = code._stop; + assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet())); + assertEquals("-1",Eval2.eval(code,-1)); + assertEquals("1",Eval2.eval(code,0)); + assertEquals("3",Eval2.eval(code,3)); + } + + private static int countMemoryNodes(Node n, Class<?> kind, BitSet seen) { + if (n==null || seen.get(n._nid)) return 0; + seen.set(n._nid); + int cnt=kind.isInstance(n) ? 1 : 0; + for (Node def : n._inputs) cnt+=countMemoryNodes(def,kind,seen); + return cnt; + } +} diff --git a/chapter25/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java b/chapter25/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java index 6c6b9ad5..873578c2 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java +++ b/chapter25/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java @@ -251,6 +251,31 @@ public class RegAllocTestSupport { } } + public static void copyForward() throws Exception { + // Forwarding, fixed operand, two-address tie, intervening clobber, + // another user, and a scheduling-only edge. + for( int kind=0; kind<6; kind++ ) { + CodeGen code = graph(); + RegAlloc alloc = new RegAlloc(code); + Op def = new Op(A,null,null,false,code._start); + alloc.newLRG(def,null)._reg=0; + SplitNode copy = code._mach.split(null,"test",(byte)0); + copy.setDef(0,code._start); copy.setDef(1,def); + alloc.newLRG(copy,null)._reg=1; + if( kind==3 ) new Op(null,null,A,false,code._start); + int twoAddress = kind==2 ? 1 : 0; + RegMask mask = kind==1 ? B : kind==5 ? null : new RegMask(3L); + Op use = new Op(null,mask,null,false,code._start,copy) { + @Override public int twoAddress() { return twoAddress; } + }; + if( kind==4 ) new Op(null,B,null,false,code._start,copy); + var post = RegAlloc.class.getDeclaredMethod("postColor"); + post.setAccessible(true); post.invoke(alloc); + assertSame("copy case "+kind,kind==0 ? def : copy,use.in(1)); + assertEquals(kind==0 ? 0 : 1,alloc._spills); + } + } + public static void coalescing() { // Merge, incompatible masks, interference, capacity, and adjacency remapping. for( int kind=0; kind<5; kind++ ) { diff --git a/docs/chapter-backports.md b/docs/chapter-backports.md index c2a4b56c..b50bd25d 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,292 @@ Splitting Chapter 25 is deferred, not an instruction to renumber. ## Pending corrections +### Chapter 15 allocation design (implemented) + +Chapters 12-14 retain explicit initialization Stores. Chapter 15 replaces them +with New's zeroing operation while keeping one parser-visible `$mem`. + +| Interface | Chapter 15 | +|---|---| +| New inputs | `{ctrl, $mem, size}` | +| New outputs | `{ptr, $mem}`; no control result or per-alias projections | +| Coverage | field aliases from New's internal TypeStruct/TypeMemPtr | +| Input memory | partial MemMerge, no default, entries for affected aliases | +| Result integration | whole-memory MemMerge, prior default, affected entries all using the same New memory projection | +| Memory types | existing alias plus stored-value type; no lattice extension | + +A null MemMerge default means absent aliases are uncovered. Empty structs +therefore have an empty partial input; their allocation does not change the +whole-memory aggregate. New's output also covers only its own aliases. + +An alias-specific contents query combines incoming values with the field's +initializer from New's struct (integer zero, floating zero, or null). It records +optimizer dependencies and stops at cached Phi types to break loops. MemPhi +merges the scalar contents of its precise alias. Start's incoming heap is empty +in this chapter; functions with heap arguments will require a different entry +query in Chapter 18. + +Loads fold against their own New and bypass a proven distinct allocation via +New's memory input. Different pointer nodes alone do not establish disjointness. +Array length remains a subsequent Store; array access control inputs remain. +Global code motion and the evaluator scheduler both follow an alias through +MemMerge to discover the allocation's ordering constraint. MemMerge is packaging, +not itself a clobber. The viewer and evaluator use the new slot conventions. + +### Chapter 16 constructor integration (reviewed) + +ScopeNode now owns the variable records and one `$mem` binding; the obsolete +ScopeMin alias table is removed. New inputs are `{ctrl, $mem, size, fields...}`, +with initializer inputs in struct-field order. Outputs remain `{ptr, $mem}`; +partial memory coverage and whole-memory integration are unchanged from 15. +Alias contents read initializer types with dependencies, rather than assuming +zero. The evaluator reads these same inputs, including array length. Constructor +body effects participate in ordinary lazy branch/loop memory SSA. + +The sole-use Store-to-New check is deliberately unchanged. A shared allocation +memory result has more users than a per-alias result, so it permits fewer folds. +`Chapter15Test.testBasic5` retains a `.y` Load instead of folding it to 3.14; +both runtime results remain checked. Other changed printed expectations only +adjust Region/Loop IDs after removing parser memory nodes. + +### Chapter 18 function memory (reviewed) + +Chapter 17 retains readonly casts, forward reference type updates, lexical +guards, and increment/assignment semantics while replacing ScopeMin with one +memory binding. Chapter 18 keeps its top-level Var class and makes MemMerge an +ordinary optimizer node rather than a parser alias table. Memory Parm 1 stays +opaque; local branches, loops, and merged returns use BulkMemPhi/MemPhi. +Function entry and CallEnd memory have unknown contents, including heap arguments. +Calls consume and return the complete memory state. There are no alias summaries +or private constructor effects. Inlining can expose the existing alias rewrites. + +New retains `{ctrl, $mem, size, fields...}` inputs and `{ptr, $mem}` outputs. +The evaluator, Eval2, graph viewer, and scheduling use those slots. Call is a +clobber for every alias. GCM raises a Load before the Call's block terminator; +it must not append the Load to the Call's inputs, which encode arguments and a +last-slot function pointer. The one-step Phi Load guard also stops at Calls. + +### Chapter 19 instruction selection (reviewed) + +Both selectors preserve BulkMemPhi exclusions and MemPhi aliases. Selected +memory instructions carry their alias through MemOpNode; GCM follows aggregates +for ordinary and folded reads, waits for matching writers, and inserts ordering +edges that require no register. Direct machine calls embed their target and must +keep their argument lists intact, just like ideal Calls. + +Preserve this chapter's existing lowering of nonzero initializers into Stores. +New now takes `{ctrl, $mem, size}` and produces `{ptr, $mem}`; there are no field +value inputs at this stage. Partial coverage and whole-memory integration are +unchanged. New's alias contents meet incoming memory with the field's zero type. +Both CPU allocation register masks and both evaluators use the new slots. + +The nested-loop regression reaches x86's existing right-hand Load/Add TODO; +that commutative case now uses AddMemX86 just like a left-hand Load. No other +unfinished instruction patterns or encoding work is included. + +### Chapter 20 register allocation (reviewed) + +ARM joins x86-64 and RISC-V in preserving bulk/precise memory Phis, allocation +slots, and selected memory effects. The allocator already excludes TypeMem Phis +and gives memory/ordering edges no register constraints; no allocator heuristic +changes are needed. Allocation regressions check that memory stays unallocated +and the pointer projection receives a legal register on all three targets. + +Folded x86 Add must retain a value input for its two-address result even when +that value is constant. The old immediate form omitted input 4 and caused a null +live range in BuildLRG. Both left- and right-hand Load patterns now retain the +other addend; the existing constant selector materializes it. + +Keep Chapter 20's inlining-safe Return typing and the dependency registrations +on a rejected Phi factoring attempt. Those fixes predate this memory port. + +### Chapter 21 encoding (reviewed) + +All three targets retain the memory Phi subclasses and New's pointer slot 0, +memory slot 1, and size input 2. Chapter 21 caches New's register masks in the +shared node during register allocation; update that shared interface. Keep its +existing scheduler rule that writers are already placed when adding read-before- +write constraints. Calls receive no scheduling-only operands. + +Dependency registration reverses direction here: `consumer.addDep(producer)`. +The forwarded memory queries and explicit forward dependencies follow that API. +Synthetic no-exit loops collect MemPhi inputs by alias and use BulkMemPhi for +the merged return memory. The one-step Load factoring guard is unchanged. + +Actual execution exposed a full-width integer Load allocated to XMM but encoded +as a GPR load. LoadX86 now selects the encoding from the allocated register bank, +preserving register masks. ARM's emulator now supports MOVK and MOVZ's complete +16-bit immediate and halfword shift. Carry these narrow fixes with later ports. + +BrainFuck follow-up: both the original and ported compilers retain 12 ideal +Loads and 114 Stores (106 Stores initialize the program). In the RISC-V Hello +World run, both execute 3,261 heap loads and 646 heap stores; stack loads/stores +change from 15/15 to 1,006/572, mostly spilling `d+4`. There are also pre-existing +missed length optimizations: `program#` reloads the known 106 on all 907 loop +tests, and `old#` is reloaded after allocating the replacement output array. +Load forwarding stops at memory Phis; its loop-profit test does not see through +the backedge's multiway Phi. The old-output pointer is itself a Phi, beyond the +two-direct-allocations disjointness test. Field aliases distinguish array length +from array data, but do not distinguish separate arrays of the same element type. +A scratch source replacing only `program#` with 106 passes Hello World, removes +907 heap loads, and returns stack traffic to 15/15. Its weighted split score +nevertheless rises from 130 to 165 as allocation inserts many pointer copies +before initialization stores; this score is not a dynamic memory-traffic measurement. No compiler +optimization was changed in this investigation. Probe sources and before/after +logs: `build/BrainMemory{Probe,Variants}.java`, `build/brain-memory-*.log`, and +`build/brain-variants-*.log`. + +The subsequent Chapter 21 Load search now folds `program#` through the loop. +The search returns a folding witness without creating nodes or rewiring edges; +it retains optimizer dependencies. At a non-loop MemPhi it must prove every +arm, with a BitSet remembering successful merges. Returning to the original +loop memory is success only for an invariant pointer. Unknown clobbers, other +loops, and incomplete merges fail. A separate builder creates value Phis after +the proof; unchanged paths refer to the new loop value Phi. Enqueue that Phi: +a self-edge can otherwise keep an obsolete Phi alive after local folding. +Controlled array accesses are not hoisted across a merge. The one-step +`Load.clobbered` factoring guard is unchanged. Carry this search with later ports. + +The unmodified BrainFuck source now has 11 Loads. RISC-V Hello World executes +2,354 heap loads, 646 heap stores, and 15/15 stack loads/stores, compared with +3,261 / 646 / 1,006 / 572 before this search. Emulator instruction count drops +22,965 to 21,920 (not a hardware timing). Retained/weighted moves change +46/130 to 144/165 as allocation inserts repeated initialization-store pointer copies. +Four reduced cases cover unchanged N-way arms, a possibly aliasing arm, +loop-carried pointers, and distinct values folding on all arms; they pass +2,800 evaluations across 100 seeds. The existing 9,800 memory evaluations pass. +At that checkpoint, the 432-test suite had only the user-edited BrainFuck golden failure +(42 versus 165). A temporary test copy restoring native BrainFuck execution +and using 165 passes all 91 spill-cohort entries and their runtime checks: +943 / 1,594 moves / weighted moves. The user's test edits were preserved. +Logs: `build/load-search21-tests5.log`, `build/load-search21-seeds.log`, +`build/load-search21-loop-seeds.log`, `build/brain-variants-search.log`, and +`build/load-search21-spills-complete.log`. + +### Chapters 22-24 lazy memory (implemented, review checkpoint) + +The forward port now reaches Chapter 24. Parameters, calls, and returns carry +whole memory; BulkMemPhi/MemPhi discover aliases within functions. New consumes +partial memory for its instance fields and produces `{ptr, $mem}`. The Chapter +21 loop Load search, one-step Load factoring guard, dependency fixes, and +alias-aware scheduling/evaluation carry through all three snapshots. + +Chapter 22 preserves C calls and its default-main handling. Chapter 23 keeps +cyclic field types, deep-final Loads, and symbolic field offsets; class-wide +fields do not participate in an allocation's memory coverage. Phi factoring +rejects operand Phis mixing pointers and integers, even for boolean operations. +A precise Phi waits for its bulk input to split instead of hiding that request +behind an upcast. Eval2 keeps unknown-caller pointer placeholders distinct from +actual heap constants. + +Chapter 24 retains its optimistic solver: MemPhi ignores TOP/XCONTROL arms, +New returns high results while its control is high, and alias contents register +a dependency on that control. The existing integer widening and call-graph +solver remain intact. Register checks run before encoding adds untyped branches. +Private constructor memory, escape tracking, and incomplete-type SSA stay in 25. +The separate pretty-printer consolidation remains pending. + +### Chapter 25 memory follow-through (implemented, same review batch) + +The memory representation was already present. The remaining port carries the +pure loop Load search, generalized Phi factoring with the one-step Load guard, +aggregate-aware scheduling, Call operand protection, and dependency wakeups. +It also carries the x86 register-bank Load encoding and ARM MOVZ/MOVK emulator +corrections. Bulk splitting queues new precise Phis instead of assuming an +eager fold still returns a MemPhi. + +The search preserves private constructor memory and Escape publication. A Store +whose alias is still 1 blocks the proof; external storage cannot be bypassed +using allocation identity. Length reads can drop control, while indexed array +reads retain it. Integer loop value Phis use Convert to preserve the declared +load width, and every new nested value Phi is queued for optimization. The +constructor, escape, serialization, and incomplete-type architecture stays intact. + +### Adjacent-copy forwarding: Chapters 21-25 + +Chapter 21 now removes a post-color Split when its sole use is the immediately +following non-Phi, non-CFG instruction, the operand accepts the source register, +and the operand is not two-address tied. Adjacency proves no intervening clobber. +The same rule and focused guard regression are present in 22-25; Chapter 20 +retains its baseline allocator. No spill-selection or CFG splitting rule changed. + +RISC-V BrainFuck keeps its original source and now has 35 / 42 retained / weighted +moves instead of 144 / 165. Hello World executes 21,787 emulator instructions +instead of 21,920; heap traffic remains 2,354 loads / 646 stores and stack traffic +15 / 15. Full Make suites and fuzzer targets pass in 21-25. Chapters 21-24 also +pass complete spill reports after refreshing reduced-move expectations: + +| Compiler | Allocations | Retained moves | Weighted moves | +|---|---:|---:|---:| +| 21 | 91 | 821 | 1,444 | +| 22 | 117 | 826 | 1,470 | +| 23 | 147 | 913 | 1,711 | +| 24 | 214 | 1,310 | 2,969 | + +Cohort 22 includes the two zero-move C return ABI checks added since the older +24-entry audit. Chapter 25's frozen 212-entry manifest remains unchanged. +Its reporter currently fails on ten pre-existing String-constructor parse errors; +the unmodified allocator reproduces all ten. The same 216 successful allocations +(including 14 current cohort-25 allocations) improve from 2,563 / 5,496 to +2,494 / 5,399, but these are partial totals. Fresh system-library encoding and +native cohort-25 checks pass. Logs: `build/copy-forward21-spills-final.log` +through `build/copy-forward24-spills-final.log`, `build/copy-forward25-spills.log`, +`build/copy-forward25-baseline.log`, and `build/copy-forward*-tests*.log`. + +### Other pending corrections + +- **Chapter 25 historical String fixtures.** Ten replay entries fail before RA + because `Test.String.cs` is not fully initialized. They come from + `Chapter21Test-testStringExport.smp` (four entries), + `Chapter21AllocTest-testString.smp` (three), and + `Chapter23AllocTest-testString.smp` (three), under + `chapter25/src/test/java/com/seaofnodes/simple/spill/`. Both original and + adjacent-copy-forwarding allocators reproduce them. Adapt these constructor + fixtures separately and document the comparability change; do not omit the + failed entries from a claimed complete spill total. The ordinary Chapter 25 + suite passes, including its current String programs. + + +- **Chapter 21 read-before-conditional-store scheduling.** A reduced negative + test for Load search exposed an existing ordering error: summing `a.x` before + a possibly aliasing conditional store in a loop returns 140 instead of 120 + when the pointer aliases `a`. It reproduces with the saved pre-search Load + class as well. Source and before/after graph dumps are in + `build/LoadSearchProbe.java` and `build/load-search-probe-{before,after}.txt`. + The Load-search rejection tests put the Store before the read to isolate + the optimization from that separate scheduling defect. Investigate separately. + +- **Forward Chapter 21 whole-program printer ordering through 22-25.** + `CodeGen.toString()` before scheduling now uses function-local CFG RPO and + groups every Region/Loop with its Phis. Chapter 25 supplied the function + ownership and grouping approach, but its combined CFG/data walk still puts + BrainFuck's closing Region before the body when adapted to 21. Chapter 21 + therefore orders control edges first, then places data for display using + private identity maps. No scheduling, idom queries, or graph mutation occurs. + The depth-limited node printer and scheduled whole-program printer retain + their existing entry points. Checked 18 parse/opto/loop-tree snapshots for + complete unique output, CFG ordering, Phi adjacency, repeatability, and + unchanged graph/cache state. Updated BrainFuck dump: + `build/brain-print-after-opto.txt`; diagnostic probe: `build/Print21Check.java`. + Release passes. The current full suite passes 430/431 tests; the sole failure + is the debugger-edited BrainFuck spill golden (42 versus the unchanged 130). + Those test edits were preserved. Log: `build/printer21-tests.log`. + +- **Chapter 21 synthetic TOP return allocation.** The original compiler at + `f01ee052` and the memory port both fail in `Coalesce.coalesce`: a Split of + TOP has no input live range. Reproduction: `struct S { int x; int y; }; + S !a=new S; if(arg) while(1) { a.x+=arg; a.y+=a.x; } return a.x+a.y;` + through Encoding on x86/SystemV. The new memory regression checks both + precise slices of the synthetic exit, then schedules all three targets; + allocation of its scalar TOP arm remains separate work. Do not hide the + missing live range with a null guard in coalescing. + +- **Existing Chapter 18 floating-array assertion.** The unchanged + `TypeStruct.makeAry` assertion accepts integers and nullable references but + excludes TypeFloat; `return new flt[1];` fails under `-ea`. The Chapter 18 + memory probe uses floating struct fields for initialization coverage instead. + Investigate separately; this port does not change the array type lattice. + - **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 @@ -143,11 +429,11 @@ reports actual retained moves, per target and cohort, with assertions enabled. | Chapter | Additional technique | Final cohort sizes | |---|---|---| | 20 | Basic coloring, rematerialization and loop splitting, with shared legality/progress fixes | 39 | -| 21 | Conservative copy coalescing | 39, 52 | -| 22 | Stronger copy-chain/backedge bias and cheap-spill ordering | 39, 52, 24 | -| 23 | Group popular single-def uses by compatible register classes | 39, 52, 24, 30 | -| 24 | One cold-only attempt for loop-Phi self-conflicts, then mandatory fallback | 39, 52, 24, 30, 67 | -| 25 | Existing area/cost ranking and later conflict strategies | 39, 52, 24, 30, 67, 10 | +| 21 | Conservative copy coalescing and adjacent-copy forwarding | 39, 52 | +| 22 | Stronger copy-chain/backedge bias and cheap-spill ordering | 39, 52, 26 | +| 23 | Group popular single-def uses by compatible register classes | 39, 52, 26, 30 | +| 24 | One cold-only attempt for loop-Phi self-conflicts, then mandatory fallback | 39, 52, 26, 30, 67 | +| 25 | Existing area/cost ranking and later conflict strategies | 39, 52, 24, 30, 67, 14 (ten historical entries currently fail) | Shared corrections include RegMask/LRG bookkeeping, null use masks, kills without an output LRG, self-conflict splitting, compatible rematerialization, clobber-aware @@ -176,15 +462,16 @@ Chapter 25 freezes the 212 earlier compilation entries in Constructor/library adaptations change IR, so this is a program-cohort comparison, not identical machine graphs. Those rows replay allocation and legality checks; current Chapter25Test native checks and a fresh system-library encoding contribute -ten more allocations. Total: **2,597 moves / 5,579 loop-weighted moves** over 222 +ten more allocations. Earlier audit total, before adjacent-copy forwarding: **2,597 moves / 5,579 loop-weighted moves** over 222 compilations. Chapter 25's area/cost implementation is retained. Substituting the earlier ranking saves five moves on the clients but fails a fresh `sys` allocation at the eight-round limit; a failed library cannot be omitted from the comparison. The earlier tables are refreshed where the final mask fixes changed them: -Chapter 20 is 238 / 357; Chapter 21 is 840 / 1,456. Coalescing on/off now saves -230 / 433 moves in Chapter 21 with identical legality fixes. Chapters 22-24's -aggregate values are unchanged. Full details and comparison limits belong in +Chapter 20 was 238 / 357 before the subsequent lazy-memory port (now 234 / 360); +Chapter 21 was 840 / 1,456 before lazy memory and adjacent-copy forwarding. +The audit's coalescing on/off comparison saves +230 / 433 moves in Chapter 21 with identical legality fixes. Current totals for 21-24 appear in the adjacent-copy record above. Full details and comparison limits belong in the individual READMEs rather than a second evolving set of tables here. ## Review and test protocol @@ -210,6 +497,218 @@ The top-level runner accepts explicit chapter lists, e.g. ## Validation record +- **Chapter 25 memory follow-through.** Full Make validation passes **482 tests + plus the 17-seed fuzzer wrapper**, and the release jar and system library build. + The loop-search and read-before-write regressions fail against the original LoadNode and pass with the port. A + scratch run across **100 optimizer seeds / 3,100 evaluations** checks the + invariant length, a possibly aliasing arm, a changing loop pointer, differing + stored values at a multiway merge, and reads before writes. Seeds 0 and 9 are + retained in the regression for unresolved Store aliases and nested Phi queueing. + The historical spill replay retains exactly its **ten known String-constructor + parse failures**, with no spill-golden or native-check failures. The same 216 + successful allocations total **2,488 moves / 5,379 weighted moves**, compared + with 2,494 / 5,399 before this port; this is not a complete-suite total. + Logs: `build/mem25-negative.log`, `build/mem25-seeds.log`, + `build/mem25-final2.log` (spill replay), and `build/mem25-final4.log` + (full tests and release). + +- **Chapters 22-24 lazy memory.** Full Make suites pass **454 / 476 / 506 + tests**, respectively, plus each chapter's fuzzer wrapper. Release jars build + for all three chapters. Forwarded checks cover nested memory, constructors, + calls and recursion, allocation registers, native x86 and emulated RISC-V/ARM + execution, no-exit loop memory, and the loop Load search. Existing Chapter 23 + null-guard tests cover the mixed pointer/integer factoring rejection; Chapter + 24's BubbleSort workload covers the precise-Phi/bulk-input collapse guard. + Spill reports pass all **117 / 147 / 214** compilation entries, including + runtime and allocation checks, with no golden failures. Current raw/weighted + totals are **823 / 1,474**, **901 / 1,699**, and **1,307 / 3,008**; prior + totals were 826 / 1,470, 913 / 1,711, and 1,310 / 2,969. These include graph + changes outside allocation, so they are not allocator-only comparisons. + The chapter README tables contain the current cohort breakdowns. + Logs: `build/mem22-final.log`, `build/mem22-release.log`, + `build/mem23-final2.log`, `build/mem23-release.log`, and + `build/mem24-final3.log`. + +- **Chapter 21 lazy memory and encoding.** Baseline: **412 tests plus the + fuzzer wrapper**. Final: **431 tests plus the fuzzer wrapper**. Make release + and spill-stats pass. The 16 forwarded memory checks retain scheduling and + allocation coverage; three execution/synthetic-exit tests add 42 native x86 + and 84 ARM/RISC-V evaluations with independent expected results, plus checks + of no-exit loop memory through scheduling. **9,800 scheduled evaluations** + pass across 100 optimizer seeds. Emulated multi-function programs enter at + main's encoded offset, which need not be zero. + The unchanged **91-entry** spill cohort changes **840 / 1,456** retained / + weighted moves to **845 / 1,559**. RISC-V BrainFuck changes 35 / 42 to + 37 / 58 in the Chapter 20 cohort and 35 / 42 to 46 / 130 in Chapter 21. + An ablation omitting only read-before-New ordering returns those rows to + 35 / 42 and 37 / 58; the ordering is retained, with no allocator tuning. + All eleven changed rows are recorded in the chapter README. + Logs: `build/memory21-baseline.log`, `build/memory21-final.log`, + `build/memory21-seeds.log`, `build/memory21-spills-baseline.log`, + `build/memory21-spills-final.log`, `build/memory21-spills-ablation.log`, + and `build/memory21-release.log`. The later 22-24 port is recorded above. + +- **Chapter 20 lazy memory and register allocation.** Baseline: 380 tests plus + the fuzzer wrapper. Final: **396 tests plus the fuzzer wrapper**, with the + 15 forwarded regressions and one allocation regression spanning all three + targets. **9,800 scheduled runtime evaluations** pass across 100 optimizer + seeds. **42 selected/scheduled/allocated graphs** pass legality checks at fixed + seed 123 on x86-64, RISC-V, and ARM. Make release and spill-stats pass. + The unchanged 39-entry spill cohort changes from **238 / 357** retained / + weighted moves to **234 / 360**. The x86 array prefix sum changes 5 / 5 to + 6 / 13 because selected-read ordering adds a loop move; a scratch ablation + omitting that ordering returns to 5 / 5. No allocator heuristics or benchmark + membership changed. The chapter README records all six changed program rows. + Logs: `build/memory20-baseline.log`, `build/memory20-final.log`, + `build/memory20-seeds.log`, `build/memory20-machine.log`, + `build/memory20-spills-baseline.log`, `build/memory20-spills-final.log`, + `build/memory20-spills-ablation.log`, and `build/memory20-release.log`. + This checkpoint was reviewed and committed as `f01ee052`. + + +- **Chapter 19 lazy memory and instruction selection.** Baseline: 360 tests + plus the fuzzer wrapper. Final: **375 tests plus the fuzzer wrapper**; release + jar builds with Make alone. Forwarded 13 memory regressions and added two + instruction-selection checks: alias/Phi preservation and allocation/call slots + on both targets, and folded x86 reads ordered before clobbering Stores. + **9,800 scheduled evaluations** and **2,800 selected/scheduled graphs** pass + across 100 optimizer seeds, with x86_64_v2 and riscv selection exercised. + The latter checks graph scheduling and shape; this chapter does not execute + encoded machine code. Logs: `build/memory19-baseline.log`, + `build/memory19-final.log`, `build/memory19-seeds.log`, + `build/memory19-machine-seeds.log`, and `build/memory19-release.log`. + Chapter 14's inherited And graph factors here without widening; its runtime + assertion remains. This checkpoint was reviewed and committed as `612555ee`. + + +- **Lazy memory through 17 and 18.** Baselines: 17 has 288 tests; 18 has 318 + plus its fuzzer wrapper. The port passes **298 tests in 17** and **331 tests + plus the fuzzer wrapper in 18**. Both carry the nine memory regressions and + constructor regression; 18 adds checks for non-inlined calls, recursive heap + updates, and allocation inside an inlined function. The non-inlined check + also verifies Call's input count and last-slot function pointer after GCM. + **7,700 / 9,800 evaluations across 100 optimizer seeds** pass in 17 / 18, + checking final memory values, complete scheduling, partial New inputs, and + two-result New. Full suites and both release jars build with Make alone. + Logs: `build/memory17-18-baseline.log`, `build/memory17-18-final.log`, + `build/memory17-seeds.log`, and `build/memory18-seeds.log`. + Chapter 17's And graph remains unfactored when the result would widen; the + existing runtime assertion is unchanged. Shared allocation memory retains + the `.y` Load in its inherited array example, as in 16. Nullable diagnostics + select the earlier array dereference in 17/18. The Chapter 17 null test now + makes `p2` mutable so its setup does not fail first on an unrelated final-field + store. This checkpoint was reviewed and committed as `2d51b647`. + +- **Chapter 16 lazy memory and constructors.** Baseline: 237 tests. The port + passes 247 tests, including nine carried memory regressions and one constructor + case combining reads, writes, a loop, and an unrelated alias. Full Make tests + and release builds for 10a-16 pass: **1,568 tests across eight snapshots**. + An additional **7,700 evaluations across 100 optimizer seeds** check memory + behavior, complete scheduling, two-result New, and partial allocation inputs. + Logs: `build/memory16-baseline.log`, `build/memory16-all-tests.log`, and + `build/memory16-seeds.log`. + The changed graph order exposed a dependency notification gap in + `Chapter14Test.testCloneAnd`: replacing Minus#25 rewired And#28 without waking + its dependent Phi#26. `Node.subsume` now wakes each rewired user's recorded + dependents in 10a-16. The existing test passes without weakening its assertions. + Chapters 17+ are unchanged; constructor integration is the review boundary. + +- **Generalized Phi factoring, 10a through 15.** Replaced binary-only factoring + and the blanket memory exclusion with operand-wise Phis and exact-class + `copyEmpty`. Matching Loads and sole-use Stores are eligible when unbound to + control; operation attributes, field/alias identity, operand types, and + result-type monotonicity are preserved. Load safety examines memory users + before explicit GCM anti-dependencies exist. The final guard checks only + immediate users, stopping at Stores, Phis, MemMerges, and New in 15. Memory factoring waits for bulk + partitioning at its Region; MemMerge and BulkMemPhi are never factored. + Stores bound to control in 11-14 remain excluded. User removal now wakes + recorded dependents, and empty-diamond folding observes projection rewiring. + Three regressions live in each snapshot's `Chapter10Test`; the original 10a + Phi fails both positive folding checks, and disabling the Load safety guard + exposes an invalid read/write schedule in the negative probe. + Full Make tests pass **159 / 162 / 177 / 181 / 196 / 216 / 230** and all seven + release jars build. A 10a Phi label and one 14 Region ID were refreshed; + expression/value expectations remain unchanged. Each snapshot also passes + **3,500 evaluations across 100 optimizer seeds**, and 15 passes another + **7,000 allocation/memory evaluations**. Logs: `build/phi-drop-baseline.log`, + `build/phi-drop-tests.log`, `build/phi-drop-seeds*.log`, + `build/phi-drop-negative.log`, `build/phi-drop-antidep-negative.log`, and + `build/phi-drop-allocation-seeds15.log`. Chapters 16+ are unchanged. + The subsequent guard refactor moves memory eligibility into virtual + `MemOpNode.canDrop` overrides and clobber inspection into Load. Removed 10a's + unused visited set. All 1,321 tests and seven release builds passed + (`build/phi-guards-refactor.log`). At Cliff's request, the subsequent + simplification also removes recursive aggregate traversal from 10b-15: + a one-step check conservatively stops at possible indirect clobbers, + keeping the teaching cost proportionate to saving one Load. The aggregate + example now keeps two Loads in 10b-15; its runtime checks remain unchanged. + All 1,321 tests and seven release builds pass with the simpler guard + (`build/phi-guards-one-step.log`). + +- **Chapter 15 partial allocation memory (review checkpoint).** Implemented the + approved two-result New and carried the lazy memory model through parser, + scheduling, evaluator, viewer roles, and chapter documentation. Full Make + suite passes **227 tests** and the release jar builds, up from baseline **218**: six carried memory + regressions plus three allocation regressions. They cover unrelated aliases, + mixed integer/float/pointer contents, reads across allocation, and allocation + inside loops. A disposable probe also passed **7,000 evaluations across 100 + optimizer seeds**, including empty structs, arrays, both slot conventions, + partial coverage, and complete scheduling of live definitions. Region/Loop + golden changes only alter IDs; the nullable-access diagnostic matches 13-14. + Logs: `build/memory15-baseline.log`, `build/memory-forward-15.log`, and + `build/memory15-seeds.log`; final tests and release: `build/memory15-final.log`. + +- **Memory forward port through Chapters 12-14.** Preserved + floating-point operations, typed reference initialization, and narrow stores + while forwarding the reviewed parser, memory nodes, GCM, Return slots, and + evaluator. Each chapter inherits all six memory regressions. Make tests pass + **178 / 193 / 213**, up from baselines **172 / 187 / 207**; all three release + jars build. Updated printed Region/Loop IDs without changing expression + expectations. The invalid `head.next.i` test in 13 and 14 now reports the + possibly-null `i` access first; both accesses remain invalid, and the test + still requires rejection. Logs: `build/memory-forward-baselines.log` and + `build/memory-forward-12-14.log`. The subsequent allocation port is recorded above. + +- **Control/memory slot convention (10a, 10b, 11).** Return now takes + `{ctrl, $mem, result}` and describes its result tuple in that order, with + whole memory typed as `TypeMem.BOT`. Evaluators use `expr()` rather than a + literal result index. Updated viewer roles, existing projection checks, + documentation, and numbered diagram edges. Make tests pass **156 / 159 / 174** + respectively, and all three release jars build. Log: + `build/return-memory-slots.log`. Preserve control slot 0 and memory slot 1 + when forwarding Call, CallEnd, and other nodes in later chapters. + +- **Chapter 11 memory forward port (2026-09-29, review checkpoint).** Parser, + Start, Scope, and Return now use 10b's one-memory model and its three memory + nodes. GCM schedules MemMerge as a dependency node, filters both Load + readiness and anti-dependencies by alias, and retains the full Store placement + range needed by the evaluator. Synthetic never-taken loop returns remain + scope-free. Updated the evaluator, viewer Return edge roles, walkthrough + diagrams, and scheduling code excerpts. The baseline passed **168 tests**; + the port passes **174**, including all six forwarded memory regressions. + Fifty existing printed expectations changed only Region/Loop node IDs. + Five memory programs pass seven inputs across 100 optimizer seeds, with + scheduling assertions and final heap/value checks (**3,500 evaluations**). + Make also builds the release jar and regenerates all six updated walkthrough + diagrams; `git diff --check` passes. + Logs: `build/memory-forward-11-baseline.log`, `build/memory-forward-11.log`. + Chapters 12-24 are untouched pending review. + +- **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`. At this checkpoint Chapters + 11-24 had not yet adopted the new memory representation; forward-port progress + is recorded 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..d4f9bf2c 100644 --- a/skills/chapter25-codex-notes.md +++ b/skills/chapter25-codex-notes.md @@ -27,6 +27,11 @@ For shared viewer work, read [the graph viewer notes](graph-viewer-codex-notes.m - Printers are valid debugging tools. If printing changes behavior, fixing the printer/accessor side effect is immediately high priority. - Keep edited text files LF-only, including on Windows. +- When mixing `||` and `&&` in one expression, always parenthesize the groups + explicitly rather than relying on operator precedence. +- For any node consuming or producing both control and memory, control belongs + in slot 0 and memory in slot 1; other inputs/results follow. This applies to + Start, Return, and future forward ports of Call, CallEnd, and other such nodes. - Preserve unrelated dirty changes. Emacs lock/backup files are common and are not permission to clean the tree. - Do not push without explicit approval. Prefer commits at meaningful test @@ -37,18 +42,144 @@ 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. Cliff reviewed and + committed the forward port through Chapter 21. Chapters 22-24 now carry lazy + memory through cyclic types and SCCP; related corrections now reach 25. + The 22-25 batch is ready for review. + Keep Chapter 25's separate constructor-memory and incomplete-type design. + See the concrete boundary notes + in `docs/chapter-backports.md`. GCM readiness and anti-dependency checks must both + filter by alias and ignore MemMerge as a clobber; keep the evaluator's same + alias filtering too. MemMerge still needs ordinary data-dependency placement. + +- Chapter 15 New consumes `{ctrl, $mem, size}` and produces `{ptr, $mem}`: + no control result, pointer slot 0, memory slot 1. Its TypeStruct/TypeMemPtr + defines the covered field aliases. Use a partial input MemMerge (null default), + one memory output projection shared across those aliases, and a whole-memory + aggregate preserving unrelated prior slices. Missing partial aliases are + uncovered, not zero or unknown memory. Typed alias queries follow New's input + and meet in each field's initializer; cached MemPhi types break cycles, and + explicit dependencies preserve worklist progress. Start's contents are empty + in this chapter because it has no heap arguments; revisit that in Chapter 18. + Both schedulers must follow aggregates to find New's effect for a read alias. + Only a proven distinct allocation permits Load to bypass New; unequal pointer + nodes alone do not prove disjoint objects. + - 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. Use a locally sensible fix when the general solution requires concepts not yet introduced; report substantial representation changes for review. + +- Chapter 16 extends New inputs to `{ctrl, $mem, size, fields...}` while + retaining `{ptr, $mem}` outputs and partial alias coverage. Constructor values + determine alias contents; register dependencies on their nodes as well as + New. ScopeNode owns Var records and the single memory binding. The existing + sole-use Store-to-New guard is intentionally conservative with a shared memory + result; do not add alias traversal just to recover a small constant fold. + +- Chapter 18 keeps memory Parm 1 and CallEnd memory opaque: heap arguments make + the earlier empty-entry-heap assumption invalid. Local/return merges use + BulkMemPhi; calls carry the complete memory state and clobber all aliases. + MemMerge no longer tracks parser state. GCM can raise a Load before a Call's + terminating block position, but must not append anti-dependence inputs to + Call: the last input is its function pointer. The one-step Phi Load guard + stops at Calls. Eval2's New initializer indexing and tuple printer use ptr 0, + memory 1, size input 2, field inputs 3 onward. + +- Chapter 19 lowers nonzero initializers into explicit Stores. New has inputs + `{ctrl, $mem, size}` and results `{ptr, $mem}`; use zero field types for alias + contents queries, not constructor inputs. Both CPU selectors copy BulkMemPhi + and MemPhi with their alias metadata. GCM handles reads through MemOpNode, + including folded x86 arithmetic/comparison reads; selected TypeMem results + identify writers. Trailing anti-dependence edges on writers have no register + requirement. Direct machine Calls embed the target, but still must not gain + scheduling-only inputs. The one-step Phi Load guard remains unchanged. + +- Chapter 20 includes ARM and moves CodeGen to the codegen package. Memory + Phis and projections have no live range; the existing allocator already + excludes TypeMem. Preserve its inlining-safe Return compute and rejected-Phi + operand dependencies. Folded x86 Add requires a register value in slot 4 even + for a constant addend, because that value is also its two-address result. + Spill counts use the same fixed 39-entry cohort and seed 123: the lazy-memory + port changes 238 / 357 moves / weighted moves to 234 / 360 without changing + allocator heuristics. Selected-read ordering explains the extra hot array move. + +- Chapter 21 reverses addDep's direction: use `consumer.addDep(producer)`, + including explicit forward dependencies. New's register masks are cached in + the common node during allocation; size uses input 2 and pointer output 0. + Its GCM already places writers before adding read-before-write constraints; + preserve that chapter-specific rule. LoopNode.forceExit reconstructs memory + using actual MemPhi aliases and merges return memory with BulkMemPhi. Machine + execution tests must find main's encoded offset, rather than assuming PC 0. + Full-width x86 loads choose their instruction from the allocated register + bank, preserving integer bits when allocated to XMM. ARM's emulator needs + MOVK and full MOVZ immediate/shift support. The 91-entry spill cohort changes + 840 / 1,456 to 845 / 1,559; read-before-New ordering explains most of the + RISC-V BrainFuck increase. No allocator heuristics changed. Synthetic scalar + TOP exit allocation already failed before this port; see the pending queue. + +- Chapter 21's Load search is separate from the one-step factoring guard. + It creates no nodes or rewiring, but records dependencies and returns folding + witnesses. All arms of a non-loop memory Phi must succeed; a BitSet records + successfully checked merges. Only the original loop Phi closes an unchanged + path, and only for a loop-invariant pointer. Build value Phis after proving + profitability, and enqueue the root value Phi even when it folds locally: + self-edges otherwise keep an obsolete Phi alive outside the worklist. Reject + hoisting controlled array accesses. BrainFuck's program length now folds; + its RISC-V run loses 907 heap loads and all hot stack spills. Static move + counts initially rose from repeated initialization-store pointer copies; + adjacent-copy forwarding removes them. Measure executed memory traffic too. + Carry this search with subsequent memory ports. + +- Chapters 23-24 exclude class-wide (`Field._one`) fields from New's alias + coverage and keep symbolic field offsets until layout. Alias contents must + preserve cyclic field types and readonly information. Phi factoring must not + introduce a pointer/integer operand Phi even when the result is boolean. + A precise Phi must let its bulk input split before collapsing to it; inserting + a scalar-style upcast would hide the alias request from the bulk Phi. + Chapter 24 MemPhi skips both TOP and XCONTROL predecessor arms during SCCP; + New's outputs and alias-contents query stay high while its control is high. + Check registers before encoding, which may add untyped branch nodes. + +- Chapter 25 Load search follows Escape to private/public memory; New has no + public-memory input to bypass. Alias 1 on a Store is unresolved, so it blocks + the search. External storage cannot use allocation disjointness. Array length + Loads can drop control, while element Loads retain it. New integer value Phis + start at i64 and use Convert at the result to preserve the declared width; + decline hoisting when that would lose a sharper inferred range. Queue every + new value Phi, including nested merges, and queue new precise memory Phis + instead of eagerly folding them while bulk splitting is still in progress. + User-count dependencies must wake from Node.delUse, including unkeep. + +- Generalized Phi factoring now starts in 10a and is forwarded through 25. + Memory-specific eligibility lives in `MemOpNode.canDrop`, with virtual Load + and Store checks. Load owns `clobbered`: all these chapters check only + immediate memory users, without recursion or a visited set. Stop at Stores, + Phis, MemMerges (10b+), New (15+), and Calls (18+); do not chase aliases or aggregates just + to save one Load. Keep this teaching optimization simple and conservative. + Preserve exact operation attributes with clone-based `copyEmpty`, type each + operand Phi separately, reuse identical inputs, and reject type widening. + Stores require sole use by the Phi; Loads inspect memory users for clobbers + before GCM has made anti-dependence edges. From 10b, create precise memory + Phis for the factored operands. Do not factor aggregates or bulk Phis; + wait for other BulkMemPhis at the Region to disappear, since their slice + lookup assumes Region/alias identifies the completed memory point. Existing + control-bound Stores in 11-14 remain ineligible. `delUse` wakes recorded + dependents for user-count queries; Region's empty-diamond fold depends on + projection rewiring as well as projection types. Carry these with the 21+ + memory port; do not duplicate the later chapters' existing `copyEmpty`. + `subsume` must wake each rewired user's recorded dependents, just as `setDef` + does: operand identity changes can enable a Phi fold without changing the + intervening operation's type (`Chapter14Test.testCloneAnd` in 16). - Find the chapter where the relevant feature first appears, not just the chapter that can parse the original reproducer. Reduce away later syntax/features when possible. Put the regression in that earliest `ChapterNTest.java`, and forward 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 @@ -169,16 +300,22 @@ For shared viewer work, read [the graph viewer notes](graph-viewer-codex-notes.m all 13 original programs; Chapter21AllocTest retains the four revised cases, counted with the native variants as cohort 21. On Windows the cohorts are 39 and 52 compilations. Preserve source/target membership when moving forward. +- Chapter 21 adds adjacent-copy forwarding after coloring; carry it through 25. + A Split with exactly one use, in the next scheduled instruction, can be removed + if the operand accepts the source register and is not two-address tied. Exclude + Phi/CFG users. Adjacency supplies the no-clobber proof; do not expand this into + CFG lifetime partitioning. Chapter 20 keeps its simpler baseline. - Chapter 22 adds stronger copy-chain/backedge bias and cheap-spill ordering; popular-use grouping remains for 23. Preserve `person21` (64-bit age) separately from 22's narrower person example, and keep its revised infinite-loop input in - cohort 22. The Windows cohort counts are 39, 52, and 24. + cohort 22. The Windows cohort counts are now 39, 52, and 26; the two added C return + ABI checks have zero moves. - Chapter 23 groups popular single-def uses by compatible register masks. One intersection pass suffices: narrowing a class preserves its earlier users, and newly added classes are disjoint. Snapshot distinct users before rewiring, ignore null register masks, and count a call once despite repeated arguments. The grouping correction/regression is already forwarded through 25. -- Chapter 23's frozen cohorts count 39, 52, 24, and 30 allocations. Preserve +- Chapter 23's current cohorts count 39, 52, 26, and 30 allocations. Preserve `stringHash21` (with the old guard); its revised source and newly enabled Jig tests belong to 23. Grouping on/off gives identical 933 raw / 1,745 weighted moves here; report the zero gain rather than implying every heuristic helps @@ -511,6 +648,16 @@ Consequences: ## Side-effect-free diagnostics: B09 lessons +- Chapter 21's pre-scheduling `CodeGen.toString()` uses a separate CFG-only + RPO walk, then printer-local data placement. Walking CFG and data uses in + the same DFS can move a loop's closing Region before its body via Phi + backedges, even when CFG uses are visited first. Keep Loop/Region Phis + contiguous with their header, without pulling their backedge definitions + up to the header. Preserve function boundaries at Calls/Returns. This + correction remains queued for 22-25; 25 supplied the grouping approach but + still mixes CFG and data traversal. All printer bookkeeping uses identity + maps and raw edges, without invoking actual scheduling or dominator queries. + - `Node._inputs` remains allocated after `kill()`. Use `isDead()` when printing or filtering dead nodes; an empty input array alone does not imply death. Bounds-safe edge inspection only needs null-node and input-count checks. |
