summaryrefslogtreecommitdiff
path: root/chapter12/README.md
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-10-03 14:03:48 -0700
committerCliff Click <cliffc@acm.org>2026-10-03 14:03:48 -0700
commit22df891893d30e9906d0b14fc3ae5d8e67d500b2 (patch)
tree2bda142f92febc8b26f1cabd4bb0c46d1864f34b /chapter12/README.md
parentc3958e34c0de21df52e57e47e4f47ffe6464f138 (diff)
downloadSimple-22df891893d30e9906d0b14fc3ae5d8e67d500b2.tar.gz
Simple-22df891893d30e9906d0b14fc3ae5d8e67d500b2.zip
Chapter renumber, and split ch 17
Renumber chapters 10a, 10b, 11, 12, 13, 14 -> 10, 11, 13, 12, [11,14]. Split ch 17 into a 17a and 17b; shuffle around im/mutable syntax to be consistent, and present it more slowly. THe new immutable syntax is pushed through all later chapters
Diffstat (limited to 'chapter12/README.md')
-rw-r--r--chapter12/README.md161
1 files changed, 124 insertions, 37 deletions
diff --git a/chapter12/README.md b/chapter12/README.md
index 36922373..3110d735 100644
--- a/chapter12/README.md
+++ b/chapter12/README.md
@@ -1,61 +1,148 @@
-# Chapter 12: Floats
+# Chapter 12: References
[Previous: Chapter 11](../chapter11/README.md) |
[Next: Chapter 13](../chapter13/README.md)
-# Table of Contents
+Chapters 10 and 11 gave structs integer fields and represented their memory
+effects in SSA. This chapter lets fields contain references to other structs,
+including structs whose definitions have not yet appeared. Linked lists and
+mutually referring objects become possible without changing the memory graph.
-1. [Float](#floats)
+You can also read [this chapter](https://github.com/SeaOfNodes/Simple/tree/linear-chapter12)
+in the linear Git history and [compare it with Chapter 11](https://github.com/SeaOfNodes/Simple/compare/linear-chapter11...linear-chapter12).
-In this chapter, we add a floating point type and values.
+## Typed fields and forward references
-You can also read [this chapter](https://github.com/SeaOfNodes/Simple/tree/linear-chapter12) 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-chapter11...linear-chapter12) it to the previous chapter.
+```java
+struct Person {
+ int age;
+ FamilyTree? tree;
+}
+struct FamilyTree {
+ Person? father;
+ Person? mother;
+}
+```
+
+The parser's `TYPES` table maps names to types. It initially contains `int`
+(and any other primitive types, more are showing up in later chapters). When
+`type()` recognizes a declaration using an unknown name, it installs a
+name-only `TypeStruct`, with `_fields == null` - this a struct where we only
+know the struct name, and no other information. A pointer to that struct is
+enough to describe a reference field,`?` allows the pointer to be null.
-## Floats
+`parseStruct()` collects all fields before installing the complete struct in
+`TYPES`. It replaces the table entry; it does not mutate the old type or
+publish successive prefixes of the field list. Field aliases are allocated
+with the completed declaration, as before.
-Floating point values start with the `flt` type and compute IEEE754 64-bit arithmetic.
-Integer expressions auto-widen to floats when involved in a float expression.
+See [the parser](src/main/java/com/seaofnodes/simple/Parser.java), particularly
+`type()`, `parseField()`, and `parseStruct()`, and the
+[grammar](docs/12-grammar.md).
+
+## Recursive structs: `L0` and `L1`
```java
-flt f = 0.5;
-int x = 2;
-f = f+x; // Auto-widen x to 2.0, result is 2.5
-f = f+true; // Since bools are just synonyms for integers 0,1 they auto-widen also
+struct LLI { LLI? next; int i; }
```
-There is no corresponding way right now to round a `flt` back to an `int`:
-```java
-int x = 3.14; // error
+Two descriptions are useful for remembering what the compiler actually builds:
+
+```text
+L0 = TypeStruct("LLI", fields = null)
+L1 = TypeStruct("LLI", fields = { next: pointer-to-L0?, i: int })
+TYPES["LLI"] = L1
```
-Here is Newton's method for computing a square root:
+`L0` is created when parsing the `LLI? next` field. After the closing brace,
+`L1` replaces it in the name table. The `next` field inside `L1` still points
+to `L0`; it is not patched to point back to `L1`.
+
+The design is **immutable shallow references, resolved by name when needed**.
+The runtime objects may form a cycle; their compiler type descriptions remain
+finite and acyclic. This is an intentional decision to **stop recursive type
+expansion**. Recursively substituting the completed definition into every
+reference would either expand forever or require cyclic type interning,
+equality, hashing, and lattice operations (which indeed show up in a lattice
+chapter).
+
+[`TypeStruct`](src/main/java/com/seaofnodes/simple/type/TypeStruct.java)
+instead stops at the name-only description. Its `dual()` and `glb()` leave that
+description alone. Meeting a complete description with a name-only description
+of the same struct yields the name-only description. That loses field detail
+without losing the struct's identity. There is no pass trying to expand `L0`
+into an ever deeper sequence of `L1`, `L2`, and so on.
+
+## Resolving a shallow reference
+
+When parsing `head.next`, `parsePostfix()` takes the pointer's struct name and
+looks up the current declaration in `TYPES`. It gets the field layout and alias
+from that declaration, even if the pointer's own type still contains `L0`.
+Doing the lookup again for the next field access is sufficient for a chain
+such as `head.next.next.i`.
+
+For a local declaration or assignment, `parseExpressionStatement()` also
+resolves a name-only pointer before checking assignment compatibility. This
+"deepening" changes the temporary type used for the check; it does not rewrite
+the expression node's type or replace all old references in the graph.
+
```java
-flt guess = arg; // Initial guess is just argument
-while( true ) {
- // Next guess from Newton's method
- flt next = (arg/guess + guess)/2;
- // Break if hit fixed point
- if( next == guess ) break;
- guess = next;
+struct LLI { LLI? next; int i; }
+LLI? head = null;
+while (arg) {
+ LLI x = new LLI;
+ x.next = head;
+ x.i = arg;
+ head = x;
+ arg = arg - 1;
}
-return guess;
+if (!head) return 0;
+LLI? next = head.next;
+if (next == null) return 1;
+return next.i;
```
-When run with argument 2.0, produces 1.414213562373095.
-Floating point operations have their own Nodes:
+The null checks refine the pointer on the surviving control path. In
+particular, the false path of an `if` must record its refinement even when
+there is no explicit `else`. Resolving a name supplies the field definition;
+it does not prove that the pointer is non-null.
-| Node | Op |
-|------------------------|----------------------|
-| AddF, SubF, MulF, DivF | Basic binary FP ops |
-| MinusF | Unary negate |
-| EQF, LTF, LEF | FP compare operators |
+## Initialization and incomplete definitions
-Within the Type lattice, we add the following domain:
+`newStruct()` initializes each field using its type's `makeInit()`: zero for
+integers and null for references. Loads and Stores use the same aliases and
+memory optimizations as Chapter 11. A Load from an initializing Store can
+therefore fold to null.
-* ![Float](../docs/type-float.svg) - Float values. The same shape as the Integer values, but floats
- with a top (`TOP`, ⊤), bottom (`BOT`, ⊥), and constants.
+An unused forward reference need not ever be defined:
+
+```java
+struct Holder { Missing? ref; }
+Holder h = new Holder;
+return h.ref; // null; no definition of Missing is needed
+```
+
+There is no way here to construct a non-null `Missing` without its complete
+definition: `new Missing` is rejected. Accessing one of its fields also needs
+a definition, as well as a non-null pointer. Simply mentioning a nullable
+reference type does not impose a requirement to define it before the end of
+the program.
+
+There is one temporary initialization allowance in this chapter:
+
+```java
+struct N { N next; int i; }
+N n = new N;
+return n.next; // null, despite the non-null field declaration
+```
-The other domains retain Chapter 10b's named structs, nullable pointers, and
-precise memory aliases.
+The initializing Store may write null even into a non-null reference field.
+An ordinary `n.next = null` is rejected. Chapter 16 adds constructors and
+checks initialization; this chapter keeps the distinction explicit in
+[`StoreNode`](src/main/java/com/seaofnodes/simple/node/StoreNode.java).
-![Graph1](./docs/lattice.svg)
+The [reference tests](src/test/java/com/seaofnodes/simple/Chapter12Test.java)
+exercise linked lists, mutual references, null checks, and incomplete types.
+Run them with `make tests`; `make view` displays the same unscheduled graph.
+Chapter 13 now has the memory and reference semantics it needs to introduce
+global code motion, including load/store anti-dependencies.