summaryrefslogtreecommitdiff
path: root/chapter10/README.md
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-09-29 12:34:58 -0700
committerCliff Click <cliffc@acm.org>2026-09-29 12:34:58 -0700
commit6347fa6dc2ee8d3060f2f0db21f03f11d374b0bc (patch)
tree6da4371d0ac6a0e17cfc31dad2275f1331ab3edb /chapter10/README.md
parent41be002e89358649da46c33c8245aa786afdad6a (diff)
downloadSimple-6347fa6dc2ee8d3060f2f0db21f03f11d374b0bc.tar.gz
Simple-6347fa6dc2ee8d3060f2f0db21f03f11d374b0bc.zip
Squashed commit of the following:
commit 3ba6686fc991998a0a420709dfb0012bbddf77ab Author: Cliff Click <cliffc@acm.org> Date: Tue Sep 29 12:21:46 2026 -0700 Backport lazy memory splits to ch10, split into ch10a and b commit fa7d56f72455828826d0ab7e1c142ca814e6e2aa Author: Cliff Click <cliffc@acm.org> Date: Tue Sep 29 10:10:53 2026 -0700 Reverse edges commit e1bba17ff54400a34ede0eaacad863420d021119 Author: Cliff Click <cliffc@acm.org> Date: Tue Sep 29 09:44:41 2026 -0700 Split ch10 Basic memory introduction in 10a, then precise aliases in 10b.
Diffstat (limited to 'chapter10/README.md')
-rw-r--r--chapter10/README.md359
1 files changed, 0 insertions, 359 deletions
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.
-
-![Graph1](./docs/lattice.svg)
-
-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:
-
-![Graph1](./docs/example1.svg)
-
-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:
-
-![Graph2a](./docs/example2a.svg)
-
-After enabling Store-Store peephole we get:
-
-![Graph2b](./docs/example2b.svg)
-
-After load-after-store elision:
-
-![Graph2c](./docs/example2c.svg)
-
-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.