summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--chapter01/README.md69
-rw-r--r--chapter02/README.md46
-rw-r--r--chapter03/README.md25
-rw-r--r--chapter04/README.md24
-rw-r--r--chapter04/appendix/dce.md10
-rw-r--r--chapter05/README.md89
-rw-r--r--chapter08/README.md25
-rw-r--r--chapter09/README.md97
-rw-r--r--chapter10a/README.md101
-rw-r--r--chapter10a/docs/Makefile15
-rw-r--r--chapter10a/docs/example1.json24
-rw-r--r--chapter10a/docs/example1.svg195
-rw-r--r--chapter10a/docs/example2a.json31
-rw-r--r--chapter10a/docs/example2a.svg232
-rw-r--r--chapter10a/docs/example2b.json31
-rw-r--r--chapter10a/docs/example2b.svg222
-rw-r--r--chapter10a/docs/example2c.json22
-rw-r--r--chapter10a/docs/example2c.svg163
-rw-r--r--chapter10a/docs/lattice.gv25
-rw-r--r--chapter10a/docs/lattice.svg260
-rw-r--r--graph/README.md2
21 files changed, 1076 insertions, 632 deletions
diff --git a/chapter01/README.md b/chapter01/README.md
index ebe2811e..b0950913 100644
--- a/chapter01/README.md
+++ b/chapter01/README.md
@@ -47,44 +47,39 @@ If necessary the reader can consult a standard compiler textbook.
## Architecture
-We construct the intermediate Sea of Nodes (SoN) representation directly as we parse the language. There is no Abstract Syntax Tree representation. The reason for this is to demonstrate a key benefit of the SoN IR:
-a number of pessimistic peephole optimizations can be performed while parsing a language. This aspect is more fully explored from [Chapter 2](../chapter02/README.md) onwards.
-
-## Data Structures
-
-Our data structures are based upon the descriptions provided in the following papers:
-
-* [From Quads to Graphs: An Intermediate Representation's Journey](http://softlib.rice.edu/pub/CRPC-TRs/reports/CRPC-TR93366-S.pdf)
-* [Combining Analyses, Combining Optimizations](https://dl.acm.org/doi/pdf/10.1145/201059.201061)
-* [A Simple Graph-Based Intermediate Representation](https://www.oracle.com/technetwork/java/javase/tech/c2-ir95-150110.pdf)
-* [Global Code Motion Global Value Numbering](https://courses.cs.washington.edu/courses/cse501/06wi/reading/click-pldi95.pdf)
-* [EasySSA](https://www.dropbox.com/scl/fi/0ww4sgl3ynep9hhe3i4xn/EasySSA.pdf?rlkey=2cp78hzxke62flkmyneiebzoz&dl=0)
-* [SeaOfNodes](https://www.dropbox.com/scl/fi/cxykfvlzsmlcatyg6rlbt/SeaOfNodes.pdf?rlkey=z6o7y3rwr6atrejilcze6r8x0&e=1&dl=0)
-
-Following the lead from above, we represent our intermediate representation using an object oriented data model. Details of the
-representation follow.
+We construct the intermediate Sea of Nodes (SoN) representation directly as we
+parse the language. There is no Abstract Syntax Tree representation. The reason
+for this is to demonstrate a key benefit of the SoN IR: a number of pessimistic
+peephole optimizations can be performed while parsing a language. This aspect
+is more fully explored from [Chapter 2](../chapter02/README.md) onwards, and
+eventually leads to a full-fledged program typing without needing any
+type-analysis passes.
### Intermediate Representation as a Graph of Nodes
The intermediate representation is a graph of Node objects. The `Node` class is the base type for objects in the IR graph.
The `Node` class provides common capabilities that are inherited by all subtypes.
-Each subtype implements semantics relevant to that subtype.
+Subtypes implements semantics relevant to that subtype, e.g. AddNodes add and MulNodes multiply.
Each `Node` represents an "instruction" as it may appear in traditional IRs.
### Nodes are in a Graph
-The key idea of the Sea of Nodes IR is that each Node is linked to other Nodes by def-use dependencies.
-As this is such an important and fundamental aspect of the IR, it is important to understand how we implement this, and depict this in graph visuals.
+The key idea of the Sea of Nodes IR is that each Node is linked to other Nodes
+by def-use dependencies. As this is such an important and fundamental aspect
+of the IR, it is important to understand how we implement this, and depict this
+in graph visuals.
-The base `Node` class maintains a list of Nodes that are inputs to it. An input is an edge from a "def" to a "use". What this means is that if `B` is a definition, and `A` uses `B`,
-then there is a def-use edge from `B` to `A`.
+The base `Node` class maintains a list of Nodes that are inputs to it. An
+input is an edge from a "use" to a "def". What this means is that if `B` is a
+definition, and `A` uses `B`, then `B` has a def->use edge from `B` to `A` and
+correspondingly A has an input use->def edge from `A` to `B`.
Visually we show an arrow from the "use" to the "def". Here is an example:
![Use Def](./docs/01-use-def.svg)
-From an implementation point of view, our `Node` type also maintains a reverse link.
+From an implementation point of view, our `Node` type also maintains a both links.
This means that in the above scenario:
* Since `A` is a "use" of `B`, then `B` will appear in `A`'s list of inputs.
@@ -122,7 +117,7 @@ public abstract class Node {
There are two categories of Nodes in the intermediate representation.
-* **Control Nodes** - these represent the control flow subgraph (CFG) of the compiled program
+* **Control Nodes** - these represent the Control Flow Graph (CFG) of the compiled program
* **Data Nodes** - these capture the data semantics
The following control and data nodes appear in this chapter.
@@ -133,9 +128,12 @@ The following control and data nodes appear in this chapter.
| Return | Control | Represents the termination of a function | Predecessor control node, Data node value | Return value of the function |
| Constant | Data | Represents constants such as integer literals | None, however Start node is set as input to enable graph walking | Value of the constant |
-Within a traditional basic block, instructions are executed in sequence. In the Sea of Nodes model, the correct sequence of instructions is determined by a scheduling
-algorithm that depends only on dependencies between nodes (including control dependencies) that are explicit as edges in the graph. This enables a number of optimizations
-at very little cost (nearly always small constant time) because all dependencies are always available.
+Within a traditional basic block, instructions are executed in sequence. In the
+Sea of Nodes model, the correct sequence of instructions is determined by a
+scheduling algorithm that depends only on dependencies between nodes (including
+control dependencies) that are explicit as edges in the graph. This enables a
+number of optimizations at very little cost (nearly always small constant time)
+because all dependencies are always available.
### Unique Node ID
@@ -168,7 +166,7 @@ The Constant's value is the value stored in it.
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.
-In this presentation, Return functions as a Stop node, since multiple `return` statements are not possible.
+In this presentation, Return ends the program, since multiple `return` statements are not possible.
The Stop node will be introduced in [Chapter 5](../chapter05/README.md) when we implement `if` statements.
The Return's output is the value from the data node.
@@ -187,3 +185,20 @@ return 1;
* Control edges are red and thicker than data edges
* The edges from Constants to Start are shown as dashed gray lines as these are not true control edges
* We label each edge with its position in the `_inputs` array, thus `0` means the edge is `_inputs[0]`.
+
+
+
+## Reading Material
+
+Our data structures are based upon the descriptions provided in the following papers:
+
+* [From Quads to Graphs: An Intermediate Representation's Journey](http://softlib.rice.edu/pub/CRPC-TRs/reports/CRPC-TR93366-S.pdf)
+* [Combining Analyses, Combining Optimizations](https://dl.acm.org/doi/pdf/10.1145/201059.201061)
+* [A Simple Graph-Based Intermediate Representation](https://www.oracle.com/technetwork/java/javase/tech/c2-ir95-150110.pdf)
+* [Global Code Motion Global Value Numbering](https://courses.cs.washington.edu/courses/cse501/06wi/reading/click-pldi95.pdf)
+* [EasySSA](https://www.dropbox.com/scl/fi/0ww4sgl3ynep9hhe3i4xn/EasySSA.pdf?rlkey=2cp78hzxke62flkmyneiebzoz&dl=0)
+* [SeaOfNodes](https://www.dropbox.com/scl/fi/cxykfvlzsmlcatyg6rlbt/SeaOfNodes.pdf?rlkey=z6o7y3rwr6atrejilcze6r8x0&e=1&dl=0)
+
+Following the lead from above, we represent our intermediate representation using an object oriented data model. Details of the
+representation follow.
+
diff --git a/chapter02/README.md b/chapter02/README.md
index 9b6d3651..86b7aac0 100644
--- a/chapter02/README.md
+++ b/chapter02/README.md
@@ -27,15 +27,7 @@ Here is the [complete language grammar](docs/02-grammar.md) for this chapter.
## Extensions to Intermediate Representation
-In [Chapter 1](../chapter01/README.md) we introduced the following nodes.
-
-| Node Name | Type | Description | Inputs | Value |
-|-----------|---------|------------------------------------|------------------------------------------------------------------|-------------------------------------------------------|
-| Start | Control | Start of function | None | None for now as we do not have function arguments yet |
-| Return | Control | End of function | Predecessor control node, Data node value | Return value of the function |
-| Constant | Data | Constants such as integer literals | None, however Start node is set as input to enable graph walking | Value of the constant |
-
-We extend the set of nodes by adding the following additional node types.
+We extend the set of nodes by adding the following additional node types:
| Node Name | Type | Description | Inputs | Value |
|-----------|------|-------------------------------|------------------------------------------------------------|-----------------------------|
@@ -46,15 +38,7 @@ We extend the set of nodes by adding the following additional node types.
| Minus | Data | Negate a value | One data node, value is negated | Result of the unary minus |
-## *Value* equality vs *Reference* equality
-
-In much of Simple, Nodes are looked up (`find()` calls) via *reference*
-equality. This is by far the most common case. In [Chapter
-9](../chapter09/README.md) we introduce *value* equality for the first time.
-In both cases the choice of value-vs-reference equality is intentional: it is
-*never* correct to "just pick one or the other kind of equality". When in
-doubt check the context: only *Global Value Numbering* uses value equality;
-everywhere else we mean reference equality.
+We keep from [Chapter 1](../chapter01/README.md) the following nodes: Start, Return, Constant.
## Peephole Optimizations
@@ -65,7 +49,7 @@ locally rewrite the graph.
During parsing, these peephole optimizations are particularly easy to check and
apply: there are no uses (yet) of a just-created Node from a just-parsed piece
-of syntax, so there's no effort to the "rewrite" part of the problem. We just
+of syntax, so there's no effort to the "rewrite" part of the program. We just
replace in-place before installing Nodes into the graph.
This replacement might allow us to *kill* the unused inputs from the replaced
@@ -135,14 +119,14 @@ Our lattice elements can be one of three types:
An invariant of peephole optimizations is that the type of a Node always moves
*up* the lattice (towards "top"); peepholes are *pessimistic* assuming the worst
until they can prove better. A later *optimistic* optimization will start all
-Nodes at *top* and move Types *down* the lattice as eager assumptions are
+Nodes at top and move Types down the lattice as eager assumptions are
proven wrong.
In later chapters we will explore extending this lattice, as it frequently
forms the heart of core optimizations we want our compiler to do.
We add a `_type` field to every Node, to store its current computed best
-`Type`. We need a field to keep the optimizer runtime linear, and later when
+`Type`. We need the field to keep the optimizer runtime linear, and later when
doing an optimistic version of constant propagation (called [Sparse Conditional
Constant Propagation](https://en.wikipedia.org/wiki/Sparse_conditional_constant_propagation)).
@@ -162,10 +146,14 @@ There are other important properties of the Lattice that we discuss in [Chapter
The following schematic shows the operations before peephole folding. The parser
optimizes each node as it is built; it does not retain this entire intermediate graph:
+```java
+return 1 + 2 * 3 + -5;
+```
+
![Example Visual](./docs/02-pre-peephole-ex1.svg)
* Control nodes appear as rectangular boxes with pale yellow backgrounds
-* Control edges are red and thicker than data edges
+* Control edges are in red
* The edges from Constants to Start are shown as dashed gray lines as these are not true control edges
* We label each edge with its position in the node's list of inputs.
@@ -226,6 +214,7 @@ This can be captured by:
```java
// Replace constant computations from non-constants with a constant node
if (!(this instanceof ConstantNode) && type.isConstant()) {
+ ....
}
```
Note how we return a constant type when doing constant folding
@@ -284,3 +273,16 @@ _type=null;
We finally end up with this:
![Example Visual](./docs/02-demonstration-peephole.svg)
+
+
+## Implementation Notes
+
+### *Value* equality vs *Reference* equality
+
+In much of Simple, Nodes are looked up (`find()` calls) via *reference*
+equality. This is by far the most common case. In [Chapter
+9](../chapter09/README.md) we introduce *value* equality for the first time.
+In both cases the choice of value-vs-reference equality is intentional: it is
+*never* correct to "just pick one or the other kind of equality". When in
+doubt check the context: only *Global Value Numbering* uses value equality;
+everywhere else we mean reference equality.
diff --git a/chapter03/README.md b/chapter03/README.md
index 5998efad..ce5901ce 100644
--- a/chapter03/README.md
+++ b/chapter03/README.md
@@ -31,25 +31,13 @@ Here is the [complete language grammar](docs/03-grammar.md) for this chapter.
## Extensions to Intermediate Representation
-To recap, our list of Nodes is:
-
-| Node Name | Type | Description | Inputs | Value |
-|-----------|---------|------------------------------------|------------------------------------------------------------------|-------------------------------------------------------|
-| Start | Control | Start of function | None | None for now as we do not have function arguments yet |
-| Return | Control | End of function | Predecessor control node, Data node value | Return value of the function |
-| Constant | Data | Constants such as integer literals | None, however Start node is set as input to enable graph walking | Value of the constant |
-| Add | Data | Add two values | Two data nodes, values are added, order not important | Result of the add operation |
-| Sub | Data | Subtract a value from another | Two data nodes, values are subtracted, order matters | Result of the subtract |
-| Mul | Data | Multiply two values | Two data nodes, values are multiplied, order not important | Result of the multiply |
-| Div | Data | Divide a value by another | Two data nodes, values are divided, order matters | Result of the division |
-| Minus | Data | Negate a value | One data node, value is negated | Result of the unary minus |
-
In this chapter we introduce:
| Node Name | Type | Description | Inputs | Value |
|-----------|--------------|--------------------------------|---------------------------------|-------|
| Scope | Symbol Table | Represents scopes in the graph | All nodes that define variables | None |
+and keep all nodes from prior chapters.
## Symbol Tables
@@ -69,10 +57,13 @@ the stack of symbol tables, in lexical order.
A ScopeNode encapsulates the stack of symbol tables that are used to implement lexical scopes.
-* The nodes referenced by the names in a lexical scope become inputs to the ScopeNode; this makes ScopeNode a user of these values, thus keeping them alive while a particular lexical scope is alive.
- When the lexical scope ends, those inputs are removed. This is crucial because peepholes will remove dead Nodes and every new Node is effectively dead at the instant of creation without such a reference.
+* The nodes referenced by the names in a lexical scope become inputs to the
+ ScopeNode; this makes ScopeNode a user of these values, thus keeping them
+ alive while a particular lexical scope is alive. When the lexical scope
+ ends, those inputs are removed. This is crucial because peepholes will remove
+ dead Nodes and every new Node is effectively dead at the instant of creation.
* 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.
+ 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 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.
@@ -90,7 +81,7 @@ that uses the variable) this is when the expression dies:
int a=1;
{
int b=a*a*a;
- } // When this scope closes & ends, b (and the cube of a) dies
+ } // When this scope closes and ends, b (and the cube of a) dies
return a;
```
diff --git a/chapter04/README.md b/chapter04/README.md
index e7ae71d2..f9a2dd36 100644
--- a/chapter04/README.md
+++ b/chapter04/README.md
@@ -49,18 +49,8 @@ Following are revised or new nodes
> A `Proj` is a control node when it projects the control slot: `Proj#0` off `Start`.
> Every other projection is a data node. See `ProjNode.isCFG()`.
-Below is our list of Nodes from [Chapter 3](../chapter03/README.md):
-
-| Node Name | Type | Description | Inputs | Value |
-|-----------|--------------|------------------------------------|------------------------------------------------------------------|------------------------------|
-| Return | Control | End of function | Predecessor control node, Data node value | Return value of the function |
-| Constant | Data | Constants such as integer literals | None, however Start node is set as input to enable graph walking | Value of the constant |
-| Add | Data | Add two values | Two data nodes, values are added, order not important | Result of the add operation |
-| Sub | Data | Subtract a value from another | Two data nodes, values are subtracted, order matters | Result of the subtract |
-| Mul | Data | Multiply two values | Two data nodes, values are multiplied, order not important | Result of the multiply |
-| Div | Data | Divide a value by another | Two data nodes, values are divided, order matters | Result of the division |
-| Minus | Data | Negate a value | One data node, value is negated | Result of the unary minus |
-| Scope | Symbol Table | Represents scopes in the graph | All nodes that define variables | None |
+We keep all other nodes from prior chapters.
+
## Projection Nodes
@@ -267,11 +257,6 @@ track the current in-scope control node via the name `$ctrl`. This means that
when we need to create an edge to the predecessor control node, we simply look
up this name in the current scope.
-This introduces the idea that the control flow subgraph is a Petri net model.[^1]
-The control token moves virtually from node to node as execution proceeds. The
-initial control token is in Start, it then moves via the Proj node to Return.
-In later chapters we will see how the token moves across branches.
-
## More Peephole Optimizations
@@ -327,7 +312,7 @@ Here is a list of peepholes introduced in this Chapter, more will be introduced
## Peephole Walkthrough
-The peephole optimizations introduced in this chapter are local. They are triggered during parsing
+The peephole optimizations introduced in this chapter are all local. They are triggered during parsing
as new nodes are created, before the newly created node has any uses.
For example, when we parse a unary expression, and create a Node for the parsed expression,
@@ -432,6 +417,3 @@ Note the temporary add of a bogus user to the Node `m`. The reason this is
done is that we know `m` is the new replacement and is alive, but since it is
not yet hooked into the graph, it has no users yet. By adding a bogus user we
prevent it being mistaken for dead and being killed.
-
-[^1]: Click, C. (1995).
- Combining Analyses, Combining Optimizations, 131.
diff --git a/chapter04/appendix/dce.md b/chapter04/appendix/dce.md
index ad5be2d0..1f9dfa7a 100644
--- a/chapter04/appendix/dce.md
+++ b/chapter04/appendix/dce.md
@@ -4,12 +4,13 @@ Dead Code Elimination is the process or removing nodes that are dead(unused).
There are two kinds of dead code eliminations present in this chapter.
- Delete unused nodes when popping the scope.
-- Delete old nodes after finding a better replacement(after idealize call).
+- Delete old nodes after finding a better replacement (after calling `idealize()`).
-Both of the methods mentioned above delete the nodes recursively. However, the first case happens
-regardless of whether `peephole` is called or not.
+Both of the methods mentioned above delete the nodes recursively. However, the
+first case happens regardless of whether `peephole` is called or not.
### Delete unused nodes when popping the scope
+
A node is unused if it has no outputs(users).
### Example 1:
@@ -109,11 +110,10 @@ return 1;
- We move onto`a = 12`, since "a" has only one user left(the scope) and `this = scope` it can get killed easily.
- Same as before, we go through the inputs of "a" and will delete them from the outputs of the control node.
-*A constant node pointing to "Start"(ignored by the graph visualizer) but still maintains a connection:*
-
![Graph3](../docs/04-dce2.svg)
## Delete old nodes after finding a better replacement
+
Consider the following code:
```
return (2+1)+3;
diff --git a/chapter05/README.md b/chapter05/README.md
index f7ff382c..c507df07 100644
--- a/chapter05/README.md
+++ b/chapter05/README.md
@@ -22,7 +22,8 @@
15. [Finally](#finally)
16. [Example 2](#example-2)
17. [Example 3](#example-3)
-18. [More Examples](#more-examples)
+18. [Pushing addition up through a Phi](#pushing-addition-up-through-a-phi)
+19. [More Examples](#more-examples)
You can also read [this chapter](https://github.com/SeaOfNodes/Simple/tree/linear-chapter05) 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-chapter04...linear-chapter05) it to the previous chapter.
@@ -34,30 +35,6 @@ In this chapter we extend the language grammar with the following features:
Here is the [complete language grammar](docs/05-grammar.md) for this chapter.
-## Recap
-
-Here is a recap of the nodes introduced in previous chapters:
-
-| Node Name | Type | Chapter | Description | Inputs | Value |
-|-----------|----------------|---------|------------------------------------------------|-------------------------------------------------------------------------------|----------------------------------------------------------------------------|
-| Multi | Abstract class | 4 | A node that has a tuple result | | A tuple |
-| Start | Control | 1 | Start of function, now a MultiNode | | A tuple with a ctrl token and an `arg` data node |
-| Proj | Data / Control | 4 | Projection nodes extract values from MultiNode | A MultiNode and index | Result is the extracted value from the input MultiNode at offset index |
-| Bool | Data | 4 | Represents results of a comparison operator | Two data nodes | Result is a comparison, represented as integer value where 1=true, 0=false |
-| Not | Data | 4 | Logical not | One data node | Result converts 0 to 1 and vice versa |
-| Return | Control | 1 | End of function | Predecessor control node and a data node for the return value of the function | Return value of the function |
-| Constant | Data | 1 | Represents constants such as integer literals | None, however Start node is set as input to enable graph walking | Value of the constant |
-| Add | Data | 2 | Add two values | Two data nodes without restrictions on the order | Result of the add operation |
-| Sub | Data | 2 | Subtract a value from another | Two data nodes, the first one is subtracted by the second one | Result of the subtraction |
-| Mul | Data | 2 | Multiply two values | Two data nodes without restrictions on the order | Result of the multiplication |
-| Div | Data | 2 | Divide a value by another | Two data nodes, the first one is divided by the second one | Result of the division |
-| Minus | Data | 2 | Negate a value | One data node that value is negated | Result of the negation |
-| Scope | Symbol Table | 3 | Represents scopes in the graph | Nodes that represent the current value of variables | None |
-
-> A `Proj` is a control node when it projects a control slot: `Proj#0` off `Start`,
-> and both projections off an `If`. Every other projection is a data node.
-> See `ProjNode.isCFG()`.
-
## New Nodes
The following new nodes are introduced in this chapter:
@@ -69,6 +46,8 @@ The following new nodes are introduced in this chapter:
| Phi | Data | 5 | A phi function picks a value based on control flow | A Region, and data nodes for each control path | Depends on control flow path taken |
| Stop | Control | 5 | Termination of the program | All return nodes of the function | None |
+and keep all nodes from prior chapters.
+
#### `IfNode`
`If` node takes in both control and data (predicate expression) and routes the
@@ -113,7 +92,7 @@ exists in a “sea” of Nodes, with little control structure.
The “sea” of Nodes is useful for optimization, but does not represent any
traditional intermediate representation such as a CFG. We need a way to
serialize the graph and get back the control dependences. We do this with a
-simple global code motion algorithm.[^3]
+simple Global Code Motion [^3] algorithm in [Chapter 11](../chapter11/README.md).
#### `Stop` Nodes
`StopNode`s only have `ReturnNode` inputs. They mark the program termination.
@@ -174,7 +153,7 @@ At the merge point we merge two ScopeNodes. The goals are:
1) Merge names whose bindings have changed between the two nodes. For each such name, a Phi node is created, referencing the two original data nodes.
2) A new Region node is created representing the merged control flow. The phis have this region node as the first input.
-3) After the merge is completed, the duplicate is discarded, and its use of each of the nodes is also deleted.
+3) After the merge is completed, the duplicate `ScopeNode` is discarded along with its edges.
The merging logic takes advantage of the fact that the two ScopeNodes have the bound nodes in the same order in the list of inputs. This was ensured during duplicating the ScopeNode.
Although only the innermost occurrence of a name can have its binding changed, we scan all the nodes in our input list, and simply ignore ones where the binding has not changed.
@@ -212,8 +191,11 @@ Below is the graph after we created a `Region` node and merged the two definitio
![Graph2](./docs/05-graph2.svg)
The duplicate `ScopeNode` has been discarded and was merged into the other one.
-Since `a` had two different definitions in both scopes a `Phi` node was created and is now referenced from `a`.
-The `Phi` node's inputs are the `Region` node and the `Add` node from the `True` branch, and `Sub` node from the `False` branch.
+Since `a` had two different definitions in both scopes a `Phi` node was created
+and is now referenced from `a`. The `Phi` node's inputs are the `Region` node
+and the `Add` node from the `True` branch, and `Sub` node from the `False`
+branch. The `arg` variable did not get assigned; both scopes agree on the
+value so `arg` does not get a `Phi`.
### Finally
@@ -326,7 +308,54 @@ Post-peephole:
The common operator was pulled out and the Phi node only got applied to the
operands. Notice how only the second operand of the `==` changes, the first
one stays the same (`arg`). The implementation is in
-[`PhiNode.idealize()`](https://github.com/SeaOfNodes/Simple/blob/main/chapter05/src/main/java/com/seaofnodes/simple/node/PhiNode.java#L31-L71)
+[`PhiNode.idealize()`](src/main/java/com/seaofnodes/simple/node/PhiNode.java).
+
+In graph notation, with `r` denoting the same Region throughout:
+
+```text
+Phi(r, a+b, c+d) -> Phi(r, a, c) + Phi(r, b, d)
+```
+
+We call this **pulling the operation down** through the Phi: the common
+operation moves from the incoming paths to after their merge. Each new Phi
+selects the corresponding operand from the same path. If an operand is shared,
+as `arg` is above, its Phi collapses to that operand.
+
+
+## Pushing addition up through a Phi
+
+The opposite direction can expose constant folding. For example:
+
+```java
+int x = 1;
+if( arg ) x = 2;
+return (arg+x)+3;
+```
+
+The merge gives `x` a Phi of constants. Although `x` itself is not constant,
+adding 3 to each possible value is a compile-time calculation. The addition
+peephole reassociates the expression and **pushes the addition up** onto the
+incoming paths:
+
+```text
+(arg + Phi(r, 2, 1)) + 3
+ -> arg + Phi(r, 2+3, 1+3)
+ -> arg + Phi(r, 5, 4)
+```
+
+The implementation first appears here in
+[`AddNode.idealize()`](src/main/java/com/seaofnodes/simple/node/AddNode.java),
+for the shape `(x + Phi(constants)) + constant`. The final operand can also
+be another Phi of constants, but both Phis must use the **same Region** so
+their corresponding arms describe the same path.
+
+Why not push every operation through a Phi? That would duplicate work across
+the incoming paths, and the pull-down peephole could immediately undo it.
+Repeatedly applying the two rewrites would never finish. Here every new
+addition folds to a constant **before** we build the result Phi. There are no
+new additions left for that Phi to pull back down, and one runtime addition
+has disappeared. The folding requirement supplies both the benefit and the
+reason this pair of opposing rewrites terminates.
## More examples
diff --git a/chapter08/README.md b/chapter08/README.md
index 46e4eeca..f412fd36 100644
--- a/chapter08/README.md
+++ b/chapter08/README.md
@@ -131,12 +131,10 @@ We have several ways of implementing these backedges.
Phis that are stacked.
In this chapter we adopt option 3 as it is the simplest implementation on top
-of our basic `while` loop architecture. We also experimented with
-option 2. For readers who would like to see this alternative solution, we
-provide a separate branch with such an implementation.
+of our basic `while` loop architecture.[^1]
The implementation requires some careful handling of scopes. This is because
-we would like to only generate `PhiNode/RegionNode`s for continues if necessary.
+we would like to only generate `PhiNode/RegionNode`s for `continues` if necessary.
In our [basic loop architecture](../chapter07/README.md), by the time we get to
the loop backedge, we have already exited all nested blocks/scopes, we just
@@ -299,9 +297,16 @@ while(arg < 10) {
return t;
```
-In this code, the two statements within the loop may be evaluated in either order by the graph, so we must ensure that the new value `arg` is not cached before the value of `t` can be set to the previous value of `arg`.
+In this code, the two statements within the loop may be evaluated in either
+order by the graph, so we must ensure that the new value `arg` is not cached
+before the value of `t` can be set to the previous value of `arg`.
-This approach to evaluation is useful for testing because it allows a program to be executed without worrying about instruction selection, register allocation or code generation. It executes the graph produced with normal peephole optimization. In later chapters, the evaluator can compare graphs optimized with different worklist orders to detect inconsistent results. This chapter's fuzzer checks generated programs for compiler and evaluator failures.
+This approach to evaluation is useful for testing because it allows a program
+to be executed without worrying about instruction selection, register
+allocation or code generation. It executes the graph produced with normal
+peephole optimization. In later chapters, the evaluator can compare graphs
+optimized with different worklist orders to detect inconsistent results. This
+chapter's fuzzer checks generated programs for compiler and evaluator failures.
``` java
arg=arg+arg;
@@ -311,4 +316,10 @@ arg=arg+arg;
return arg;
```
-On the other hand, this engine is not designed to be particularly fast! Because not all expressions are cached, it's possible to construct programs like the preceding one which have exponential runtime.
+On the other hand, this engine is not designed to be particularly fast! Because
+not all expressions are cached, it's possible to construct programs like the
+preceding one which have exponential runtime.
+
+
+[^1]: We also experimented with option 2. For readers who would like to see this
+alternative solution, we provide a separate branch with such an implementation. \ No newline at end of file
diff --git a/chapter09/README.md b/chapter09/README.md
index 2a9abe4e..9c9eba07 100644
--- a/chapter09/README.md
+++ b/chapter09/README.md
@@ -182,54 +182,6 @@ Changes:
dependents onto the worklist.
-## Other Concerns
-
-There are more issues we will want to deal with in a later Chapter:
-
-* Blindly running peepholes in any order has some drawbacks:
-
- - Dead and dying stuff might get peepholes done... and then die. Wasted work.
- - Dead infinite loops often lead to infinite peephole cycles... if only we
- would get around to working on the "base" of the dead loop it would fold
- up.
- - Some peepholes naturally reduce the graph directly; while some keep its
- size the same but reduce other things (e.g. swapping a Mul-by-2 with a
- Shift), and we might end up with a few which try to grow the graph briefly
- before collapsing.
-
-* All this means is there's some benefit to running peepholes that reduce the
- graph directly (e.g. Dead Code Eliminate), before running peeps that reduce
- other things, before running all other peeps. This implies a sorted
- worklist, but the count of unique orders is really limited - a radix sort is
- all that is needed. We'll have to break up the peepholes into some
- categories like
- - "strictly reducing" vs
- - "same Nodes but swapping e.g. `Mul` for `Shift`, vs
- - getting more freedom (edge bypass), vs
- - "grow now because shrink later" (inlining lands in this camp).
-
-* Also there's a benefit to not always grabbing from either end of the list -
- many peep patterns might go quadratic if approached from one end or another,
- because they modify something then push it back onto the list where it
- immediately gets pulled again. I.e., you end up spinning in a loop repeating
- the same peeps while slowly migrating e.g. left-spine add-tree into a
- right-spine add-tree. A pseudo-random pull uses randomization to defeat bad
- peep patterns.
-
-* Why isn't `IterPeeps` just passing over all of the Nodes once or twice,
- instead of using a worklist with the `addDeps` mechanism? We could even
- visit them in a defs-before-uses order (e.g. Reverse Post Order).
-
- In the absence of loops exactly one such pass will find all local peepholes,
- and indeed the Parser already does this. However, this will fail to find
- opportunities at loops and farther remote cases - and to get those peepholes
- around loops will require another visit. It is easy to construct a case
- requiring O(N) passes, each of cost O(N) and the algorithm quickly goes
- quadratic.
-
- The `addDeps` solution avoids this quadratic cost, in exchange for some more
- costs in writing peepholes.
-
# Examples
@@ -299,3 +251,52 @@ Enabling the post parse worklist based optimization yields below. Notice that no
loop body:
![Graph6](./docs/09-graph6.svg)
+
+
+## Future Work
+
+There are more issues we will want to deal with in a later Chapter:
+
+* Blindly running peepholes in any order has some drawbacks:
+
+ - Dead and dying stuff might get peepholes done... and then die. Wasted work.
+ - Dead infinite loops often lead to infinite peephole cycles... if only we
+ would get around to working on the "base" of the dead loop it would fold
+ up.
+ - Some peepholes naturally reduce the graph directly; while some keep its
+ size the same but reduce other things (e.g. swapping a Mul-by-2 with a
+ Shift), and we might end up with a few which try to grow the graph briefly
+ before collapsing.
+
+* All this means is there's some benefit to running peepholes that reduce the
+ graph directly (e.g. Dead Code Eliminate), before running peeps that reduce
+ other things, before running all other peeps. This implies a sorted
+ worklist, but the count of unique orders is really limited - a radix sort is
+ all that is needed. We'll have to break up the peepholes into some
+ categories like
+ - "strictly reducing" vs
+ - "same Nodes but swapping e.g. `Mul` for `Shift`, vs
+ - getting more freedom (edge bypass), vs
+ - "grow now because shrink later" (inlining lands in this camp).
+
+* Also there's a benefit to not always grabbing from either end of the list -
+ many peep patterns might go quadratic if approached from one end or another,
+ because they modify something then push it back onto the list where it
+ immediately gets pulled again. I.e., you end up spinning in a loop repeating
+ the same peeps while slowly migrating e.g. left-spine add-tree into a
+ right-spine add-tree. A pseudo-random pull uses randomization to defeat bad
+ peep patterns.
+
+* Why isn't `IterPeeps` just passing over all of the Nodes once or twice,
+ instead of using a worklist with the `addDeps` mechanism? We could even
+ visit them in a defs-before-uses order (e.g. Reverse Post Order).
+
+ In the absence of loops exactly one such pass will find all local peepholes,
+ and indeed the Parser already does this. However, this will fail to find
+ opportunities at loops and farther remote cases - and to get those peepholes
+ around loops will require another visit. It is easy to construct a case
+ requiring O(N) passes, each of cost O(N) and the algorithm quickly goes
+ quadratic.
+
+ The `addDeps` solution avoids this quadratic cost, in exchange for some more
+ costs in writing peepholes.
diff --git a/chapter10a/README.md b/chapter10a/README.md
index b9ea258a..6f51df28 100644
--- a/chapter10a/README.md
+++ b/chapter10a/README.md
@@ -31,6 +31,9 @@ different fields are independent. Chapter 10b will remove it.
![One chain orders stores and loads](docs/example1.svg)
+The graphs show dependencies before local folding. Definitions appear above
+their uses, except for loop backedges; arrows point from use to definition.
+
| Node | Inputs | Result |
|----------|---|---|
| `New` | Control and a struct type | A fresh, non-null object pointer |
@@ -86,41 +89,6 @@ 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.
@@ -182,8 +150,9 @@ Within the type lattice, we now have the following domains:
Memory top and memory bottom are `TypeMem.TOP` and `TypeMem.BOT`. Neither
records the values stored in individual fields.
* ![Tuple](docs/type-tuple.svg) 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.
+ such as Start's control, memory, and argument. The pink headers identify tuples
+ of different lengths; each vertically stacked black `type` cell stands for
+ the full type lattice again, including the possibility of another tuple.
We use the following operations on the lattice:
@@ -299,6 +268,59 @@ To enable this behaviour, we make the following enhancements.
ptr value, we convert to `0`. See `NotNode.compute()`.
+## 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 up through a memory Phi. This extends
+[Chapter 5's push-through-Phi optimization](../chapter05/README.md#pushing-addition-up-through-a-phi):
+there, arithmetic folds on the incoming paths; here, a load can fold against
+a matching store. In graph notation, keeping the same pointer and field:
+
+```text
+Load(Phi(r, m0, m1), p.x)
+ -> Phi(r, Load(m0, p.x), Load(m1, p.x))
+```
+
+The Phi of memory becomes a Phi of loaded values. This chapter handles two
+incoming paths and requires a load-after-store fold on at least one of them.
+On a loop, that path must be the **backedge**: folding only on entry would
+leave a load that could be pushed around the cycle indefinitely. As with the
+arithmetic rewrite, exposing a fold is part of deciding to transform the
+graph, not just a hoped-for cleanup afterward. See
+[`LoadNode.idealize()`](src/main/java/com/seaofnodes/simple/node/LoadNode.java).
+
+The reverse direction extends
+[Chapter 5's common-operation pull-down](../chapter05/README.md#example-3):
+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.
+We must be more careful here with memory ops; it is possible to lose some
+type precision, so we check for that as part of the profitability.
+
+Also, a Store must have no other users, so moving it removes the original
+effect. A Load must have no possible conflicting writes or memory merges among
+its memory's users; otherwise delaying the read could change its value. Much
+later, we will be adding anti-dependences, but for now we do a conservative
+check.
+
+### Missing an easy one
+
+The single memory chain is correct but conservative. A store to `y` can
+prevent forwarding an earlier store to `x`, even when the objects are
+identical. This is a missed optimization and it motivates the next chapter.
+
## Executing the graph
The evaluator includes a small late scheduler. A load must execute before a
@@ -306,6 +328,11 @@ 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.
+For example, after allocating `p`, `int old = p.x; p.x = arg; return old;`
+reads the value before the final store. In the graph below, Load and Store
+consume the same memory state. The scheduler must execute the Load first;
+there is no ordinary data edge between them.
+
![A load must precede a clobbering store](docs/example2c.svg)
The scheduler is sufficient to execute this chapter. Chapter 11 develops
diff --git a/chapter10a/docs/Makefile b/chapter10a/docs/Makefile
index 055882b2..6fa0f057 100644
--- a/chapter10a/docs/Makefile
+++ b/chapter10a/docs/Makefile
@@ -1,14 +1 @@
-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)
+include ../../graph/docs.mk
diff --git a/chapter10a/docs/example1.json b/chapter10a/docs/example1.json
new file mode 100644
index 00000000..1b556c04
--- /dev/null
+++ b/chapter10a/docs/example1.json
@@ -0,0 +1,24 @@
+{
+ "ver": 1,
+ "comp": "chapter10a/docs/example1.svg",
+ "step": 1,
+ "roots": [13],
+ "scope": 0,
+ "nodes": [
+ {"id": 1, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null},
+ {"id": 2, "label": "$ctrl", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 1, "role": "CTRL", "label": null}], "proj": {"par": 1, "idx": 0}},
+ {"id": 3, "label": "$mem", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 1, "role": "MEM", "label": null}], "proj": {"par": 1, "idx": 1}},
+ {"id": 4, "label": "arg", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "DATA", "label": null}], "proj": {"par": 1, "idx": 2}},
+ {"id": 5, "label": "new Point", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 2, "role": "CTRL", "label": null}], "proj": null},
+ {"id": 6, "label": "#0", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null},
+ {"id": 7, "label": "Store p.x", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 6, "role": "DATA", "label": null}], "proj": null},
+ {"id": 8, "label": "Store p.y", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 7, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 6, "role": "DATA", "label": null}], "proj": null},
+ {"id": 9, "label": "#3", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null},
+ {"id": 10, "label": "Store p.x", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 8, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 9, "role": "DATA", "label": null}], "proj": null},
+ {"id": 11, "label": "Store p.y", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 10, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 4, "role": "DATA", "label": null}], "proj": null},
+ {"id": 12, "label": "Load p.x", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 11, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 0, "role": "DATA", "label": null}], "proj": null},
+ {"id": 13, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 2, "role": "CTRL", "label": null}, {"idx": 1, "def": 11, "role": "MEM", "label": null}, {"idx": 2, "def": 12, "role": "DATA", "label": null}], "proj": null}
+ ],
+ "groups": [
+ ]
+}
diff --git a/chapter10a/docs/example1.svg b/chapter10a/docs/example1.svg
index fdb4864c..b50701b0 100644
--- a/chapter10a/docs/example1.svg
+++ b/chapter10a/docs/example1.svg
@@ -1,73 +1,122 @@
-<?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="650pt" height="72pt"
- viewBox="0.00 0.00 650.34 72.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 68)">
-<title>Memory</title>
-<polygon fill="#fafbfc" stroke="none" points="-4,4 -4,-68 646.34,-68 646.34,4 -4,4"/>
-<!-- M0 -->
-<g id="node1" class="node">
-<title>M0</title>
-<polygon fill="#dcecff" stroke="#758191" points="124.26,-38.54 124.26,-53.46 87.87,-64 36.4,-64 0,-53.46 0,-38.54 36.4,-28 87.87,-28 124.26,-38.54"/>
-<text text-anchor="middle" x="62.13" y="-41.73" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#152330">Initial memory</text>
-</g>
-<!-- X -->
-<g id="node2" class="node">
-<title>X</title>
-<polygon fill="#dcecff" stroke="#758191" points="278.2,-38.54 278.2,-53.46 244.83,-64 197.63,-64 164.26,-53.46 164.26,-38.54 197.63,-28 244.83,-28 278.2,-38.54"/>
-<text text-anchor="middle" x="221.23" y="-41.73" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#152330">Store p.x = 3</text>
-</g>
-<!-- X&#45;&gt;M0 -->
-<g id="edge3" class="edge">
-<title>X&#45;&gt;M0</title>
-<path fill="none" stroke="#2867b2" stroke-width="1.5" d="M163.79,-46C163.79,-46 133.87,-46 133.87,-46"/>
-<polygon fill="#2867b2" stroke="#2867b2" stroke-width="1.5" points="133.87,-43.55 126.87,-46 133.87,-48.45 133.87,-43.55"/>
-</g>
-<!-- Y -->
-<g id="node3" class="node">
-<title>Y</title>
-<polygon fill="#dcecff" stroke="#758191" points="446.59,-38.54 446.59,-53.46 408.98,-64 355.8,-64 318.2,-53.46 318.2,-38.54 355.8,-28 408.98,-28 446.59,-38.54"/>
-<text text-anchor="middle" x="382.39" y="-41.73" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#152330">Store p.y = arg</text>
-</g>
-<!-- Y&#45;&gt;X -->
-<g id="edge2" class="edge">
-<title>Y&#45;&gt;X</title>
-<path fill="none" stroke="#2867b2" stroke-width="1.5" d="M317.77,-46C317.77,-46 287.85,-46 287.85,-46"/>
-<polygon fill="#2867b2" stroke="#2867b2" stroke-width="1.5" points="287.85,-43.55 280.85,-46 287.85,-48.45 287.85,-43.55"/>
-</g>
-<!-- L -->
-<g id="node4" class="node">
-<title>L</title>
-<path fill="#edf3f7" stroke="#758191" d="M536.34,-36C536.34,-36 498.59,-36 498.59,-36 492.59,-36 486.59,-30 486.59,-24 486.59,-24 486.59,-12 486.59,-12 486.59,-6 492.59,0 498.59,0 498.59,0 536.34,0 536.34,0 542.34,0 548.34,-6 548.34,-12 548.34,-12 548.34,-24 548.34,-24 548.34,-30 542.34,-36 536.34,-36"/>
-<text text-anchor="middle" x="517.46" y="-13.72" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#152330">Load p.x</text>
-</g>
-<!-- L&#45;&gt;Y -->
-<g id="edge1" class="edge">
-<title>L&#45;&gt;Y</title>
-<path fill="none" stroke="#2867b2" stroke-width="1.5" d="M486.13,-32C486.13,-32 434.33,-32 434.33,-32"/>
-<polygon fill="#2867b2" stroke="#2867b2" stroke-width="1.5" points="434.33,-29.55 427.33,-32 434.33,-34.45 434.33,-29.55"/>
-</g>
-<!-- Return -->
-<g id="node5" class="node">
-<title>Return</title>
-<polygon fill="#fff1c4" stroke="#758191" points="642.34,-64 588.34,-64 588.34,-28 642.34,-28 642.34,-64"/>
-<text text-anchor="middle" x="615.34" y="-41.73" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#152330">Return</text>
-</g>
-<!-- Return&#45;&gt;Y -->
-<g id="edge4" class="edge">
-<title>Return&#45;&gt;Y</title>
-<path fill="none" stroke="#2867b2" stroke-width="1.5" d="M588.16,-50C588.16,-50 455.99,-50 455.99,-50"/>
-<polygon fill="#2867b2" stroke="#2867b2" stroke-width="1.5" points="455.99,-47.55 448.99,-50 455.99,-52.45 455.99,-47.55"/>
-</g>
-<!-- Return&#45;&gt;L -->
-<g id="edge5" class="edge">
-<title>Return&#45;&gt;L</title>
-<path fill="none" stroke="#536477" stroke-width="1.5" d="M588.07,-32C588.07,-32 558,-32 558,-32"/>
-<polygon fill="#536477" stroke="#536477" stroke-width="1.5" points="558,-29.55 551,-32 558,-34.45 558,-29.55"/>
-</g>
-</g>
-</svg>
+<svg aria-label="Compiler graph" width="471" height="870" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 471 870" style="font-family: sans-serif;"><style>
+ html, body { height:100%; margin:0; font-family:sans-serif; }
+ .viewer { height:100%; display:grid; grid-template-rows:auto minmax(0,1fr) auto; }
+ .source { padding:8px; border-bottom:1px solid #ccc; }
+ #program { width:min(600px,80vw); vertical-align:middle; }
+ #status { margin-top:4px; }
+ #graph { min-height:0; overflow:hidden; position:relative; background:#fafbfc; }
+ #elk { width:100%; height:100%; }
+ #graph svg { display:block; }
+ .tools { display:flex; flex-wrap:wrap; align-items:center; gap:14px; margin-top:8px; font-size:12px; }
+ .hint { color:#616c78; }
+ .ctrl { color:#b63838; } .mem { color:#2867b2; } .data { color:#536477; }
+ .edge { fill:none; stroke-width:1.5; }
+ .edge.CTRL { stroke-width:2; }
+ .edge.ASSOC { stroke-dasharray:4 4; }
+ .edge.PARSER { stroke-dasharray:5 4; }
+ .parser rect { fill:#f6f0ff; stroke:#7956a5; stroke-width:1.5; stroke-dasharray:5 4; }
+ .edge.selected { stroke-width:3.5; }
+ .edge.bundle { stroke-width:4; }
+ .edge.bundle.selected { stroke-width:6; }
+ .edge-hit { fill:none; stroke:transparent; stroke-width:12; vector-effect:non-scaling-stroke; pointer-events:stroke; cursor:pointer; }
+ .edge-jump { fill:transparent; pointer-events:all; cursor:pointer; }
+ .node { cursor:pointer; }
+ .box { stroke:#758191; stroke-width:1; }
+ .node.held .box { stroke-dasharray:5 3; }
+ .node.near .box { stroke:#c18221; stroke-width:2; }
+ .node.focus .box { stroke:#9c4900; stroke-width:3; }
+ .node.selected .box { stroke:#1569b9; stroke-width:3; }
+ .node.folded .box { stroke-width:4; }
+ .node.folding .box { fill:#f4ccd7; }
+ .fold-tag { font-size:9px; font-weight:700; fill:#963751; }
+ .peep-area { fill:#fff3d9; fill-opacity:.8; stroke:#c18221; stroke-width:2; pointer-events:none; }
+ .node.peep-edit .box { stroke:#9c4900; stroke-width:3; }
+ .node.error .box { stroke:#b42318; stroke-width:4; }
+ #event.error { color:#b42318; font-weight:600; white-space:pre-wrap; overflow-wrap:anywhere; }
+ .peep-ghosts { pointer-events:none; }
+ .region-box { stroke:#9da9b8; stroke-width:1.5; }
+ .fold-btn { cursor:pointer; }
+ .fold-btn rect { fill:white; stroke:#596674; }
+ .fold-btn text { font-size:14px; fill:#152330; }
+ .fold-btn:focus rect { stroke:#1569b9; stroke-width:2; }
+ .shortcut { cursor:pointer; }
+ .shortcut path { fill:none; stroke-width:1.5; }
+ .shortcut rect { fill:#fff8ec; stroke:#b63838; stroke-dasharray:3 2; }
+ .shortcut text { font-size:11px; fill:#783030; }
+ .shortcut.selected rect, .shortcut:focus rect { stroke-width:2.5; }
+ .label { font-size:12px; fill:#152330; font-weight:600; }
+ .node-id { font-size:10px; fill:#596674; font-weight:400; }
+ .type { font-size:11px; fill:#455768; }
+ .port { stroke:#536477; stroke-width:1; }
+ .row { stroke:#b6afcf; stroke-width:1; }
+ .bind { font-size:12px; fill:#152330; }
+ .slot { font-size:10px; fill:#364352; paint-order:stroke; stroke:#fafbfc; stroke-width:3; }
+ #detail { position:absolute; top:8px; right:8px; margin:0; padding:12px; max-width:320px;
+ max-height:45%; overflow:auto; white-space:pre-wrap; overflow-wrap:anywhere;
+ background:#fffffff2; border:1px solid #bac5d0; border-radius:4px; font-size:12px; }
+ .controls { display:flex; flex-wrap:wrap; justify-content:center; align-items:center; gap:6px; padding:6px; }
+ .button { color:white; background:black; font-size:20px; min-width:30px; }
+ .button:disabled { background:grey; }
+ .count { font-size:20px; min-width:35px; text-align:center; }
+ #N { width:6ch; }
+ #scrub { width:clamp(120px,35vw,600px); }
+ </style><defs><marker id="arrow-CTRL" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#b63838"/></marker><marker id="arrow-DATA" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#536477"/></marker><marker id="arrow-MEM" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#2867b2"/></marker><marker id="arrow-ASSOC" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#9396a3"/></marker><marker id="arrow-PARSER" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#7956a5"/></marker></defs><rect width="100%" height="100%" fill="rgb(250, 251, 252)"/><g><g class="regions" style="opacity: 1;"/><rect class="peep-area" rx="36" style="opacity: 0;"/><g class="edges"><path class="edge CTRL" id="e5i0" d="M84,118 L84,138 L318,138 L318,166" stroke="#b63838" marker-start="url(#arrow-CTRL)" style="opacity: 1;"><title>#5[0] → #2 · CTRL</title></path><path class="edge ASSOC" id="e6i0" d="M387,46 C387,70 166,145 166,169" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#6[0] → #1 · ASSOC</title></path><path class="edge MEM" id="e7i1" d="M204,118 L204,128 L95,128 L95,232 L143,232 L143,270" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#7[1] → #3 · MEM</title></path><path class="edge DATA" id="e7i2" d="M318,222 L318,242 L167,242 L167,270" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#7[2] → #5 · DATA</title></path><path class="edge DATA" id="e7i3" d="M166,222 L166,232 L191,232 L191,270" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#7[3] → #6 · DATA</title></path><path class="edge MEM" id="e8i1" d="M155,326 L155,336 L154,336 L154,374" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#8[1] → #7 · MEM</title></path><path class="edge DATA" id="e8i2" d="M318,222 L318,242 L225,242 L225,336 L178,336 L178,374" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#8[2] → #5 · DATA</title></path><path class="edge DATA" id="e8i3" d="M166,222 L166,232 L236,232 L236,346 L202,346 L202,374" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#8[3] → #6 · DATA</title></path><path class="edge ASSOC" id="e9i0" d="M387,46 C387,70 307,353 307,377" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#9[0] → #1 · ASSOC</title></path><path class="edge MEM" id="e10i1" d="M166,430 L166,440 L212,440 L212,478" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#10[1] → #8 · MEM</title></path><path class="edge DATA" id="e10i2" d="M318,222 L318,356 L236,356 L236,478" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#10[2] → #5 · DATA</title></path><path class="edge DATA" id="e10i3" d="M307,430 L307,440 L260,440 L260,478" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#10[3] → #9 · DATA</title></path><path class="edge MEM" id="e11i1" d="M224,534 L224,544 L317,544 L317,582" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#11[1] → #10 · MEM</title></path><path class="edge DATA" id="e11i2" d="M318,222 L318,356 L377,356 L377,544 L341,544 L341,582" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#11[2] → #5 · DATA</title></path><path class="edge DATA" id="e11i3" d="M324,118 L324,128 L388,128 L388,554 L365,554 L365,582" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#11[3] → #4 · DATA</title></path><path class="edge MEM" id="e12i1" d="M329,638 L329,648 L375,648 L375,686" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#12[1] → #11 · MEM</title></path><path class="edge DATA" id="e12i2" d="M318,222 L318,242 L399,242 L399,686" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#12[2] → #5 · DATA</title></path><path class="edge CTRL" id="e13i0" d="M84,118 L84,790" stroke="#b63838" marker-start="url(#arrow-CTRL)" style="opacity: 1;"><title>#13[0] → #2 · CTRL</title></path><path class="edge MEM" id="e13i1" d="M329,638 L329,648 L114,648 L114,790" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#13[1] → #11 · MEM</title></path><path class="edge DATA" id="e13i2" d="M387,742 L387,752 L144,752 L144,790" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#13[2] → #12 · DATA</title></path></g><g class="edge-hits"/><g class="parser" style="opacity: 1;"/><g class="nodes"><g class="node" id="n1" data-id="1" transform="translate(24,24)" style="opacity: 1;"><path class="box" d="M0,0H360V44H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="180" y="18"><tspan>Start</tspan><tspan class="node-id">  #1</tspan></text><text class="type" text-anchor="middle" y="35" x="180"/><title>#1 Start
+Type not computed
+
+</title></g><g class="node" id="n2" data-id="2" transform="translate(24,68)" style="opacity: 1;"><path class="box" d="M0,0H120V44H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>$ctrl</tspan><tspan class="node-id">  #2/0</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#2 $ctrl
+Projection 0 of #1
+Type not computed
+
+[0] → #1 CTRL</title></g><g class="node" id="n3" data-id="3" transform="translate(144,68)" style="opacity: 1;"><path class="box" d="M0,0H120V44H0Z" fill="#dcecff"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>$mem</tspan><tspan class="node-id">  #3/1</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#3 $mem
+Projection 1 of #1
+Type not computed
+
+[0] → #1 MEM</title></g><g class="node" id="n4" data-id="4" transform="translate(264,68)" style="opacity: 1;"><path class="box" d="M0,0H120V44H0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>arg</tspan><tspan class="node-id">  #4/2</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#4 arg
+Projection 2 of #1
+Type not computed
+
+[0] → #1 DATA</title></g><g class="node" id="n5" data-id="5" transform="translate(258,172)" style="opacity: 1;"><path class="box" d="M14,0H106Q120,0 120,14V30Q120,44 106,44H14Q0,44 0,30V14Q0,0 14,0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>new Point</tspan><tspan class="node-id">  #5</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#5 new Point
+Type not computed
+
+[0] → #2 CTRL</title><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><text class="slot" x="60" y="-10" text-anchor="middle">0</text></g><g class="node" id="n6" data-id="6" transform="translate(106,172)" style="opacity: 1;"><path class="box" d="M14,0H106Q120,0 120,14V30Q120,44 106,44H14Q0,44 0,30V14Q0,0 14,0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>#0</tspan><tspan class="node-id">  #6</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#6 #0
+Type not computed
+
+[0] → #1 ASSOC</title><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><text class="slot" x="60" y="-10" text-anchor="middle">0</text></g><g class="node" id="n7" data-id="7" transform="translate(95,276)" style="opacity: 1;"><path class="box" d="M10,0H110L120,10V34L110,44H10L0,34V10Z" fill="#dcecff"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>Store p.x</tspan><tspan class="node-id">  #7</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#7 Store p.x
+Type not computed
+
+[0] → — CTRL
+[1] → #3 MEM
+[2] → #5 DATA
+[3] → #6 DATA</title><circle class="port" cx="24" cy="-3" r="3" fill="white"/><circle class="port" cx="48" cy="-3" r="3" fill="#536477"/><circle class="port" cx="72" cy="-3" r="3" fill="#536477"/><circle class="port" cx="96" cy="-3" r="3" fill="#536477"/><text class="slot" x="24" y="-10" text-anchor="middle">0</text><text class="slot" x="48" y="-10" text-anchor="middle">1</text><text class="slot" x="72" y="-10" text-anchor="middle">2</text><text class="slot" x="96" y="-10" text-anchor="middle">3</text></g><g class="node" id="n8" data-id="8" transform="translate(106,380)" style="opacity: 1;"><path class="box" d="M10,0H110L120,10V34L110,44H10L0,34V10Z" fill="#dcecff"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>Store p.y</tspan><tspan class="node-id">  #8</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#8 Store p.y
+Type not computed
+
+[0] → — CTRL
+[1] → #7 MEM
+[2] → #5 DATA
+[3] → #6 DATA</title><circle class="port" cx="24" cy="-3" r="3" fill="white"/><circle class="port" cx="48" cy="-3" r="3" fill="#536477"/><circle class="port" cx="72" cy="-3" r="3" fill="#536477"/><circle class="port" cx="96" cy="-3" r="3" fill="#536477"/><text class="slot" x="24" y="-10" text-anchor="middle">0</text><text class="slot" x="48" y="-10" text-anchor="middle">1</text><text class="slot" x="72" y="-10" text-anchor="middle">2</text><text class="slot" x="96" y="-10" text-anchor="middle">3</text></g><g class="node" id="n9" data-id="9" transform="translate(247,380)" style="opacity: 1;"><path class="box" d="M14,0H106Q120,0 120,14V30Q120,44 106,44H14Q0,44 0,30V14Q0,0 14,0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>#3</tspan><tspan class="node-id">  #9</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#9 #3
+Type not computed
+
+[0] → #1 ASSOC</title><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><text class="slot" x="60" y="-10" text-anchor="middle">0</text></g><g class="node" id="n10" data-id="10" transform="translate(164,484)" style="opacity: 1;"><path class="box" d="M10,0H110L120,10V34L110,44H10L0,34V10Z" fill="#dcecff"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>Store p.x</tspan><tspan class="node-id">  #10</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#10 Store p.x
+Type not computed
+
+[0] → — CTRL
+[1] → #8 MEM
+[2] → #5 DATA
+[3] → #9 DATA</title><circle class="port" cx="24" cy="-3" r="3" fill="white"/><circle class="port" cx="48" cy="-3" r="3" fill="#536477"/><circle class="port" cx="72" cy="-3" r="3" fill="#536477"/><circle class="port" cx="96" cy="-3" r="3" fill="#536477"/><text class="slot" x="24" y="-10" text-anchor="middle">0</text><text class="slot" x="48" y="-10" text-anchor="middle">1</text><text class="slot" x="72" y="-10" text-anchor="middle">2</text><text class="slot" x="96" y="-10" text-anchor="middle">3</text></g><g class="node" id="n11" data-id="11" transform="translate(269,588)" style="opacity: 1;"><path class="box" d="M10,0H110L120,10V34L110,44H10L0,34V10Z" fill="#dcecff"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>Store p.y</tspan><tspan class="node-id">  #11</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#11 Store p.y
+Type not computed
+
+[0] → — CTRL
+[1] → #10 MEM
+[2] → #5 DATA
+[3] → #4 DATA</title><circle class="port" cx="24" cy="-3" r="3" fill="white"/><circle class="port" cx="48" cy="-3" r="3" fill="#536477"/><circle class="port" cx="72" cy="-3" r="3" fill="#536477"/><circle class="port" cx="96" cy="-3" r="3" fill="#536477"/><text class="slot" x="24" y="-10" text-anchor="middle">0</text><text class="slot" x="48" y="-10" text-anchor="middle">1</text><text class="slot" x="72" y="-10" text-anchor="middle">2</text><text class="slot" x="96" y="-10" text-anchor="middle">3</text></g><g class="node" id="n12" data-id="12" transform="translate(327,692)" style="opacity: 1;"><path class="box" d="M14,0H106Q120,0 120,14V30Q120,44 106,44H14Q0,44 0,30V14Q0,0 14,0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>Load p.x</tspan><tspan class="node-id">  #12</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#12 Load p.x
+Type not computed
+
+[0] → — CTRL
+[1] → #11 MEM
+[2] → #5 DATA
+[3] → — DATA</title><circle class="port" cx="24" cy="-3" r="3" fill="white"/><circle class="port" cx="48" cy="-3" r="3" fill="#536477"/><circle class="port" cx="72" cy="-3" r="3" fill="#536477"/><circle class="port" cx="96" cy="-3" r="3" fill="white"/><text class="slot" x="24" y="-10" text-anchor="middle">0</text><text class="slot" x="48" y="-10" text-anchor="middle">1</text><text class="slot" x="72" y="-10" text-anchor="middle">2</text><text class="slot" x="96" y="-10" text-anchor="middle">3</text></g><g class="node" id="n13" data-id="13" transform="translate(54,796)" style="opacity: 1;"><path class="box" d="M0,0H120V44H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>Return</tspan><tspan class="node-id">  #13</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#13 Return
+Type not computed
+
+[0] → #2 CTRL
+[1] → #11 MEM
+[2] → #12 DATA</title><circle class="port" cx="30" cy="-3" r="3" fill="#536477"/><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><circle class="port" cx="90" cy="-3" r="3" fill="#536477"/><text class="slot" x="30" y="-10" text-anchor="middle">0</text><text class="slot" x="60" y="-10" text-anchor="middle">1</text><text class="slot" x="90" y="-10" text-anchor="middle">2</text></g></g><g class="region-heads" style="opacity: 1;"/><g class="shortcuts" style="opacity: 1;"/><g class="peep-ghosts" pointer-events="none"/></g></svg>
diff --git a/chapter10a/docs/example2a.json b/chapter10a/docs/example2a.json
new file mode 100644
index 00000000..2fa04643
--- /dev/null
+++ b/chapter10a/docs/example2a.json
@@ -0,0 +1,31 @@
+{
+ "ver": 1,
+ "comp": "chapter10a/docs/example2a.svg",
+ "step": 1,
+ "roots": [19],
+ "scope": 0,
+ "nodes": [
+ {"id": 1, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null},
+ {"id": 2, "label": "$ctrl", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 1, "role": "CTRL", "label": null}], "proj": {"par": 1, "idx": 0}},
+ {"id": 3, "label": "$mem", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 1, "role": "MEM", "label": null}], "proj": {"par": 1, "idx": 1}},
+ {"id": 4, "label": "arg", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "DATA", "label": null}], "proj": {"par": 1, "idx": 2}},
+ {"id": 5, "label": "new Point", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 2, "role": "CTRL", "label": null}], "proj": null},
+ {"id": 6, "label": "#0", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null},
+ {"id": 7, "label": "Store p.x", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 6, "role": "DATA", "label": null}], "proj": null},
+ {"id": 8, "label": "Store p.y", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 7, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 6, "role": "DATA", "label": null}], "proj": null},
+ {"id": 9, "label": "If", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 2, "role": "CTRL", "label": null}, {"idx": 1, "def": 4, "role": "DATA", "label": null}], "proj": null},
+ {"id": 10, "label": "True", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 9, "role": "CTRL", "label": null}], "proj": {"par": 9, "idx": 0}},
+ {"id": 11, "label": "False", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 9, "role": "CTRL", "label": null}], "proj": {"par": 9, "idx": 1}},
+ {"id": 12, "label": "#3", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null},
+ {"id": 13, "label": "Store p.x", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 8, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 12, "role": "DATA", "label": null}], "proj": null},
+ {"id": 14, "label": "#4", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null},
+ {"id": 15, "label": "Store p.x", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 8, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 14, "role": "DATA", "label": null}], "proj": null},
+ {"id": 16, "label": "Region", "type": null, "kind": "REGION", "edges": [{"idx": 0, "def": 0, "role": "ASSOC", "label": null}, {"idx": 1, "def": 10, "role": "CTRL", "label": null}, {"idx": 2, "def": 11, "role": "CTRL", "label": null}], "proj": null},
+ {"id": 17, "label": "Phi $mem", "type": null, "kind": "PHI", "edges": [{"idx": 0, "def": 16, "role": "ASSOC", "label": null}, {"idx": 1, "def": 13, "role": "MEM", "label": null}, {"idx": 2, "def": 15, "role": "MEM", "label": null}], "proj": null},
+ {"id": 18, "label": "Load p.x", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 17, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 0, "role": "DATA", "label": null}], "proj": null},
+ {"id": 19, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 16, "role": "CTRL", "label": null}, {"idx": 1, "def": 17, "role": "MEM", "label": null}, {"idx": 2, "def": 18, "role": "DATA", "label": null}], "proj": null}
+ ],
+ "groups": [
+ {"id": 9, "par": 0, "nodes": [7, 8, 9, 10, 11, 13, 15, 16, 17]}
+ ]
+}
diff --git a/chapter10a/docs/example2a.svg b/chapter10a/docs/example2a.svg
index f0bbf531..eb43548f 100644
--- a/chapter10a/docs/example2a.svg
+++ b/chapter10a/docs/example2a.svg
@@ -1,85 +1,147 @@
-<?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="581pt" height="166pt"
- viewBox="0.00 0.00 581.12 166.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 162)">
-<title>Memory</title>
-<polygon fill="#fafbfc" stroke="none" points="-4,4 -4,-162 577.12,-162 577.12,4 -4,4"/>
-<!-- M -->
-<g id="node1" class="node">
-<title>M</title>
-<polygon fill="#dcecff" stroke="#758191" points="152.14,-101.54 152.14,-116.46 107.58,-127 44.56,-127 0,-116.46 0,-101.54 44.56,-91 107.58,-91 152.14,-101.54"/>
-<text text-anchor="middle" x="76.07" y="-104.72" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#152330">Incoming memory</text>
-</g>
-<!-- A -->
-<g id="node2" class="node">
-<title>A</title>
-<polygon fill="#dcecff" stroke="#758191" points="311.24,-132.54 311.24,-147.46 277.57,-158 229.95,-158 196.27,-147.46 196.27,-132.54 229.95,-122 277.57,-122 311.24,-132.54"/>
-<text text-anchor="middle" x="253.76" y="-135.72" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#152330">True: Store x</text>
-</g>
-<!-- A&#45;&gt;M -->
-<g id="edge2" class="edge">
-<title>A&#45;&gt;M</title>
-<path fill="none" stroke="#2867b2" stroke-width="1.5" d="M272.92,-121.71C272.92,-117.22 272.92,-113.67 272.92,-113.67 272.92,-113.67 161.73,-113.67 161.73,-113.67"/>
-<polygon fill="#2867b2" stroke="#2867b2" stroke-width="1.5" points="161.73,-111.22 154.73,-113.67 161.73,-116.12 161.73,-111.22"/>
-</g>
-<!-- B -->
-<g id="node3" class="node">
-<title>B</title>
-<polygon fill="#dcecff" stroke="#758191" points="315.37,-71.54 315.37,-86.46 279.28,-97 228.24,-97 192.14,-86.46 192.14,-71.54 228.24,-61 279.28,-61 315.37,-71.54"/>
-<text text-anchor="middle" x="253.76" y="-74.72" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#152330">False: Store x</text>
-</g>
-<!-- B&#45;&gt;M -->
-<g id="edge4" class="edge">
-<title>B&#45;&gt;M</title>
-<path fill="none" stroke="#2867b2" stroke-width="1.5" d="M234.6,-97.29C234.6,-101.78 234.6,-105.33 234.6,-105.33 234.6,-105.33 161.82,-105.33 161.82,-105.33"/>
-<polygon fill="#2867b2" stroke="#2867b2" stroke-width="1.5" points="161.82,-102.88 154.82,-105.33 161.82,-107.78 161.82,-102.88"/>
-</g>
-<!-- P -->
-<g id="node4" class="node">
-<title>P</title>
-<polygon fill="#f5e4fa" stroke="#758191" points="479.12,-79 448.18,-97 386.31,-97 355.37,-79 386.31,-61 448.18,-61 479.12,-79"/>
-<text text-anchor="middle" x="417.25" y="-74.72" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#152330">Phi(memory)</text>
-</g>
-<!-- P&#45;&gt;A -->
-<g id="edge1" class="edge">
-<title>P&#45;&gt;A</title>
-<path fill="none" stroke="#2867b2" stroke-width="1.5" d="M417.25,-97.3C417.25,-115.27 417.25,-140 417.25,-140 417.25,-140 320.67,-140 320.67,-140"/>
-<polygon fill="#2867b2" stroke="#2867b2" stroke-width="1.5" points="320.67,-137.55 313.67,-140 320.67,-142.45 320.67,-137.55"/>
-</g>
-<!-- P&#45;&gt;B -->
-<g id="edge3" class="edge">
-<title>P&#45;&gt;B</title>
-<path fill="none" stroke="#2867b2" stroke-width="1.5" d="M354.5,-79C354.5,-79 325.06,-79 325.06,-79"/>
-<polygon fill="#2867b2" stroke="#2867b2" stroke-width="1.5" points="325.06,-76.55 318.06,-79 325.06,-81.45 325.06,-76.55"/>
-</g>
-<!-- Region -->
-<g id="node5" class="node">
-<title>Region</title>
-<polygon fill="#fff1c4" stroke="#758191" points="282.65,-36 224.86,-36 204.29,0 303.23,0 282.65,-36"/>
-<text text-anchor="middle" x="253.76" y="-13.72" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#152330">Region</text>
-</g>
-<!-- P&#45;&gt;Region -->
-<g id="edge5" class="edge">
-<title>P&#45;&gt;Region</title>
-<path fill="none" stroke="#9396a3" stroke-width="1.5" stroke-dasharray="5,2" d="M417.25,-60.7C417.25,-42.73 417.25,-18 417.25,-18 417.25,-18 302.6,-18 302.6,-18"/>
-<polygon fill="#9396a3" stroke="#9396a3" stroke-width="1.5" points="302.6,-15.55 295.6,-18 302.6,-20.45 302.6,-15.55"/>
-</g>
-<!-- Return -->
-<g id="node6" class="node">
-<title>Return</title>
-<polygon fill="#fff1c4" stroke="#758191" points="573.12,-97 519.12,-97 519.12,-61 573.12,-61 573.12,-97"/>
-<text text-anchor="middle" x="546.12" y="-74.72" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#152330">Return</text>
-</g>
-<!-- Return&#45;&gt;P -->
-<g id="edge6" class="edge">
-<title>Return&#45;&gt;P</title>
-<path fill="none" stroke="#2867b2" stroke-width="1.5" d="M518.79,-79C518.79,-79 489.34,-79 489.34,-79"/>
-<polygon fill="#2867b2" stroke="#2867b2" stroke-width="1.5" points="489.34,-76.55 482.34,-79 489.34,-81.45 489.34,-76.55"/>
-</g>
-</g>
-</svg>
+<svg aria-label="Compiler graph" width="763" height="972" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 763 972" style="font-family: sans-serif;"><style>
+ html, body { height:100%; margin:0; font-family:sans-serif; }
+ .viewer { height:100%; display:grid; grid-template-rows:auto minmax(0,1fr) auto; }
+ .source { padding:8px; border-bottom:1px solid #ccc; }
+ #program { width:min(600px,80vw); vertical-align:middle; }
+ #status { margin-top:4px; }
+ #graph { min-height:0; overflow:hidden; position:relative; background:#fafbfc; }
+ #elk { width:100%; height:100%; }
+ #graph svg { display:block; }
+ .tools { display:flex; flex-wrap:wrap; align-items:center; gap:14px; margin-top:8px; font-size:12px; }
+ .hint { color:#616c78; }
+ .ctrl { color:#b63838; } .mem { color:#2867b2; } .data { color:#536477; }
+ .edge { fill:none; stroke-width:1.5; }
+ .edge.CTRL { stroke-width:2; }
+ .edge.ASSOC { stroke-dasharray:4 4; }
+ .edge.PARSER { stroke-dasharray:5 4; }
+ .parser rect { fill:#f6f0ff; stroke:#7956a5; stroke-width:1.5; stroke-dasharray:5 4; }
+ .edge.selected { stroke-width:3.5; }
+ .edge.bundle { stroke-width:4; }
+ .edge.bundle.selected { stroke-width:6; }
+ .edge-hit { fill:none; stroke:transparent; stroke-width:12; vector-effect:non-scaling-stroke; pointer-events:stroke; cursor:pointer; }
+ .edge-jump { fill:transparent; pointer-events:all; cursor:pointer; }
+ .node { cursor:pointer; }
+ .box { stroke:#758191; stroke-width:1; }
+ .node.held .box { stroke-dasharray:5 3; }
+ .node.near .box { stroke:#c18221; stroke-width:2; }
+ .node.focus .box { stroke:#9c4900; stroke-width:3; }
+ .node.selected .box { stroke:#1569b9; stroke-width:3; }
+ .node.folded .box { stroke-width:4; }
+ .node.folding .box { fill:#f4ccd7; }
+ .fold-tag { font-size:9px; font-weight:700; fill:#963751; }
+ .peep-area { fill:#fff3d9; fill-opacity:.8; stroke:#c18221; stroke-width:2; pointer-events:none; }
+ .node.peep-edit .box { stroke:#9c4900; stroke-width:3; }
+ .node.error .box { stroke:#b42318; stroke-width:4; }
+ #event.error { color:#b42318; font-weight:600; white-space:pre-wrap; overflow-wrap:anywhere; }
+ .peep-ghosts { pointer-events:none; }
+ .region-box { stroke:#9da9b8; stroke-width:1.5; }
+ .fold-btn { cursor:pointer; }
+ .fold-btn rect { fill:white; stroke:#596674; }
+ .fold-btn text { font-size:14px; fill:#152330; }
+ .fold-btn:focus rect { stroke:#1569b9; stroke-width:2; }
+ .shortcut { cursor:pointer; }
+ .shortcut path { fill:none; stroke-width:1.5; }
+ .shortcut rect { fill:#fff8ec; stroke:#b63838; stroke-dasharray:3 2; }
+ .shortcut text { font-size:11px; fill:#783030; }
+ .shortcut.selected rect, .shortcut:focus rect { stroke-width:2.5; }
+ .label { font-size:12px; fill:#152330; font-weight:600; }
+ .node-id { font-size:10px; fill:#596674; font-weight:400; }
+ .type { font-size:11px; fill:#455768; }
+ .port { stroke:#536477; stroke-width:1; }
+ .row { stroke:#b6afcf; stroke-width:1; }
+ .bind { font-size:12px; fill:#152330; }
+ .slot { font-size:10px; fill:#364352; paint-order:stroke; stroke:#fafbfc; stroke-width:3; }
+ #detail { position:absolute; top:8px; right:8px; margin:0; padding:12px; max-width:320px;
+ max-height:45%; overflow:auto; white-space:pre-wrap; overflow-wrap:anywhere;
+ background:#fffffff2; border:1px solid #bac5d0; border-radius:4px; font-size:12px; }
+ .controls { display:flex; flex-wrap:wrap; justify-content:center; align-items:center; gap:6px; padding:6px; }
+ .button { color:white; background:black; font-size:20px; min-width:30px; }
+ .button:disabled { background:grey; }
+ .count { font-size:20px; min-width:35px; text-align:center; }
+ #N { width:6ch; }
+ #scrub { width:clamp(120px,35vw,600px); }
+ </style><defs><marker id="arrow-CTRL" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#b63838"/></marker><marker id="arrow-DATA" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#536477"/></marker><marker id="arrow-MEM" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#2867b2"/></marker><marker id="arrow-ASSOC" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#9396a3"/></marker><marker id="arrow-PARSER" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#7956a5"/></marker></defs><rect width="100%" height="100%" fill="rgb(250, 251, 252)"/><g><g class="regions" style="opacity: 1;"><g class="region" transform="translate(24,277)"><rect class="region-box" rx="8" width="574" height="458" fill="#f2f7ef"/></g></g><rect class="peep-area" rx="36" style="opacity: 0;"/><g class="edges"><path class="edge CTRL" id="e5i0" d="M116,118 L116,148 L361,148 L361,166" stroke="#b63838" marker-start="url(#arrow-CTRL)" style="opacity: 1;"><title>#5[0] → #2 · CTRL</title></path><path class="edge ASSOC" id="e6i0" d="M419,46 C419,70 209,145 209,169" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#6[0] → #1 · ASSOC</title></path><path class="edge MEM" id="e7i1" d="M236,118 L236,138 L138,138 L138,252 L400,252 L400,311" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#7[1] → #3 · MEM</title></path><path class="edge DATA" id="e7i2" d="M361,222 L361,242 L424,242 L424,311" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#7[2] → #5 · DATA</title></path><path class="edge DATA" id="e7i3" d="M209,222 L209,232 L448,232 L448,311" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#7[3] → #6 · DATA</title></path><path class="edge MEM" id="e8i1" d="M412,367 L412,377 L445,377 L445,415" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#8[1] → #7 · MEM</title></path><path class="edge DATA" id="e8i2" d="M361,222 L361,242 L482,242 L482,377 L469,377 L469,415" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#8[2] → #5 · DATA</title></path><path class="edge DATA" id="e8i3" d="M209,222 L209,232 L493,232 L493,415" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#8[3] → #6 · DATA</title></path><path class="edge CTRL" id="e9i0" d="M116,118 L116,519" stroke="#b63838" marker-start="url(#arrow-CTRL)" style="opacity: 1;"><title>#9[0] → #2 · CTRL</title></path><path class="edge DATA" id="e9i1" d="M356,118 L356,128 L127,128 L127,262 L196,262 L196,519" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#9[1] → #4 · DATA</title></path><path class="edge ASSOC" id="e12i0" d="M419,46 C419,70 527,145 527,169" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#12[0] → #1 · ASSOC</title></path><path class="edge MEM" id="e13i1" d="M457,471 L457,481 L362,481 L362,530" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#13[1] → #8 · MEM</title></path><path class="edge DATA" id="e13i2" d="M361,222 L361,242 L341,242 L341,491 L386,491 L386,530" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#13[2] → #5 · DATA</title></path><path class="edge DATA" id="e13i3" d="M527,222 L527,491 L410,491 L410,530" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#13[3] → #12 · DATA</title></path><path class="edge ASSOC" id="e14i0" d="M419,46 C419,70 679,145 679,169" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#14[0] → #1 · ASSOC</title></path><path class="edge MEM" id="e15i1" d="M457,471 L457,481 L514,481 L514,530" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#15[1] → #8 · MEM</title></path><path class="edge DATA" id="e15i2" d="M361,222 L361,242 L538,242 L538,530" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#15[2] → #5 · DATA</title></path><path class="edge DATA" id="e15i3" d="M679,222 L679,252 L562,252 L562,530" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#15[3] → #14 · DATA</title></path><path class="edge CTRL" id="e16i1" d="M96,619 L96,629 L186,629 L186,667" stroke="#b63838" marker-start="url(#arrow-CTRL)" style="opacity: 1;"><title>#16[1] → #10 · CTRL</title></path><path class="edge CTRL" id="e16i2" d="M216,619 L216,667" stroke="#b63838" marker-start="url(#arrow-CTRL)" style="opacity: 1;"><title>#16[2] → #11 · CTRL</title></path><path class="edge MEM" id="e17i1" d="M374,586 L374,629 L318,629 L318,667" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#17[1] → #13 · MEM</title></path><path class="edge MEM" id="e17i2" d="M526,586 L526,639 L358,639 L358,667" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#17[2] → #15 · MEM</title></path><path class="edge MEM" id="e18i1" d="M338,723 L338,750 L397,750 L397,788" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#18[1] → #17 · MEM</title></path><path class="edge DATA" id="e18i2" d="M361,222 L361,242 L608,242 L608,750 L421,750 L421,788" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#18[2] → #5 · DATA</title></path><path class="edge CTRL" id="e19i0" d="M186,723 L186,892" stroke="#b63838" marker-start="url(#arrow-CTRL)" style="opacity: 1;"><title>#19[0] → #16 · CTRL</title></path><path class="edge MEM" id="e19i1" d="M338,723 L338,750 L216,750 L216,892" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#19[1] → #17 · MEM</title></path><path class="edge DATA" id="e19i2" d="M409,844 L409,854 L246,854 L246,892" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#19[2] → #18 · DATA</title></path></g><g class="edge-hits"/><g class="parser" style="opacity: 1;"/><g class="nodes"><g class="node" id="n1" data-id="1" transform="translate(56,24)" style="opacity: 1;"><path class="box" d="M0,0H360V44H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="180" y="18"><tspan>Start</tspan><tspan class="node-id">  #1</tspan></text><text class="type" text-anchor="middle" y="35" x="180"/><title>#1 Start
+Type not computed
+
+</title></g><g class="node" id="n2" data-id="2" transform="translate(56,68)" style="opacity: 1;"><path class="box" d="M0,0H120V44H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>$ctrl</tspan><tspan class="node-id">  #2/0</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#2 $ctrl
+Projection 0 of #1
+Type not computed
+
+[0] → #1 CTRL</title></g><g class="node" id="n3" data-id="3" transform="translate(176,68)" style="opacity: 1;"><path class="box" d="M0,0H120V44H0Z" fill="#dcecff"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>$mem</tspan><tspan class="node-id">  #3/1</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#3 $mem
+Projection 1 of #1
+Type not computed
+
+[0] → #1 MEM</title></g><g class="node" id="n4" data-id="4" transform="translate(296,68)" style="opacity: 1;"><path class="box" d="M0,0H120V44H0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>arg</tspan><tspan class="node-id">  #4/2</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#4 arg
+Projection 2 of #1
+Type not computed
+
+[0] → #1 DATA</title></g><g class="node" id="n5" data-id="5" transform="translate(301,172)" style="opacity: 1;"><path class="box" d="M14,0H106Q120,0 120,14V30Q120,44 106,44H14Q0,44 0,30V14Q0,0 14,0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>new Point</tspan><tspan class="node-id">  #5</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#5 new Point
+Type not computed
+
+[0] → #2 CTRL</title><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><text class="slot" x="60" y="-10" text-anchor="middle">0</text></g><g class="node" id="n6" data-id="6" transform="translate(149,172)" style="opacity: 1;"><path class="box" d="M14,0H106Q120,0 120,14V30Q120,44 106,44H14Q0,44 0,30V14Q0,0 14,0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>#0</tspan><tspan class="node-id">  #6</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#6 #0
+Type not computed
+
+[0] → #1 ASSOC</title><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><text class="slot" x="60" y="-10" text-anchor="middle">0</text></g><g class="node" id="n7" data-id="7" transform="translate(352,317)" style="opacity: 1;"><path class="box" d="M10,0H110L120,10V34L110,44H10L0,34V10Z" fill="#dcecff"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>Store p.x</tspan><tspan class="node-id">  #7</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#7 Store p.x
+Type not computed
+
+[0] → — CTRL
+[1] → #3 MEM
+[2] → #5 DATA
+[3] → #6 DATA</title><circle class="port" cx="24" cy="-3" r="3" fill="white"/><circle class="port" cx="48" cy="-3" r="3" fill="#536477"/><circle class="port" cx="72" cy="-3" r="3" fill="#536477"/><circle class="port" cx="96" cy="-3" r="3" fill="#536477"/><text class="slot" x="24" y="-10" text-anchor="middle">0</text><text class="slot" x="48" y="-10" text-anchor="middle">1</text><text class="slot" x="72" y="-10" text-anchor="middle">2</text><text class="slot" x="96" y="-10" text-anchor="middle">3</text></g><g class="node" id="n8" data-id="8" transform="translate(397,421)" style="opacity: 1;"><path class="box" d="M10,0H110L120,10V34L110,44H10L0,34V10Z" fill="#dcecff"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>Store p.y</tspan><tspan class="node-id">  #8</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#8 Store p.y
+Type not computed
+
+[0] → — CTRL
+[1] → #7 MEM
+[2] → #5 DATA
+[3] → #6 DATA</title><circle class="port" cx="24" cy="-3" r="3" fill="white"/><circle class="port" cx="48" cy="-3" r="3" fill="#536477"/><circle class="port" cx="72" cy="-3" r="3" fill="#536477"/><circle class="port" cx="96" cy="-3" r="3" fill="#536477"/><text class="slot" x="24" y="-10" text-anchor="middle">0</text><text class="slot" x="48" y="-10" text-anchor="middle">1</text><text class="slot" x="72" y="-10" text-anchor="middle">2</text><text class="slot" x="96" y="-10" text-anchor="middle">3</text></g><g class="node" id="n9" data-id="9" transform="translate(36,525)" style="opacity: 1;"><path class="box" d="M0,0H240V44H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="120" y="18"><tspan>If</tspan><tspan class="node-id">  #9</tspan></text><text class="type" text-anchor="middle" y="35" x="120"/><title>#9 If
+Type not computed
+
+[0] → #2 CTRL
+[1] → #4 DATA</title><circle class="port" cx="80" cy="-3" r="3" fill="#536477"/><circle class="port" cx="160" cy="-3" r="3" fill="#536477"/><text class="slot" x="80" y="-10" text-anchor="middle">0</text><text class="slot" x="160" y="-10" text-anchor="middle">1</text></g><g class="node" id="n10" data-id="10" transform="translate(36,569)" style="opacity: 1;"><path class="box" d="M0,0H120V44H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>True</tspan><tspan class="node-id">  #10/0</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#10 True
+Projection 0 of #9
+Type not computed
+
+[0] → #9 CTRL</title></g><g class="node" id="n11" data-id="11" transform="translate(156,569)" style="opacity: 1;"><path class="box" d="M0,0H120V44H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>False</tspan><tspan class="node-id">  #11/1</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#11 False
+Projection 1 of #9
+Type not computed
+
+[0] → #9 CTRL</title></g><g class="node" id="n12" data-id="12" transform="translate(467,172)" style="opacity: 1;"><path class="box" d="M14,0H106Q120,0 120,14V30Q120,44 106,44H14Q0,44 0,30V14Q0,0 14,0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>#3</tspan><tspan class="node-id">  #12</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#12 #3
+Type not computed
+
+[0] → #1 ASSOC</title><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><text class="slot" x="60" y="-10" text-anchor="middle">0</text></g><g class="node" id="n13" data-id="13" transform="translate(314,536)" style="opacity: 1;"><path class="box" d="M10,0H110L120,10V34L110,44H10L0,34V10Z" fill="#dcecff"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>Store p.x</tspan><tspan class="node-id">  #13</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#13 Store p.x
+Type not computed
+
+[0] → — CTRL
+[1] → #8 MEM
+[2] → #5 DATA
+[3] → #12 DATA</title><circle class="port" cx="24" cy="-3" r="3" fill="white"/><circle class="port" cx="48" cy="-3" r="3" fill="#536477"/><circle class="port" cx="72" cy="-3" r="3" fill="#536477"/><circle class="port" cx="96" cy="-3" r="3" fill="#536477"/><text class="slot" x="24" y="-10" text-anchor="middle">0</text><text class="slot" x="48" y="-10" text-anchor="middle">1</text><text class="slot" x="72" y="-10" text-anchor="middle">2</text><text class="slot" x="96" y="-10" text-anchor="middle">3</text></g><g class="node" id="n14" data-id="14" transform="translate(619,172)" style="opacity: 1;"><path class="box" d="M14,0H106Q120,0 120,14V30Q120,44 106,44H14Q0,44 0,30V14Q0,0 14,0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>#4</tspan><tspan class="node-id">  #14</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#14 #4
+Type not computed
+
+[0] → #1 ASSOC</title><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><text class="slot" x="60" y="-10" text-anchor="middle">0</text></g><g class="node" id="n15" data-id="15" transform="translate(466,536)" style="opacity: 1;"><path class="box" d="M10,0H110L120,10V34L110,44H10L0,34V10Z" fill="#dcecff"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>Store p.x</tspan><tspan class="node-id">  #15</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#15 Store p.x
+Type not computed
+
+[0] → — CTRL
+[1] → #8 MEM
+[2] → #5 DATA
+[3] → #14 DATA</title><circle class="port" cx="24" cy="-3" r="3" fill="white"/><circle class="port" cx="48" cy="-3" r="3" fill="#536477"/><circle class="port" cx="72" cy="-3" r="3" fill="#536477"/><circle class="port" cx="96" cy="-3" r="3" fill="#536477"/><text class="slot" x="24" y="-10" text-anchor="middle">0</text><text class="slot" x="48" y="-10" text-anchor="middle">1</text><text class="slot" x="72" y="-10" text-anchor="middle">2</text><text class="slot" x="96" y="-10" text-anchor="middle">3</text></g><g class="node" id="n16" data-id="16" transform="translate(126,673)" style="opacity: 1;"><path class="box" d="M12,0H108L120,44H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>Region</tspan><tspan class="node-id">  #16</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#16 Region
+Type not computed
+
+[0] → — ASSOC
+[1] → #10 CTRL
+[2] → #11 CTRL</title><circle class="port" cx="30" cy="-3" r="3" fill="white"/><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><circle class="port" cx="90" cy="-3" r="3" fill="#536477"/><text class="slot" x="30" y="-10" text-anchor="middle">0</text><text class="slot" x="60" y="-10" text-anchor="middle">1</text><text class="slot" x="90" y="-10" text-anchor="middle">2</text></g><g class="node" id="n17" data-id="17" transform="translate(278,673)" style="opacity: 1;"><path class="box" d="M12,0H108L120,22L108,44H12L0,22Z" fill="#f5e4fa"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>Phi $mem</tspan><tspan class="node-id">  #17</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#17 Phi $mem
+Type not computed
+
+[0] → #16 ASSOC
+[1] → #13 MEM
+[2] → #15 MEM</title><circle class="port" cx="-3" cy="22" r="3" fill="#536477"/><circle class="port" cx="40" cy="-3" r="3" fill="#536477"/><circle class="port" cx="80" cy="-3" r="3" fill="#536477"/><path class="reg" fill="none" stroke="#b63838" d="M-3,22H-18m4,-3l-4,3l4,3"><title>[0] → #16</title></path><text class="slot" x="-10" y="15" text-anchor="middle">0</text><text class="slot" x="40" y="-10" text-anchor="middle">1</text><text class="slot" x="80" y="-10" text-anchor="middle">2</text></g><g class="node" id="n18" data-id="18" transform="translate(349,794)" style="opacity: 1;"><path class="box" d="M14,0H106Q120,0 120,14V30Q120,44 106,44H14Q0,44 0,30V14Q0,0 14,0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>Load p.x</tspan><tspan class="node-id">  #18</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#18 Load p.x
+Type not computed
+
+[0] → — CTRL
+[1] → #17 MEM
+[2] → #5 DATA
+[3] → — DATA</title><circle class="port" cx="24" cy="-3" r="3" fill="white"/><circle class="port" cx="48" cy="-3" r="3" fill="#536477"/><circle class="port" cx="72" cy="-3" r="3" fill="#536477"/><circle class="port" cx="96" cy="-3" r="3" fill="white"/><text class="slot" x="24" y="-10" text-anchor="middle">0</text><text class="slot" x="48" y="-10" text-anchor="middle">1</text><text class="slot" x="72" y="-10" text-anchor="middle">2</text><text class="slot" x="96" y="-10" text-anchor="middle">3</text></g><g class="node" id="n19" data-id="19" transform="translate(156,898)" style="opacity: 1;"><path class="box" d="M0,0H120V44H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>Return</tspan><tspan class="node-id">  #19</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#19 Return
+Type not computed
+
+[0] → #16 CTRL
+[1] → #17 MEM
+[2] → #18 DATA</title><circle class="port" cx="30" cy="-3" r="3" fill="#536477"/><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><circle class="port" cx="90" cy="-3" r="3" fill="#536477"/><text class="slot" x="30" y="-10" text-anchor="middle">0</text><text class="slot" x="60" y="-10" text-anchor="middle">1</text><text class="slot" x="90" y="-10" text-anchor="middle">2</text></g></g><g class="region-heads" style="opacity: 1;"><g class="region-head" transform="translate(24,277)"><text class="label" x="26" y="17"><tspan>If</tspan><tspan class="node-id">  #9</tspan></text><title>If · 9 nodes</title><g class="fold-btn" role="button" tabindex="0" transform="translate(4,4)" aria-label="Fold region #9"><rect width="16" height="16" rx="2"/><text x="8" y="12" text-anchor="middle">−</text><title>Fold region</title></g></g></g><g class="shortcuts" style="opacity: 1;"/><g class="peep-ghosts" pointer-events="none"/></g></svg>
diff --git a/chapter10a/docs/example2b.json b/chapter10a/docs/example2b.json
new file mode 100644
index 00000000..50fbfc74
--- /dev/null
+++ b/chapter10a/docs/example2b.json
@@ -0,0 +1,31 @@
+{
+ "ver": 1,
+ "comp": "chapter10a/docs/example2b.svg",
+ "step": 1,
+ "roots": [19],
+ "scope": 0,
+ "nodes": [
+ {"id": 1, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null},
+ {"id": 2, "label": "$ctrl", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 1, "role": "CTRL", "label": null}], "proj": {"par": 1, "idx": 0}},
+ {"id": 3, "label": "$mem", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 1, "role": "MEM", "label": null}], "proj": {"par": 1, "idx": 1}},
+ {"id": 4, "label": "arg", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "DATA", "label": null}], "proj": {"par": 1, "idx": 2}},
+ {"id": 5, "label": "new Point", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 2, "role": "CTRL", "label": null}], "proj": null},
+ {"id": 6, "label": "#0", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null},
+ {"id": 7, "label": "Store p.x", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 6, "role": "DATA", "label": null}], "proj": null},
+ {"id": 8, "label": "Store p.y", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 7, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 6, "role": "DATA", "label": null}], "proj": null},
+ {"id": 9, "label": "Loop", "type": null, "kind": "LOOP", "edges": [{"idx": 0, "def": 0, "role": "ASSOC", "label": null}, {"idx": 1, "def": 2, "role": "CTRL", "label": null}, {"idx": 2, "def": 13, "role": "CTRL", "label": null}], "proj": null},
+ {"id": 10, "label": "Phi $mem", "type": null, "kind": "PHI", "edges": [{"idx": 0, "def": 9, "role": "ASSOC", "label": null}, {"idx": 1, "def": 8, "role": "MEM", "label": null}, {"idx": 2, "def": 15, "role": "MEM", "label": null}], "proj": null},
+ {"id": 11, "label": "Phi arg", "type": null, "kind": "PHI", "edges": [{"idx": 0, "def": 9, "role": "ASSOC", "label": null}, {"idx": 1, "def": 4, "role": "DATA", "label": null}, {"idx": 2, "def": 17, "role": "DATA", "label": null}], "proj": null},
+ {"id": 12, "label": "If", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 9, "role": "CTRL", "label": null}, {"idx": 1, "def": 11, "role": "DATA", "label": null}], "proj": null},
+ {"id": 13, "label": "True", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 12, "role": "CTRL", "label": null}], "proj": {"par": 12, "idx": 0}},
+ {"id": 14, "label": "False", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 12, "role": "CTRL", "label": null}], "proj": {"par": 12, "idx": 1}},
+ {"id": 15, "label": "Store p.x", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 10, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 11, "role": "DATA", "label": null}], "proj": null},
+ {"id": 16, "label": "#1", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null},
+ {"id": 17, "label": "-", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 11, "role": "DATA", "label": null}, {"idx": 2, "def": 16, "role": "DATA", "label": null}], "proj": null},
+ {"id": 18, "label": "Load p.x", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 10, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 0, "role": "DATA", "label": null}], "proj": null},
+ {"id": 19, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 14, "role": "CTRL", "label": null}, {"idx": 1, "def": 10, "role": "MEM", "label": null}, {"idx": 2, "def": 18, "role": "DATA", "label": null}], "proj": null}
+ ],
+ "groups": [
+ {"id": 9, "par": 0, "nodes": [7, 8, 9, 10, 11, 12, 13, 14, 15, 17]}
+ ]
+}
diff --git a/chapter10a/docs/example2b.svg b/chapter10a/docs/example2b.svg
index 77ebec85..e5322a61 100644
--- a/chapter10a/docs/example2b.svg
+++ b/chapter10a/docs/example2b.svg
@@ -1,74 +1,148 @@
-<?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="510pt" height="105pt"
- viewBox="0.00 0.00 509.87 105.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 101)">
-<title>Memory</title>
-<polygon fill="#fafbfc" stroke="none" points="-4,4 -4,-101 505.87,-101 505.87,4 -4,4"/>
-<!-- P -->
-<g id="node1" class="node">
-<title>P</title>
-<polygon fill="#f5e4fa" stroke="#758191" points="223.99,-48 193.05,-66 131.17,-66 100.24,-48 131.17,-30 193.05,-30 223.99,-48"/>
-<text text-anchor="middle" x="162.11" y="-43.73" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#152330">Phi(memory)</text>
-</g>
-<!-- S -->
-<g id="node2" class="node">
-<title>S</title>
-<polygon fill="#dcecff" stroke="#758191" points="407.87,-12.54 407.87,-27.46 365.73,-38 306.13,-38 263.99,-27.46 263.99,-12.54 306.13,-2 365.73,-2 407.87,-12.54"/>
-<text text-anchor="middle" x="335.93" y="-15.72" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#152330">Loop&#45;body Store</text>
-</g>
-<!-- P&#45;&gt;S -->
-<g id="edge2" class="edge">
-<title>P&#45;&gt;S</title>
-<path fill="none" stroke="#2867b2" stroke-width="1.5" d="M202.86,-35.33C202.86,-35.33 284.35,-35.33 284.35,-35.33"/>
-<polygon fill="#2867b2" stroke="#2867b2" stroke-width="1.5" points="284.35,-37.78 291.35,-35.33 284.35,-32.88 284.35,-37.78"/>
-</g>
-<!-- Entry -->
-<g id="node3" class="node">
-<title>Entry</title>
-<polygon fill="#dcecff" stroke="#758191" points="60.24,-71.54 60.24,-86.46 42.59,-97 17.64,-97 0,-86.46 0,-71.54 17.64,-61 42.59,-61 60.24,-71.54"/>
-<text text-anchor="middle" x="30.12" y="-74.72" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#152330">Entry</text>
-</g>
-<!-- P&#45;&gt;Entry -->
-<g id="edge1" class="edge">
-<title>P&#45;&gt;Entry</title>
-<path fill="none" stroke="#2867b2" stroke-width="1.5" d="M107.6,-52.67C67.92,-52.67 21.12,-52.67 21.12,-52.67 21.12,-52.67 21.12,-53.47 21.12,-53.47"/>
-<polygon fill="#2867b2" stroke="#2867b2" stroke-width="1.5" points="18.67,-51.44 21.12,-58.44 23.57,-51.44 18.67,-51.44"/>
-</g>
-<!-- Loop -->
-<g id="node4" class="node">
-<title>Loop</title>
-<path fill="#ffe0ab" stroke="#758191" d="M45.12,-36C45.12,-36 15.12,-36 15.12,-36 9.12,-36 3.12,-30 3.12,-24 3.12,-24 3.12,-12 3.12,-12 3.12,-6 9.12,0 15.12,0 15.12,0 45.12,0 45.12,0 51.12,0 57.12,-6 57.12,-12 57.12,-12 57.12,-24 57.12,-24 57.12,-30 51.12,-36 45.12,-36"/>
-<text text-anchor="middle" x="30.12" y="-13.72" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#152330">Loop</text>
-</g>
-<!-- P&#45;&gt;Loop -->
-<g id="edge4" class="edge">
-<title>P&#45;&gt;Loop</title>
-<path fill="none" stroke="#9396a3" stroke-width="1.5" stroke-dasharray="5,2" d="M105.65,-44.33C73.32,-44.33 39.12,-44.33 39.12,-44.33 39.12,-44.33 31.88,-44.33 31.88,-44.33"/>
-<polygon fill="#9396a3" stroke="#9396a3" stroke-width="1.5" points="34.74,-44.82 37.6,-37.98 31.1,-41.54 34.74,-44.82"/>
-</g>
-<!-- S&#45;&gt;P -->
-<g id="edge3" class="edge">
-<title>S&#45;&gt;P</title>
-<path fill="none" stroke="#2867b2" stroke-width="1.5" d="M282.83,-32.67C282.83,-32.67 207.65,-32.67 207.65,-32.67"/>
-<polygon fill="#2867b2" stroke="#2867b2" stroke-width="1.5" points="207.65,-30.22 200.65,-32.67 207.65,-35.12 207.65,-30.22"/>
-</g>
-<!-- Return -->
-<g id="node5" class="node">
-<title>Return</title>
-<polygon fill="#fff1c4" stroke="#758191" points="501.87,-66 447.87,-66 447.87,-30 501.87,-30 501.87,-66"/>
-<text text-anchor="middle" x="474.87" y="-43.73" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#152330">Return</text>
-</g>
-<!-- Return&#45;&gt;P -->
-<g id="edge5" class="edge">
-<title>Return&#45;&gt;P</title>
-<path fill="none" stroke="#2867b2" stroke-width="1.5" d="M447.39,-52C447.39,-52 227.1,-52 227.1,-52"/>
-<polygon fill="#2867b2" stroke="#2867b2" stroke-width="1.5" points="227.1,-49.55 220.1,-52 227.1,-54.45 227.1,-49.55"/>
-</g>
-<!-- Return&#45;&gt;S -->
-</g>
-</svg>
+<svg aria-label="Compiler graph" width="694" height="1024" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 694 1024" style="font-family: sans-serif;"><style>
+ html, body { height:100%; margin:0; font-family:sans-serif; }
+ .viewer { height:100%; display:grid; grid-template-rows:auto minmax(0,1fr) auto; }
+ .source { padding:8px; border-bottom:1px solid #ccc; }
+ #program { width:min(600px,80vw); vertical-align:middle; }
+ #status { margin-top:4px; }
+ #graph { min-height:0; overflow:hidden; position:relative; background:#fafbfc; }
+ #elk { width:100%; height:100%; }
+ #graph svg { display:block; }
+ .tools { display:flex; flex-wrap:wrap; align-items:center; gap:14px; margin-top:8px; font-size:12px; }
+ .hint { color:#616c78; }
+ .ctrl { color:#b63838; } .mem { color:#2867b2; } .data { color:#536477; }
+ .edge { fill:none; stroke-width:1.5; }
+ .edge.CTRL { stroke-width:2; }
+ .edge.ASSOC { stroke-dasharray:4 4; }
+ .edge.PARSER { stroke-dasharray:5 4; }
+ .parser rect { fill:#f6f0ff; stroke:#7956a5; stroke-width:1.5; stroke-dasharray:5 4; }
+ .edge.selected { stroke-width:3.5; }
+ .edge.bundle { stroke-width:4; }
+ .edge.bundle.selected { stroke-width:6; }
+ .edge-hit { fill:none; stroke:transparent; stroke-width:12; vector-effect:non-scaling-stroke; pointer-events:stroke; cursor:pointer; }
+ .edge-jump { fill:transparent; pointer-events:all; cursor:pointer; }
+ .node { cursor:pointer; }
+ .box { stroke:#758191; stroke-width:1; }
+ .node.held .box { stroke-dasharray:5 3; }
+ .node.near .box { stroke:#c18221; stroke-width:2; }
+ .node.focus .box { stroke:#9c4900; stroke-width:3; }
+ .node.selected .box { stroke:#1569b9; stroke-width:3; }
+ .node.folded .box { stroke-width:4; }
+ .node.folding .box { fill:#f4ccd7; }
+ .fold-tag { font-size:9px; font-weight:700; fill:#963751; }
+ .peep-area { fill:#fff3d9; fill-opacity:.8; stroke:#c18221; stroke-width:2; pointer-events:none; }
+ .node.peep-edit .box { stroke:#9c4900; stroke-width:3; }
+ .node.error .box { stroke:#b42318; stroke-width:4; }
+ #event.error { color:#b42318; font-weight:600; white-space:pre-wrap; overflow-wrap:anywhere; }
+ .peep-ghosts { pointer-events:none; }
+ .region-box { stroke:#9da9b8; stroke-width:1.5; }
+ .fold-btn { cursor:pointer; }
+ .fold-btn rect { fill:white; stroke:#596674; }
+ .fold-btn text { font-size:14px; fill:#152330; }
+ .fold-btn:focus rect { stroke:#1569b9; stroke-width:2; }
+ .shortcut { cursor:pointer; }
+ .shortcut path { fill:none; stroke-width:1.5; }
+ .shortcut rect { fill:#fff8ec; stroke:#b63838; stroke-dasharray:3 2; }
+ .shortcut text { font-size:11px; fill:#783030; }
+ .shortcut.selected rect, .shortcut:focus rect { stroke-width:2.5; }
+ .label { font-size:12px; fill:#152330; font-weight:600; }
+ .node-id { font-size:10px; fill:#596674; font-weight:400; }
+ .type { font-size:11px; fill:#455768; }
+ .port { stroke:#536477; stroke-width:1; }
+ .row { stroke:#b6afcf; stroke-width:1; }
+ .bind { font-size:12px; fill:#152330; }
+ .slot { font-size:10px; fill:#364352; paint-order:stroke; stroke:#fafbfc; stroke-width:3; }
+ #detail { position:absolute; top:8px; right:8px; margin:0; padding:12px; max-width:320px;
+ max-height:45%; overflow:auto; white-space:pre-wrap; overflow-wrap:anywhere;
+ background:#fffffff2; border:1px solid #bac5d0; border-radius:4px; font-size:12px; }
+ .controls { display:flex; flex-wrap:wrap; justify-content:center; align-items:center; gap:6px; padding:6px; }
+ .button { color:white; background:black; font-size:20px; min-width:30px; }
+ .button:disabled { background:grey; }
+ .count { font-size:20px; min-width:35px; text-align:center; }
+ #N { width:6ch; }
+ #scrub { width:clamp(120px,35vw,600px); }
+ </style><defs><marker id="arrow-CTRL" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#b63838"/></marker><marker id="arrow-DATA" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#536477"/></marker><marker id="arrow-MEM" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#2867b2"/></marker><marker id="arrow-ASSOC" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#9396a3"/></marker><marker id="arrow-PARSER" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#7956a5"/></marker></defs><rect width="100%" height="100%" fill="rgb(250, 251, 252)"/><g><g class="regions" style="opacity: 1;"><g class="region" transform="translate(24,275)"><rect class="region-box" rx="8" width="635" height="512" fill="#f1f6fc"/></g></g><rect class="peep-area" rx="36" style="opacity: 0;"/><g class="edges"><path class="edge CTRL" id="e5i0" d="M136,118 L136,138 L370,138 L370,166" stroke="#b63838" marker-start="url(#arrow-CTRL)" style="opacity: 1;"><title>#5[0] → #2 · CTRL</title></path><path class="edge ASSOC" id="e6i0" d="M439,46 C439,70 218,145 218,169" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#6[0] → #1 · ASSOC</title></path><path class="edge MEM" id="e7i1" d="M256,118 L256,128 L147,128 L147,232 L198,232 L198,333" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#7[1] → #3 · MEM</title></path><path class="edge DATA" id="e7i2" d="M370,222 L370,242 L222,242 L222,333" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#7[2] → #5 · DATA</title></path><path class="edge DATA" id="e7i3" d="M218,222 L218,232 L246,232 L246,333" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#7[3] → #6 · DATA</title></path><path class="edge MEM" id="e8i1" d="M210,389 L210,399 L256,399 L256,437" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#8[1] → #7 · MEM</title></path><path class="edge DATA" id="e8i2" d="M370,222 L370,242 L280,242 L280,437" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#8[2] → #5 · DATA</title></path><path class="edge DATA" id="e8i3" d="M218,222 L218,232 L304,232 L304,437" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#8[3] → #6 · DATA</title></path><path class="edge CTRL" id="e9i1" d="M136,118 L136,541" stroke="#b63838" marker-start="url(#arrow-CTRL)" style="opacity: 1;"><title>#9[1] → #2 · CTRL</title></path><path class="edge CTRL" id="e9i2" d="M116,742 L116,752 L647,752 L647,516 L166,516 L166,544" stroke="#b63838" marker-start="url(#arrow-CTRL)" style="opacity: 1;"><title>#9[2] → #13 · CTRL</title></path><path class="edge MEM" id="e10i1" d="M268,493 L268,541" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#10[1] → #8 · MEM</title></path><path class="edge MEM" id="e10i2" d="M383,698 L383,708 L637,708 L637,524 L308,524 L308,544" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#10[2] → #15 · MEM</title></path><path class="edge DATA" id="e11i1" d="M376,118 L376,128 L440,128 L440,232 L420,232 L420,541" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#11[1] → #4 · DATA</title></path><path class="edge DATA" id="e11i2" d="M535,698 L535,708 L627,708 L627,532 L460,532 L460,544" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#11[2] → #17 · DATA</title></path><path class="edge CTRL" id="e12i0" d="M136,597 L136,645" stroke="#b63838" marker-start="url(#arrow-CTRL)" style="opacity: 1;"><title>#12[0] → #9 · CTRL</title></path><path class="edge DATA" id="e12i1" d="M440,597 L440,607 L216,607 L216,645" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#12[1] → #11 · DATA</title></path><path class="edge MEM" id="e15i1" d="M288,597 L288,617 L371,617 L371,645" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#15[1] → #10 · MEM</title></path><path class="edge DATA" id="e15i2" d="M370,222 L370,242 L510,242 L510,617 L395,617 L395,645" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#15[2] → #5 · DATA</title></path><path class="edge DATA" id="e15i3" d="M440,597 L440,607 L419,607 L419,645" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#15[3] → #11 · DATA</title></path><path class="edge ASSOC" id="e16i0" d="M439,46 C439,70 565,145 565,169" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#16[0] → #1 · ASSOC</title></path><path class="edge DATA" id="e17i1" d="M440,597 L440,607 L535,607 L535,645" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#17[1] → #11 · DATA</title></path><path class="edge DATA" id="e17i2" d="M565,222 L565,645" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#17[2] → #16 · DATA</title></path><path class="edge MEM" id="e18i1" d="M288,597 L288,617 L312,617 L312,802 L371,802 L371,840" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#18[1] → #10 · MEM</title></path><path class="edge DATA" id="e18i2" d="M370,222 L370,242 L669,242 L669,802 L395,802 L395,840" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#18[2] → #5 · DATA</title></path><path class="edge CTRL" id="e19i0" d="M236,745 L236,944" stroke="#b63838" marker-start="url(#arrow-CTRL)" style="opacity: 1;"><title>#19[0] → #14 · CTRL</title></path><path class="edge MEM" id="e19i1" d="M288,597 L288,617 L312,617 L312,802 L266,802 L266,944" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#19[1] → #10 · MEM</title></path><path class="edge DATA" id="e19i2" d="M383,896 L383,906 L296,906 L296,944" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#19[2] → #18 · DATA</title></path></g><g class="edge-hits"/><g class="parser" style="opacity: 1;"/><g class="nodes"><g class="node" id="n1" data-id="1" transform="translate(76,24)" style="opacity: 1;"><path class="box" d="M0,0H360V44H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="180" y="18"><tspan>Start</tspan><tspan class="node-id">  #1</tspan></text><text class="type" text-anchor="middle" y="35" x="180"/><title>#1 Start
+Type not computed
+
+</title></g><g class="node" id="n2" data-id="2" transform="translate(76,68)" style="opacity: 1;"><path class="box" d="M0,0H120V44H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>$ctrl</tspan><tspan class="node-id">  #2/0</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#2 $ctrl
+Projection 0 of #1
+Type not computed
+
+[0] → #1 CTRL</title></g><g class="node" id="n3" data-id="3" transform="translate(196,68)" style="opacity: 1;"><path class="box" d="M0,0H120V44H0Z" fill="#dcecff"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>$mem</tspan><tspan class="node-id">  #3/1</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#3 $mem
+Projection 1 of #1
+Type not computed
+
+[0] → #1 MEM</title></g><g class="node" id="n4" data-id="4" transform="translate(316,68)" style="opacity: 1;"><path class="box" d="M0,0H120V44H0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>arg</tspan><tspan class="node-id">  #4/2</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#4 arg
+Projection 2 of #1
+Type not computed
+
+[0] → #1 DATA</title></g><g class="node" id="n5" data-id="5" transform="translate(310,172)" style="opacity: 1;"><path class="box" d="M14,0H106Q120,0 120,14V30Q120,44 106,44H14Q0,44 0,30V14Q0,0 14,0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>new Point</tspan><tspan class="node-id">  #5</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#5 new Point
+Type not computed
+
+[0] → #2 CTRL</title><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><text class="slot" x="60" y="-10" text-anchor="middle">0</text></g><g class="node" id="n6" data-id="6" transform="translate(158,172)" style="opacity: 1;"><path class="box" d="M14,0H106Q120,0 120,14V30Q120,44 106,44H14Q0,44 0,30V14Q0,0 14,0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>#0</tspan><tspan class="node-id">  #6</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#6 #0
+Type not computed
+
+[0] → #1 ASSOC</title><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><text class="slot" x="60" y="-10" text-anchor="middle">0</text></g><g class="node" id="n7" data-id="7" transform="translate(150,339)" style="opacity: 1;"><path class="box" d="M10,0H110L120,10V34L110,44H10L0,34V10Z" fill="#dcecff"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>Store p.x</tspan><tspan class="node-id">  #7</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#7 Store p.x
+Type not computed
+
+[0] → — CTRL
+[1] → #3 MEM
+[2] → #5 DATA
+[3] → #6 DATA</title><circle class="port" cx="24" cy="-3" r="3" fill="white"/><circle class="port" cx="48" cy="-3" r="3" fill="#536477"/><circle class="port" cx="72" cy="-3" r="3" fill="#536477"/><circle class="port" cx="96" cy="-3" r="3" fill="#536477"/><text class="slot" x="24" y="-10" text-anchor="middle">0</text><text class="slot" x="48" y="-10" text-anchor="middle">1</text><text class="slot" x="72" y="-10" text-anchor="middle">2</text><text class="slot" x="96" y="-10" text-anchor="middle">3</text></g><g class="node" id="n8" data-id="8" transform="translate(208,443)" style="opacity: 1;"><path class="box" d="M10,0H110L120,10V34L110,44H10L0,34V10Z" fill="#dcecff"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>Store p.y</tspan><tspan class="node-id">  #8</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#8 Store p.y
+Type not computed
+
+[0] → — CTRL
+[1] → #7 MEM
+[2] → #5 DATA
+[3] → #6 DATA</title><circle class="port" cx="24" cy="-3" r="3" fill="white"/><circle class="port" cx="48" cy="-3" r="3" fill="#536477"/><circle class="port" cx="72" cy="-3" r="3" fill="#536477"/><circle class="port" cx="96" cy="-3" r="3" fill="#536477"/><text class="slot" x="24" y="-10" text-anchor="middle">0</text><text class="slot" x="48" y="-10" text-anchor="middle">1</text><text class="slot" x="72" y="-10" text-anchor="middle">2</text><text class="slot" x="96" y="-10" text-anchor="middle">3</text></g><g class="node" id="n9" data-id="9" transform="translate(76,547)" style="opacity: 1;"><path class="box" d="M20,0H100Q120,0 120,20V24Q120,44 100,44H20Q0,44 0,24V20Q0,0 20,0Z" fill="#ffe0ab"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>Loop</tspan><tspan class="node-id">  #9</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#9 Loop
+Type not computed
+
+[0] → — ASSOC
+[1] → #2 CTRL
+[2] → #13 CTRL</title><circle class="port" cx="30" cy="-3" r="3" fill="white"/><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><circle class="port" cx="90" cy="-3" r="3" fill="#536477"/><text class="slot" x="30" y="-10" text-anchor="middle">0</text><text class="slot" x="60" y="-10" text-anchor="middle">1</text><text class="slot" x="90" y="-10" text-anchor="middle">2</text></g><g class="node" id="n10" data-id="10" transform="translate(228,547)" style="opacity: 1;"><path class="box" d="M12,0H108L120,22L108,44H12L0,22Z" fill="#f5e4fa"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>Phi $mem</tspan><tspan class="node-id">  #10</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#10 Phi $mem
+Type not computed
+
+[0] → #9 ASSOC
+[1] → #8 MEM
+[2] → #15 MEM</title><circle class="port" cx="-3" cy="22" r="3" fill="#536477"/><circle class="port" cx="40" cy="-3" r="3" fill="#536477"/><circle class="port" cx="80" cy="-3" r="3" fill="#536477"/><path class="reg" fill="none" stroke="#b63838" d="M-3,22H-18m4,-3l-4,3l4,3"><title>[0] → #9</title></path><text class="slot" x="-10" y="15" text-anchor="middle">0</text><text class="slot" x="40" y="-10" text-anchor="middle">1</text><text class="slot" x="80" y="-10" text-anchor="middle">2</text></g><g class="node" id="n11" data-id="11" transform="translate(380,547)" style="opacity: 1;"><path class="box" d="M12,0H108L120,22L108,44H12L0,22Z" fill="#f5e4fa"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>Phi arg</tspan><tspan class="node-id">  #11</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#11 Phi arg
+Type not computed
+
+[0] → #9 ASSOC
+[1] → #4 DATA
+[2] → #17 DATA</title><circle class="port" cx="-3" cy="22" r="3" fill="#536477"/><circle class="port" cx="40" cy="-3" r="3" fill="#536477"/><circle class="port" cx="80" cy="-3" r="3" fill="#536477"/><path class="reg" fill="none" stroke="#b63838" d="M-3,22H-18m4,-3l-4,3l4,3"><title>[0] → #9</title></path><text class="slot" x="-10" y="15" text-anchor="middle">0</text><text class="slot" x="40" y="-10" text-anchor="middle">1</text><text class="slot" x="80" y="-10" text-anchor="middle">2</text></g><g class="node" id="n12" data-id="12" transform="translate(56,651)" style="opacity: 1;"><path class="box" d="M0,0H240V44H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="120" y="18"><tspan>If</tspan><tspan class="node-id">  #12</tspan></text><text class="type" text-anchor="middle" y="35" x="120"/><title>#12 If
+Type not computed
+
+[0] → #9 CTRL
+[1] → #11 DATA</title><circle class="port" cx="80" cy="-3" r="3" fill="#536477"/><circle class="port" cx="160" cy="-3" r="3" fill="#536477"/><text class="slot" x="80" y="-10" text-anchor="middle">0</text><text class="slot" x="160" y="-10" text-anchor="middle">1</text></g><g class="node" id="n13" data-id="13" transform="translate(56,695)" style="opacity: 1;"><path class="box" d="M0,0H120V44H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>True</tspan><tspan class="node-id">  #13/0</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#13 True
+Projection 0 of #12
+Type not computed
+
+[0] → #12 CTRL</title></g><g class="node" id="n14" data-id="14" transform="translate(176,695)" style="opacity: 1;"><path class="box" d="M0,0H120V44H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>False</tspan><tspan class="node-id">  #14/1</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#14 False
+Projection 1 of #12
+Type not computed
+
+[0] → #12 CTRL</title></g><g class="node" id="n15" data-id="15" transform="translate(323,651)" style="opacity: 1;"><path class="box" d="M10,0H110L120,10V34L110,44H10L0,34V10Z" fill="#dcecff"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>Store p.x</tspan><tspan class="node-id">  #15</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#15 Store p.x
+Type not computed
+
+[0] → — CTRL
+[1] → #10 MEM
+[2] → #5 DATA
+[3] → #11 DATA</title><circle class="port" cx="24" cy="-3" r="3" fill="white"/><circle class="port" cx="48" cy="-3" r="3" fill="#536477"/><circle class="port" cx="72" cy="-3" r="3" fill="#536477"/><circle class="port" cx="96" cy="-3" r="3" fill="#536477"/><text class="slot" x="24" y="-10" text-anchor="middle">0</text><text class="slot" x="48" y="-10" text-anchor="middle">1</text><text class="slot" x="72" y="-10" text-anchor="middle">2</text><text class="slot" x="96" y="-10" text-anchor="middle">3</text></g><g class="node" id="n16" data-id="16" transform="translate(505,172)" style="opacity: 1;"><path class="box" d="M14,0H106Q120,0 120,14V30Q120,44 106,44H14Q0,44 0,30V14Q0,0 14,0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>#1</tspan><tspan class="node-id">  #16</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#16 #1
+Type not computed
+
+[0] → #1 ASSOC</title><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><text class="slot" x="60" y="-10" text-anchor="middle">0</text></g><g class="node" id="n17" data-id="17" transform="translate(475,651)" style="opacity: 1;"><path class="box" d="M14,0H106Q120,0 120,14V30Q120,44 106,44H14Q0,44 0,30V14Q0,0 14,0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>-</tspan><tspan class="node-id">  #17</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#17 -
+Type not computed
+
+[0] → — CTRL
+[1] → #11 DATA
+[2] → #16 DATA</title><circle class="port" cx="30" cy="-3" r="3" fill="white"/><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><circle class="port" cx="90" cy="-3" r="3" fill="#536477"/><text class="slot" x="30" y="-10" text-anchor="middle">0</text><text class="slot" x="60" y="-10" text-anchor="middle">1</text><text class="slot" x="90" y="-10" text-anchor="middle">2</text></g><g class="node" id="n18" data-id="18" transform="translate(323,846)" style="opacity: 1;"><path class="box" d="M14,0H106Q120,0 120,14V30Q120,44 106,44H14Q0,44 0,30V14Q0,0 14,0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>Load p.x</tspan><tspan class="node-id">  #18</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#18 Load p.x
+Type not computed
+
+[0] → — CTRL
+[1] → #10 MEM
+[2] → #5 DATA
+[3] → — DATA</title><circle class="port" cx="24" cy="-3" r="3" fill="white"/><circle class="port" cx="48" cy="-3" r="3" fill="#536477"/><circle class="port" cx="72" cy="-3" r="3" fill="#536477"/><circle class="port" cx="96" cy="-3" r="3" fill="white"/><text class="slot" x="24" y="-10" text-anchor="middle">0</text><text class="slot" x="48" y="-10" text-anchor="middle">1</text><text class="slot" x="72" y="-10" text-anchor="middle">2</text><text class="slot" x="96" y="-10" text-anchor="middle">3</text></g><g class="node" id="n19" data-id="19" transform="translate(206,950)" style="opacity: 1;"><path class="box" d="M0,0H120V44H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>Return</tspan><tspan class="node-id">  #19</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#19 Return
+Type not computed
+
+[0] → #14 CTRL
+[1] → #10 MEM
+[2] → #18 DATA</title><circle class="port" cx="30" cy="-3" r="3" fill="#536477"/><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><circle class="port" cx="90" cy="-3" r="3" fill="#536477"/><text class="slot" x="30" y="-10" text-anchor="middle">0</text><text class="slot" x="60" y="-10" text-anchor="middle">1</text><text class="slot" x="90" y="-10" text-anchor="middle">2</text></g></g><g class="region-heads" style="opacity: 1;"><g class="region-head" transform="translate(24,275)"><text class="label" x="26" y="17"><tspan>Loop</tspan><tspan class="node-id">  #9</tspan></text><title>Loop · 10 nodes</title><g class="fold-btn" role="button" tabindex="0" transform="translate(4,4)" aria-label="Fold region #9"><rect width="16" height="16" rx="2"/><text x="8" y="12" text-anchor="middle">−</text><title>Fold region</title></g></g></g><g class="shortcuts" style="opacity: 1;"/><g class="peep-ghosts" pointer-events="none"/></g></svg>
diff --git a/chapter10a/docs/example2c.json b/chapter10a/docs/example2c.json
new file mode 100644
index 00000000..4e7e096b
--- /dev/null
+++ b/chapter10a/docs/example2c.json
@@ -0,0 +1,22 @@
+{
+ "ver": 1,
+ "comp": "chapter10a/docs/example2c.svg",
+ "step": 1,
+ "roots": [11],
+ "scope": 0,
+ "nodes": [
+ {"id": 1, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null},
+ {"id": 2, "label": "$ctrl", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 1, "role": "CTRL", "label": null}], "proj": {"par": 1, "idx": 0}},
+ {"id": 3, "label": "$mem", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 1, "role": "MEM", "label": null}], "proj": {"par": 1, "idx": 1}},
+ {"id": 4, "label": "arg", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "DATA", "label": null}], "proj": {"par": 1, "idx": 2}},
+ {"id": 5, "label": "new Point", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 2, "role": "CTRL", "label": null}], "proj": null},
+ {"id": 6, "label": "#0", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null},
+ {"id": 7, "label": "Store p.x", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 6, "role": "DATA", "label": null}], "proj": null},
+ {"id": 8, "label": "Store p.y", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 7, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 6, "role": "DATA", "label": null}], "proj": null},
+ {"id": 9, "label": "Load p.x", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 8, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 0, "role": "DATA", "label": null}], "proj": null},
+ {"id": 10, "label": "Store p.x", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 8, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 4, "role": "DATA", "label": null}], "proj": null},
+ {"id": 11, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 2, "role": "CTRL", "label": null}, {"idx": 1, "def": 10, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}], "proj": null}
+ ],
+ "groups": [
+ ]
+}
diff --git a/chapter10a/docs/example2c.svg b/chapter10a/docs/example2c.svg
index 2aa8fa2d..eb8fad03 100644
--- a/chapter10a/docs/example2c.svg
+++ b/chapter10a/docs/example2c.svg
@@ -1,50 +1,113 @@
-<?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="357pt" height="72pt"
- viewBox="0.00 0.00 356.97 72.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 68)">
-<title>Memory</title>
-<polygon fill="#fafbfc" stroke="none" points="-4,4 -4,-68 352.97,-68 352.97,4 -4,4"/>
-<!-- M -->
-<g id="node1" class="node">
-<title>M</title>
-<polygon fill="#dcecff" stroke="#758191" points="120.13,-38.54 120.13,-53.46 84.95,-64 35.19,-64 0,-53.46 0,-38.54 35.19,-28 84.95,-28 120.13,-38.54"/>
-<text text-anchor="middle" x="60.07" y="-41.73" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#152330">Prior memory</text>
-</g>
-<!-- L -->
-<g id="node2" class="node">
-<title>L</title>
-<path fill="#edf3f7" stroke="#758191" d="M209.88,-36C209.88,-36 172.13,-36 172.13,-36 166.13,-36 160.13,-30 160.13,-24 160.13,-24 160.13,-12 160.13,-12 160.13,-6 166.13,0 172.13,0 172.13,0 209.88,0 209.88,0 215.88,0 221.88,-6 221.88,-12 221.88,-12 221.88,-24 221.88,-24 221.88,-30 215.88,-36 209.88,-36"/>
-<text text-anchor="middle" x="191.01" y="-13.72" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#152330">Load p.x</text>
-</g>
-<!-- L&#45;&gt;M -->
-<g id="edge1" class="edge">
-<title>L&#45;&gt;M</title>
-<path fill="none" stroke="#2867b2" stroke-width="1.5" d="M159.64,-32C159.64,-32 108.9,-32 108.9,-32"/>
-<polygon fill="#2867b2" stroke="#2867b2" stroke-width="1.5" points="108.9,-29.55 101.9,-32 108.9,-34.45 108.9,-29.55"/>
-</g>
-<!-- S -->
-<g id="node3" class="node">
-<title>S</title>
-<polygon fill="#dcecff" stroke="#758191" points="348.97,-38.54 348.97,-53.46 323.46,-64 287.39,-64 261.88,-53.46 261.88,-38.54 287.39,-28 323.46,-28 348.97,-38.54"/>
-<text text-anchor="middle" x="305.42" y="-41.73" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#152330">Store p.x</text>
-</g>
-<!-- S&#45;&gt;M -->
-<g id="edge2" class="edge">
-<title>S&#45;&gt;M</title>
-<path fill="none" stroke="#2867b2" stroke-width="1.5" d="M261.56,-50C261.56,-50 129.87,-50 129.87,-50"/>
-<polygon fill="#2867b2" stroke="#2867b2" stroke-width="1.5" points="129.87,-47.55 122.87,-50 129.87,-52.45 129.87,-47.55"/>
-</g>
-<!-- S&#45;&gt;L -->
-<g id="edge3" class="edge">
-<title>S&#45;&gt;L</title>
-<path fill="none" stroke="#536477" stroke-width="1.5" stroke-dasharray="5,2" d="M276.55,-32C276.55,-32 231.55,-32 231.55,-32"/>
-<polygon fill="#536477" stroke="#536477" stroke-width="1.5" points="231.55,-29.55 224.55,-32 231.55,-34.45 231.55,-29.55"/>
-<text text-anchor="middle" x="219.55" y="-33.95" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#364352">execute load first</text>
-</g>
-</g>
-</svg>
+<svg aria-label="Compiler graph" width="414" height="662" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 414 662" style="font-family: sans-serif;"><style>
+ html, body { height:100%; margin:0; font-family:sans-serif; }
+ .viewer { height:100%; display:grid; grid-template-rows:auto minmax(0,1fr) auto; }
+ .source { padding:8px; border-bottom:1px solid #ccc; }
+ #program { width:min(600px,80vw); vertical-align:middle; }
+ #status { margin-top:4px; }
+ #graph { min-height:0; overflow:hidden; position:relative; background:#fafbfc; }
+ #elk { width:100%; height:100%; }
+ #graph svg { display:block; }
+ .tools { display:flex; flex-wrap:wrap; align-items:center; gap:14px; margin-top:8px; font-size:12px; }
+ .hint { color:#616c78; }
+ .ctrl { color:#b63838; } .mem { color:#2867b2; } .data { color:#536477; }
+ .edge { fill:none; stroke-width:1.5; }
+ .edge.CTRL { stroke-width:2; }
+ .edge.ASSOC { stroke-dasharray:4 4; }
+ .edge.PARSER { stroke-dasharray:5 4; }
+ .parser rect { fill:#f6f0ff; stroke:#7956a5; stroke-width:1.5; stroke-dasharray:5 4; }
+ .edge.selected { stroke-width:3.5; }
+ .edge.bundle { stroke-width:4; }
+ .edge.bundle.selected { stroke-width:6; }
+ .edge-hit { fill:none; stroke:transparent; stroke-width:12; vector-effect:non-scaling-stroke; pointer-events:stroke; cursor:pointer; }
+ .edge-jump { fill:transparent; pointer-events:all; cursor:pointer; }
+ .node { cursor:pointer; }
+ .box { stroke:#758191; stroke-width:1; }
+ .node.held .box { stroke-dasharray:5 3; }
+ .node.near .box { stroke:#c18221; stroke-width:2; }
+ .node.focus .box { stroke:#9c4900; stroke-width:3; }
+ .node.selected .box { stroke:#1569b9; stroke-width:3; }
+ .node.folded .box { stroke-width:4; }
+ .node.folding .box { fill:#f4ccd7; }
+ .fold-tag { font-size:9px; font-weight:700; fill:#963751; }
+ .peep-area { fill:#fff3d9; fill-opacity:.8; stroke:#c18221; stroke-width:2; pointer-events:none; }
+ .node.peep-edit .box { stroke:#9c4900; stroke-width:3; }
+ .node.error .box { stroke:#b42318; stroke-width:4; }
+ #event.error { color:#b42318; font-weight:600; white-space:pre-wrap; overflow-wrap:anywhere; }
+ .peep-ghosts { pointer-events:none; }
+ .region-box { stroke:#9da9b8; stroke-width:1.5; }
+ .fold-btn { cursor:pointer; }
+ .fold-btn rect { fill:white; stroke:#596674; }
+ .fold-btn text { font-size:14px; fill:#152330; }
+ .fold-btn:focus rect { stroke:#1569b9; stroke-width:2; }
+ .shortcut { cursor:pointer; }
+ .shortcut path { fill:none; stroke-width:1.5; }
+ .shortcut rect { fill:#fff8ec; stroke:#b63838; stroke-dasharray:3 2; }
+ .shortcut text { font-size:11px; fill:#783030; }
+ .shortcut.selected rect, .shortcut:focus rect { stroke-width:2.5; }
+ .label { font-size:12px; fill:#152330; font-weight:600; }
+ .node-id { font-size:10px; fill:#596674; font-weight:400; }
+ .type { font-size:11px; fill:#455768; }
+ .port { stroke:#536477; stroke-width:1; }
+ .row { stroke:#b6afcf; stroke-width:1; }
+ .bind { font-size:12px; fill:#152330; }
+ .slot { font-size:10px; fill:#364352; paint-order:stroke; stroke:#fafbfc; stroke-width:3; }
+ #detail { position:absolute; top:8px; right:8px; margin:0; padding:12px; max-width:320px;
+ max-height:45%; overflow:auto; white-space:pre-wrap; overflow-wrap:anywhere;
+ background:#fffffff2; border:1px solid #bac5d0; border-radius:4px; font-size:12px; }
+ .controls { display:flex; flex-wrap:wrap; justify-content:center; align-items:center; gap:6px; padding:6px; }
+ .button { color:white; background:black; font-size:20px; min-width:30px; }
+ .button:disabled { background:grey; }
+ .count { font-size:20px; min-width:35px; text-align:center; }
+ #N { width:6ch; }
+ #scrub { width:clamp(120px,35vw,600px); }
+ </style><defs><marker id="arrow-CTRL" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#b63838"/></marker><marker id="arrow-DATA" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#536477"/></marker><marker id="arrow-MEM" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#2867b2"/></marker><marker id="arrow-ASSOC" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#9396a3"/></marker><marker id="arrow-PARSER" viewBox="0 -4 8 8" refX="8" refY="0" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,-4L8,0L0,4Z" fill="#7956a5"/></marker></defs><rect width="100%" height="100%" fill="rgb(250, 251, 252)"/><g><g class="regions" style="opacity: 1;"/><rect class="peep-area" rx="36" style="opacity: 0;"/><g class="edges"><path class="edge CTRL" id="e5i0" d="M84,118 L84,128 L155,128 L155,166" stroke="#b63838" marker-start="url(#arrow-CTRL)" style="opacity: 1;"><title>#5[0] → #2 · CTRL</title></path><path class="edge ASSOC" id="e6i0" d="M387,46 C387,70 296,145 296,169" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#6[0] → #1 · ASSOC</title></path><path class="edge MEM" id="e7i1" d="M204,118 L204,128 L225,128 L225,232 L214,232 L214,270" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#7[1] → #3 · MEM</title></path><path class="edge DATA" id="e7i2" d="M155,222 L155,242 L238,242 L238,270" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#7[2] → #5 · DATA</title></path><path class="edge DATA" id="e7i3" d="M296,222 L296,232 L262,232 L262,270" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#7[3] → #6 · DATA</title></path><path class="edge MEM" id="e8i1" d="M226,326 L226,374" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#8[1] → #7 · MEM</title></path><path class="edge DATA" id="e8i2" d="M155,222 L155,336 L250,336 L250,374" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#8[2] → #5 · DATA</title></path><path class="edge DATA" id="e8i3" d="M296,222 L296,336 L274,336 L274,374" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#8[3] → #6 · DATA</title></path><path class="edge MEM" id="e9i1" d="M238,430 L238,440 L143,440 L143,478" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#9[1] → #8 · MEM</title></path><path class="edge DATA" id="e9i2" d="M155,222 L155,450 L167,450 L167,478" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#9[2] → #5 · DATA</title></path><path class="edge MEM" id="e10i1" d="M238,430 L238,440 L295,440 L295,478" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#10[1] → #8 · MEM</title></path><path class="edge DATA" id="e10i2" d="M155,222 L155,450 L319,450 L319,478" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#10[2] → #5 · DATA</title></path><path class="edge DATA" id="e10i3" d="M324,118 L324,128 L366,128 L366,440 L343,440 L343,478" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#10[3] → #4 · DATA</title></path><path class="edge CTRL" id="e11i0" d="M84,118 L84,582" stroke="#b63838" marker-start="url(#arrow-CTRL)" style="opacity: 1;"><title>#11[0] → #2 · CTRL</title></path><path class="edge MEM" id="e11i1" d="M307,534 L307,544 L114,544 L114,582" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#11[1] → #10 · MEM</title></path><path class="edge DATA" id="e11i2" d="M155,534 L155,554 L144,554 L144,582" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#11[2] → #9 · DATA</title></path></g><g class="edge-hits"/><g class="parser" style="opacity: 1;"/><g class="nodes"><g class="node" id="n1" data-id="1" transform="translate(24,24)" style="opacity: 1;"><path class="box" d="M0,0H360V44H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="180" y="18"><tspan>Start</tspan><tspan class="node-id">  #1</tspan></text><text class="type" text-anchor="middle" y="35" x="180"/><title>#1 Start
+Type not computed
+
+</title></g><g class="node" id="n2" data-id="2" transform="translate(24,68)" style="opacity: 1;"><path class="box" d="M0,0H120V44H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>$ctrl</tspan><tspan class="node-id">  #2/0</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#2 $ctrl
+Projection 0 of #1
+Type not computed
+
+[0] → #1 CTRL</title></g><g class="node" id="n3" data-id="3" transform="translate(144,68)" style="opacity: 1;"><path class="box" d="M0,0H120V44H0Z" fill="#dcecff"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>$mem</tspan><tspan class="node-id">  #3/1</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#3 $mem
+Projection 1 of #1
+Type not computed
+
+[0] → #1 MEM</title></g><g class="node" id="n4" data-id="4" transform="translate(264,68)" style="opacity: 1;"><path class="box" d="M0,0H120V44H0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>arg</tspan><tspan class="node-id">  #4/2</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#4 arg
+Projection 2 of #1
+Type not computed
+
+[0] → #1 DATA</title></g><g class="node" id="n5" data-id="5" transform="translate(95,172)" style="opacity: 1;"><path class="box" d="M14,0H106Q120,0 120,14V30Q120,44 106,44H14Q0,44 0,30V14Q0,0 14,0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>new Point</tspan><tspan class="node-id">  #5</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#5 new Point
+Type not computed
+
+[0] → #2 CTRL</title><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><text class="slot" x="60" y="-10" text-anchor="middle">0</text></g><g class="node" id="n6" data-id="6" transform="translate(236,172)" style="opacity: 1;"><path class="box" d="M14,0H106Q120,0 120,14V30Q120,44 106,44H14Q0,44 0,30V14Q0,0 14,0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>#0</tspan><tspan class="node-id">  #6</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#6 #0
+Type not computed
+
+[0] → #1 ASSOC</title><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><text class="slot" x="60" y="-10" text-anchor="middle">0</text></g><g class="node" id="n7" data-id="7" transform="translate(166,276)" style="opacity: 1;"><path class="box" d="M10,0H110L120,10V34L110,44H10L0,34V10Z" fill="#dcecff"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>Store p.x</tspan><tspan class="node-id">  #7</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#7 Store p.x
+Type not computed
+
+[0] → — CTRL
+[1] → #3 MEM
+[2] → #5 DATA
+[3] → #6 DATA</title><circle class="port" cx="24" cy="-3" r="3" fill="white"/><circle class="port" cx="48" cy="-3" r="3" fill="#536477"/><circle class="port" cx="72" cy="-3" r="3" fill="#536477"/><circle class="port" cx="96" cy="-3" r="3" fill="#536477"/><text class="slot" x="24" y="-10" text-anchor="middle">0</text><text class="slot" x="48" y="-10" text-anchor="middle">1</text><text class="slot" x="72" y="-10" text-anchor="middle">2</text><text class="slot" x="96" y="-10" text-anchor="middle">3</text></g><g class="node" id="n8" data-id="8" transform="translate(178,380)" style="opacity: 1;"><path class="box" d="M10,0H110L120,10V34L110,44H10L0,34V10Z" fill="#dcecff"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>Store p.y</tspan><tspan class="node-id">  #8</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#8 Store p.y
+Type not computed
+
+[0] → — CTRL
+[1] → #7 MEM
+[2] → #5 DATA
+[3] → #6 DATA</title><circle class="port" cx="24" cy="-3" r="3" fill="white"/><circle class="port" cx="48" cy="-3" r="3" fill="#536477"/><circle class="port" cx="72" cy="-3" r="3" fill="#536477"/><circle class="port" cx="96" cy="-3" r="3" fill="#536477"/><text class="slot" x="24" y="-10" text-anchor="middle">0</text><text class="slot" x="48" y="-10" text-anchor="middle">1</text><text class="slot" x="72" y="-10" text-anchor="middle">2</text><text class="slot" x="96" y="-10" text-anchor="middle">3</text></g><g class="node" id="n9" data-id="9" transform="translate(95,484)" style="opacity: 1;"><path class="box" d="M14,0H106Q120,0 120,14V30Q120,44 106,44H14Q0,44 0,30V14Q0,0 14,0Z" fill="#edf3f7"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>Load p.x</tspan><tspan class="node-id">  #9</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#9 Load p.x
+Type not computed
+
+[0] → — CTRL
+[1] → #8 MEM
+[2] → #5 DATA
+[3] → — DATA</title><circle class="port" cx="24" cy="-3" r="3" fill="white"/><circle class="port" cx="48" cy="-3" r="3" fill="#536477"/><circle class="port" cx="72" cy="-3" r="3" fill="#536477"/><circle class="port" cx="96" cy="-3" r="3" fill="white"/><text class="slot" x="24" y="-10" text-anchor="middle">0</text><text class="slot" x="48" y="-10" text-anchor="middle">1</text><text class="slot" x="72" y="-10" text-anchor="middle">2</text><text class="slot" x="96" y="-10" text-anchor="middle">3</text></g><g class="node" id="n10" data-id="10" transform="translate(247,484)" style="opacity: 1;"><path class="box" d="M10,0H110L120,10V34L110,44H10L0,34V10Z" fill="#dcecff"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>Store p.x</tspan><tspan class="node-id">  #10</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#10 Store p.x
+Type not computed
+
+[0] → — CTRL
+[1] → #8 MEM
+[2] → #5 DATA
+[3] → #4 DATA</title><circle class="port" cx="24" cy="-3" r="3" fill="white"/><circle class="port" cx="48" cy="-3" r="3" fill="#536477"/><circle class="port" cx="72" cy="-3" r="3" fill="#536477"/><circle class="port" cx="96" cy="-3" r="3" fill="#536477"/><text class="slot" x="24" y="-10" text-anchor="middle">0</text><text class="slot" x="48" y="-10" text-anchor="middle">1</text><text class="slot" x="72" y="-10" text-anchor="middle">2</text><text class="slot" x="96" y="-10" text-anchor="middle">3</text></g><g class="node" id="n11" data-id="11" transform="translate(54,588)" style="opacity: 1;"><path class="box" d="M0,0H120V44H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>Return</tspan><tspan class="node-id">  #11</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#11 Return
+Type not computed
+
+[0] → #2 CTRL
+[1] → #10 MEM
+[2] → #9 DATA</title><circle class="port" cx="30" cy="-3" r="3" fill="#536477"/><circle class="port" cx="60" cy="-3" r="3" fill="#536477"/><circle class="port" cx="90" cy="-3" r="3" fill="#536477"/><text class="slot" x="30" y="-10" text-anchor="middle">0</text><text class="slot" x="60" y="-10" text-anchor="middle">1</text><text class="slot" x="90" y="-10" text-anchor="middle">2</text></g></g><g class="region-heads" style="opacity: 1;"/><g class="shortcuts" style="opacity: 1;"/><g class="peep-ghosts" pointer-events="none"/></g></svg>
diff --git a/chapter10a/docs/lattice.gv b/chapter10a/docs/lattice.gv
index eb4d7fb1..b56a3073 100644
--- a/chapter10a/docs/lattice.gv
+++ b/chapter10a/docs/lattice.gv
@@ -91,9 +91,28 @@ digraph Lattice {
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];
+ // Each black element stands for a recursively nested type lattice.
+ TUPLE1 [shape=plain, label=<
+ <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4">
+ <TR><TD BGCOLOR="#ffc0cb">tuple</TD></TR>
+ <TR><TD BGCOLOR="black"><FONT COLOR="white">type</FONT></TD></TR>
+ </TABLE>
+ >];
+ TUPLE2 [shape=plain, label=<
+ <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4">
+ <TR><TD BGCOLOR="#ffc0cb">tuple</TD></TR>
+ <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=plain, label=<
+ <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4">
+ <TR><TD BGCOLOR="#ffc0cb">tuple</TD></TR>
+ <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>
+ >];
{rank=same; TUPLE1; TUPLE2; TUPLEN;}
TUPLE1 -> TUPLE2 -> TUPLEN [style=invis];
TUPLE_TOP -> {TUPLE1 TUPLE2 TUPLEN};
diff --git a/chapter10a/docs/lattice.svg b/chapter10a/docs/lattice.svg
index c8bed535..006ab762 100644
--- a/chapter10a/docs/lattice.svg
+++ b/chapter10a/docs/lattice.svg
@@ -4,122 +4,122 @@
<!-- Generated by graphviz version 12.2.0 (20241103.1931)
-->
<!-- Title: Lattice Pages: 1 -->
-<svg width="1647pt" height="533pt"
- viewBox="0.00 0.00 1647.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)">
+<svg width="1551pt" height="603pt"
+ viewBox="0.00 0.00 1551.00 603.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 599.45)">
<title>Lattice</title>
-<polygon fill="white" stroke="none" points="-4,4 -4,-529.45 1643,-529.45 1643,4 -4,4"/>
+<polygon fill="white" stroke="none" points="-4,4 -4,-599.45 1547,-599.45 1547,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>
+<polygon fill="none" stroke="#cccccc" points="8,-85.99 8,-524.71 98,-524.71 98,-85.99 8,-85.99"/>
+<text text-anchor="middle" x="53" y="-507.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>
+<polygon fill="none" stroke="#cccccc" points="106,-85.99 106,-524.71 404,-524.71 404,-85.99 106,-85.99"/>
+<text text-anchor="middle" x="255" y="-507.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>
+<polygon fill="none" stroke="#cccccc" points="412,-75 412,-535.7 893,-535.7 893,-75 412,-75"/>
+<text text-anchor="middle" x="652.5" y="-518.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 1233,-481.45 1233,-75 901,-75"/>
-<text text-anchor="middle" x="1067" y="-464.15" font-family="Arial" font-size="14.00">Struct</text>
-<text text-anchor="middle" x="1067" y="-448.4" font-family="Arial" font-size="14.00">named collection of named scalar types</text>
+<polygon fill="none" stroke="#cccccc" points="901,-75 901,-551.45 1233,-551.45 1233,-75 901,-75"/>
+<text text-anchor="middle" x="1067" y="-534.15" font-family="Arial" font-size="14.00">Struct</text>
+<text text-anchor="middle" x="1067" y="-518.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="1241,-85.99 1241,-454.71 1347,-454.71 1347,-85.99 1241,-85.99"/>
-<text text-anchor="middle" x="1294" y="-437.41" font-family="Arial" font-size="14.00">Memory</text>
+<polygon fill="none" stroke="#cccccc" points="1241,-85.99 1241,-524.71 1347,-524.71 1347,-85.99 1241,-85.99"/>
+<text text-anchor="middle" x="1294" y="-507.41" 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,-470.46 1631,-470.46 1631,-85.99 1355,-85.99"/>
-<text text-anchor="middle" x="1493" y="-453.16" font-family="Arial" font-size="14.00">Tuple</text>
-<text text-anchor="middle" x="1493" y="-437.41" font-family="Arial" font-size="14.00">collection of types</text>
+<polygon fill="none" stroke="#cccccc" points="1355,-85.99 1355,-540.46 1535,-540.46 1535,-85.99 1355,-85.99"/>
+<text text-anchor="middle" x="1445" y="-523.16" font-family="Arial" font-size="14.00">Tuple</text>
+<text text-anchor="middle" x="1445" y="-507.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="875" cy="-507.45" rx="27" ry="18"/>
-<text text-anchor="middle" x="875" y="-503.35" font-family="DejaVu Sans" font-size="13.00" fill="white">⊤</text>
+<ellipse fill="black" stroke="black" cx="875" cy="-577.45" rx="27" ry="18"/>
+<text text-anchor="middle" x="875" y="-573.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>
+<ellipse fill="#ffff00" stroke="black" cx="53" cy="-474.96" rx="36.51" ry="18"/>
+<text text-anchor="middle" x="53" y="-470.86" font-family="DejaVu Sans" font-size="13.00">⊤:ctrl</text>
</g>
<!-- TOP&#45;&gt;XCTRL -->
<g id="edge53" class="edge">
<title>TOP&#45;&gt;XCTRL</title>
-<path fill="none" stroke="#555555" d="M847.89,-506.27C714.74,-505.32 135.11,-500.13 102,-481.45 82.18,-470.26 69.05,-447.37 61.41,-429.71"/>
-<polygon fill="#555555" stroke="#555555" points="63.45,-429.15 59.24,-424.39 59.57,-430.74 63.45,-429.15"/>
+<path fill="none" stroke="#555555" d="M847.89,-576.27C714.74,-575.32 135.11,-570.13 102,-551.45 82.18,-540.26 69.05,-517.37 61.41,-499.71"/>
+<polygon fill="#555555" stroke="#555555" points="63.45,-499.15 59.24,-494.39 59.57,-500.74 63.45,-499.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>
+<ellipse fill="#add8e6" stroke="black" cx="353" cy="-474.96" rx="33.44" ry="18"/>
+<text text-anchor="middle" x="353" y="-470.86" font-family="DejaVu Sans" font-size="13.00">⊤:int</text>
</g>
<!-- TOP&#45;&gt;INT_TOP -->
<g id="edge54" class="edge">
<title>TOP&#45;&gt;INT_TOP</title>
-<path fill="none" stroke="#555555" d="M847.84,-506.34C753.92,-505.8 446.62,-502.41 408,-481.45 387.24,-470.18 372.3,-447.29 363.27,-429.66"/>
-<polygon fill="#555555" stroke="#555555" points="365.18,-428.78 360.66,-424.31 361.41,-430.63 365.18,-428.78"/>
+<path fill="none" stroke="#555555" d="M847.84,-576.34C753.92,-575.8 446.62,-572.41 408,-551.45 387.24,-540.18 372.3,-517.29 363.27,-499.66"/>
+<polygon fill="#555555" stroke="#555555" points="365.18,-498.78 360.66,-494.31 361.41,-500.63 365.18,-498.78"/>
</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>
+<ellipse fill="#008000" stroke="black" cx="715" cy="-474.96" rx="61.7" ry="28.99"/>
+<text text-anchor="middle" x="715" y="-479.11" font-family="DejaVu Sans" font-size="13.00" fill="white">⊤:ptr</text>
+<text text-anchor="middle" x="715" y="-462.61" font-family="DejaVu Sans" font-size="13.00" fill="white">[*⊤:struct]</text>
</g>
<!-- TOP&#45;&gt;PTR_TOP -->
<g id="edge55" class="edge">
<title>TOP&#45;&gt;PTR_TOP</title>
-<path fill="none" stroke="#555555" d="M855.7,-494.33C831.54,-479.16 789.48,-452.73 757.62,-432.72"/>
-<polygon fill="#555555" stroke="#555555" points="758.87,-431.03 752.67,-429.62 756.64,-434.59 758.87,-431.03"/>
+<path fill="none" stroke="#555555" d="M855.7,-564.33C831.54,-549.16 789.48,-522.73 757.62,-502.72"/>
+<polygon fill="#555555" stroke="#555555" points="758.87,-501.03 752.67,-499.62 756.64,-504.59 758.87,-501.03"/>
</g>
<!-- STRUCT_TOP -->
<g id="node20" class="node">
<title>STRUCT_TOP</title>
-<ellipse fill="#90ee90" stroke="black" cx="996" cy="-404.96" rx="64.35" ry="28.99"/>
-<text text-anchor="middle" x="996" y="-409.11" font-family="DejaVu Sans" font-size="13.00">⊤:struct</text>
-<text text-anchor="middle" x="996" y="-392.61" font-family="DejaVu Sans" font-size="13.00">[struct top]</text>
+<ellipse fill="#90ee90" stroke="black" cx="996" cy="-474.96" rx="64.35" ry="28.99"/>
+<text text-anchor="middle" x="996" y="-479.11" font-family="DejaVu Sans" font-size="13.00">⊤:struct</text>
+<text text-anchor="middle" x="996" y="-462.61" font-family="DejaVu Sans" font-size="13.00">[struct top]</text>
</g>
<!-- TOP&#45;&gt;STRUCT_TOP -->
<g id="edge56" class="edge">
<title>TOP&#45;&gt;STRUCT_TOP</title>
-<path fill="none" stroke="#555555" d="M891.32,-492.9C908.92,-478.28 937.5,-454.55 960.35,-435.57"/>
-<polygon fill="#555555" stroke="#555555" points="961.58,-437.27 964.86,-431.82 958.9,-434.04 961.58,-437.27"/>
+<path fill="none" stroke="#555555" d="M891.32,-562.9C908.92,-548.28 937.5,-524.55 960.35,-505.57"/>
+<polygon fill="#555555" stroke="#555555" points="961.58,-507.27 964.86,-501.82 958.9,-504.04 961.58,-507.27"/>
</g>
<!-- MEM_TOP -->
<g id="node26" class="node">
<title>MEM_TOP</title>
-<ellipse fill="#00008b" stroke="black" cx="1294" cy="-404.96" rx="44.7" ry="18"/>
-<text text-anchor="middle" x="1294" y="-400.86" font-family="DejaVu Sans" font-size="13.00" fill="white">⊤:mem</text>
+<ellipse fill="#00008b" stroke="black" cx="1294" cy="-474.96" rx="44.7" ry="18"/>
+<text text-anchor="middle" x="1294" y="-470.86" font-family="DejaVu Sans" font-size="13.00" fill="white">⊤:mem</text>
</g>
<!-- TOP&#45;&gt;MEM_TOP -->
<g id="edge57" class="edge">
<title>TOP&#45;&gt;MEM_TOP</title>
-<path fill="none" stroke="#555555" d="M902.2,-505.86C981.35,-503.98 1207.82,-497.26 1237,-481.45 1258.22,-469.95 1273.86,-446.78 1283.33,-429.12"/>
-<polygon fill="#555555" stroke="#555555" points="1284.97,-430.54 1285.85,-424.24 1281.23,-428.61 1284.97,-430.54"/>
+<path fill="none" stroke="#555555" d="M902.2,-575.86C981.35,-573.98 1207.82,-567.26 1237,-551.45 1258.22,-539.95 1273.86,-516.78 1283.33,-499.12"/>
+<polygon fill="#555555" stroke="#555555" points="1284.97,-500.54 1285.85,-494.24 1281.23,-498.61 1284.97,-500.54"/>
</g>
<!-- TUPLE_TOP -->
<g id="node28" class="node">
<title>TUPLE_TOP</title>
-<ellipse fill="#ffc0cb" stroke="black" cx="1408" cy="-404.96" rx="44.7" ry="18"/>
-<text text-anchor="middle" x="1408" y="-400.86" font-family="DejaVu Sans" font-size="13.00">⊤:tuple</text>
+<ellipse fill="#ffc0cb" stroke="black" cx="1408" cy="-474.96" rx="44.7" ry="18"/>
+<text text-anchor="middle" x="1408" y="-470.86" font-family="DejaVu Sans" font-size="13.00">⊤:tuple</text>
</g>
<!-- TOP&#45;&gt;TUPLE_TOP -->
<g id="edge58" class="edge">
<title>TOP&#45;&gt;TUPLE_TOP</title>
-<path fill="none" stroke="#555555" d="M902.34,-506.35C997.63,-505.82 1311.4,-502.48 1351,-481.45 1372.31,-470.13 1387.94,-446.93 1397.39,-429.22"/>
-<polygon fill="#555555" stroke="#555555" points="1399.03,-430.62 1399.89,-424.32 1395.29,-428.7 1399.03,-430.62"/>
+<path fill="none" stroke="#555555" d="M902.34,-576.35C997.63,-575.82 1311.4,-572.48 1351,-551.45 1372.31,-540.13 1387.94,-516.93 1397.39,-499.22"/>
+<polygon fill="#555555" stroke="#555555" points="1399.03,-500.62 1399.89,-494.32 1395.29,-498.7 1399.03,-500.62"/>
</g>
<!-- BOT -->
<g id="node2" class="node">
@@ -136,7 +136,7 @@
<!-- XCTRL&#45;&gt;CTRL -->
<g id="edge1" class="edge">
<title>XCTRL&#45;&gt;CTRL</title>
-<path fill="none" stroke="#555555" d="M53,-386.63C53,-337.1 53,-194.83 53,-137.43"/>
+<path fill="none" stroke="#555555" d="M53,-456.55C53,-397.7 53,-206.04 53,-137.58"/>
<polygon fill="#555555" stroke="#555555" points="55.1,-137.74 53,-131.74 50.9,-137.74 55.1,-137.74"/>
</g>
<!-- XCTRL&#45;&gt;INT_TOP -->
@@ -155,8 +155,8 @@
<!-- INT_TOP&#45;&gt;INT_NEG -->
<g id="edge4" class="edge">
<title>INT_TOP&#45;&gt;INT_NEG</title>
-<path fill="none" stroke="#555555" d="M337.94,-388.74C305.38,-355.83 228.33,-277.94 190.59,-239.8"/>
-<polygon fill="#555555" stroke="#555555" points="192.09,-238.33 186.38,-235.54 189.11,-241.28 192.09,-238.33"/>
+<path fill="none" stroke="#555555" d="M338.42,-458.38C324.81,-443.56 304.22,-420.4 288,-398.97 246.77,-344.48 204.12,-276.17 182.84,-241.1"/>
+<polygon fill="#555555" stroke="#555555" points="184.8,-240.29 179.9,-236.24 181.21,-242.46 184.8,-240.29"/>
</g>
<!-- INT_ZERO -->
<g id="node8" class="node">
@@ -167,8 +167,8 @@
<!-- INT_TOP&#45;&gt;INT_ZERO -->
<g id="edge5" class="edge">
<title>INT_TOP&#45;&gt;INT_ZERO</title>
-<path fill="none" stroke="#555555" d="M345.22,-387.12C329.8,-353.79 295.37,-279.32 277.78,-241.29"/>
-<polygon fill="#555555" stroke="#555555" points="279.76,-240.57 275.34,-236.01 275.95,-242.34 279.76,-240.57"/>
+<path fill="none" stroke="#555555" d="M347.22,-456.76C332.26,-412.21 292.18,-292.93 275,-241.79"/>
+<polygon fill="#555555" stroke="#555555" points="277.02,-241.21 273.12,-236.19 273.04,-242.55 277.02,-241.21"/>
</g>
<!-- INT_POS -->
<g id="node9" class="node">
@@ -179,8 +179,8 @@
<!-- INT_TOP&#45;&gt;INT_POS -->
<g id="edge6" class="edge">
<title>INT_TOP&#45;&gt;INT_POS</title>
-<path fill="none" stroke="#555555" d="M353,-386.46C353,-353.14 353,-280.53 353,-242.43"/>
-<polygon fill="#555555" stroke="#555555" points="355.1,-242.8 353,-236.8 350.9,-242.8 355.1,-242.8"/>
+<path fill="none" stroke="#555555" d="M353,-456.76C353,-412.51 353,-294.56 353,-242.85"/>
+<polygon fill="#555555" stroke="#555555" points="355.1,-242.94 353,-236.94 350.9,-242.94 355.1,-242.94"/>
</g>
<!-- INT_TOP&#45;&gt;PTR_TOP -->
<!-- INT_BOT -->
@@ -218,50 +218,50 @@
<!-- 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>
+<ellipse fill="#008000" stroke="black" cx="799" cy="-345.97" rx="27.3" ry="18"/>
+<text text-anchor="middle" x="799" y="-341.87" font-family="DejaVu Sans" font-size="13.00" fill="white">null</text>
</g>
<!-- PTR_TOP&#45;&gt;PTR_NULL -->
<g id="edge16" class="edge">
<title>PTR_TOP&#45;&gt;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"/>
+<path fill="none" stroke="#555555" d="M732.81,-447.03C748.16,-423.83 770.03,-390.77 784.37,-369.09"/>
+<polygon fill="#555555" stroke="#555555" points="785.98,-370.45 787.54,-364.29 782.48,-368.13 785.98,-370.45"/>
</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>
+<ellipse fill="#008000" stroke="black" cx="542" cy="-345.97" rx="45.72" ry="18"/>
+<text text-anchor="middle" x="542" y="-341.87" font-family="DejaVu Sans" font-size="13.00" fill="white">*Person</text>
</g>
<!-- PTR_TOP&#45;&gt;PTR_PERSON -->
<g id="edge17" class="edge">
<title>PTR_TOP&#45;&gt;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"/>
+<path fill="none" stroke="#555555" d="M675.88,-452.05C651.99,-438.01 621.28,-418.77 596,-398.97 584.24,-389.76 572.18,-378.36 562.44,-368.58"/>
+<polygon fill="#555555" stroke="#555555" points="564.05,-367.22 558.35,-364.42 561.06,-370.17 564.05,-367.22"/>
</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>
+<ellipse fill="#008000" stroke="black" cx="631" cy="-345.97" rx="27" ry="18"/>
+<text text-anchor="middle" x="631" y="-341.87" font-family="DejaVu Sans" font-size="13.00" fill="white">*s1</text>
</g>
<!-- PTR_TOP&#45;&gt;PTR_S1 -->
<g id="edge18" class="edge">
<title>PTR_TOP&#45;&gt;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"/>
+<path fill="none" stroke="#555555" d="M697.19,-447.03C681.84,-423.83 659.97,-390.77 645.63,-369.09"/>
+<polygon fill="#555555" stroke="#555555" points="647.52,-368.13 642.46,-364.29 644.02,-370.45 647.52,-368.13"/>
</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>
+<ellipse fill="#008000" stroke="black" cx="715" cy="-345.97" rx="40.6" ry="18"/>
+<text text-anchor="middle" x="715" y="-341.87" font-family="DejaVu Sans" font-size="13.00" fill="white">*s2, …</text>
</g>
<!-- PTR_TOP&#45;&gt;PTR_MORE -->
<g id="edge19" class="edge">
<title>PTR_TOP&#45;&gt;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"/>
+<path fill="none" stroke="#555555" d="M715,-445.77C715,-423.52 715,-392.77 715,-371.51"/>
+<polygon fill="#555555" stroke="#555555" points="717.1,-371.66 715,-365.66 712.9,-371.66 717.1,-371.66"/>
</g>
<!-- PTR_TOP&#45;&gt;STRUCT_TOP -->
<!-- PTR_BOT -->
@@ -286,8 +286,8 @@
<!-- PTR_NULL&#45;&gt;PTR_PERSON_NIL -->
<g id="edge26" class="edge">
<title>PTR_NULL&#45;&gt;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"/>
+<path fill="none" stroke="#555555" d="M790.34,-328.75C783.99,-317.83 774.65,-303.55 764,-292.97 740.64,-269.76 709.08,-249.53 685.25,-235.95"/>
+<polygon fill="#555555" stroke="#555555" points="686.33,-234.14 680.07,-233.04 684.27,-237.8 686.33,-234.14"/>
</g>
<!-- PTR_S1_NIL -->
<g id="node18" class="node">
@@ -298,8 +298,8 @@
<!-- PTR_NULL&#45;&gt;PTR_S1_NIL -->
<g id="edge27" class="edge">
<title>PTR_NULL&#45;&gt;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"/>
+<path fill="none" stroke="#555555" d="M792.23,-328.1C783.31,-305.9 767.59,-266.75 757.49,-241.6"/>
+<polygon fill="#555555" stroke="#555555" points="759.55,-241.1 755.36,-236.31 755.65,-242.66 759.55,-241.1"/>
</g>
<!-- PTR_MORE_NIL -->
<g id="node19" class="node">
@@ -310,8 +310,8 @@
<!-- PTR_NULL&#45;&gt;PTR_MORE_NIL -->
<g id="edge28" class="edge">
<title>PTR_NULL&#45;&gt;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"/>
+<path fill="none" stroke="#555555" d="M804.53,-327.84C811.65,-305.78 824.07,-267.3 832.17,-242.24"/>
+<polygon fill="#555555" stroke="#555555" points="834.12,-243.03 833.96,-236.68 830.12,-241.74 834.12,-243.03"/>
</g>
<!-- PTR_ANY -->
<g id="node13" class="node">
@@ -330,41 +330,41 @@
<!-- PTR_PERSON&#45;&gt;PTR_ANY -->
<g id="edge20" class="edge">
<title>PTR_PERSON&#45;&gt;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"/>
+<path fill="none" stroke="#555555" d="M536.61,-327.84C530.59,-308.74 520.69,-277.32 513.02,-252.97"/>
+<polygon fill="#555555" stroke="#555555" points="515.08,-252.52 511.28,-247.43 511.08,-253.78 515.08,-252.52"/>
</g>
<!-- PTR_PERSON&#45;&gt;PTR_S1 -->
<!-- PTR_PERSON&#45;&gt;PTR_PERSON_NIL -->
<g id="edge23" class="edge">
<title>PTR_PERSON&#45;&gt;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"/>
+<path fill="none" stroke="#555555" d="M556.03,-328.62C575.38,-306.08 610.32,-265.36 632,-240.11"/>
+<polygon fill="#555555" stroke="#555555" points="633.38,-241.73 635.7,-235.81 630.19,-238.99 633.38,-241.73"/>
</g>
<!-- PTR_S1&#45;&gt;PTR_ANY -->
<g id="edge21" class="edge">
<title>PTR_S1&#45;&gt;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"/>
+<path fill="none" stroke="#555555" d="M621.75,-328.57C615.28,-317.84 606.03,-303.85 596,-292.97 580.61,-276.28 561.35,-260.24 544.26,-247.31"/>
+<polygon fill="#555555" stroke="#555555" points="545.84,-245.87 539.78,-243.96 543.32,-249.23 545.84,-245.87"/>
</g>
<!-- PTR_S1&#45;&gt;PTR_MORE -->
<!-- PTR_S1&#45;&gt;PTR_S1_NIL -->
<g id="edge24" class="edge">
<title>PTR_S1&#45;&gt;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"/>
+<path fill="none" stroke="#555555" d="M640.49,-328.79C647.07,-318.15 656.34,-304.18 666,-292.97 684.1,-271.96 707.73,-250.9 724.97,-236.47"/>
+<polygon fill="#555555" stroke="#555555" points="726.11,-238.25 729.39,-232.8 723.43,-235.02 726.11,-238.25"/>
</g>
<!-- PTR_MORE&#45;&gt;PTR_NULL -->
<!-- PTR_MORE&#45;&gt;PTR_ANY -->
<g id="edge22" class="edge">
<title>PTR_MORE&#45;&gt;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"/>
+<path fill="none" stroke="#555555" d="M702.63,-328.84C693.45,-317.69 680.11,-303.08 666,-292.97 635.59,-271.17 597.84,-253.38 566.22,-240.61"/>
+<polygon fill="#555555" stroke="#555555" points="567.33,-238.79 560.98,-238.52 565.78,-242.69 567.33,-238.79"/>
</g>
<!-- PTR_MORE&#45;&gt;PTR_MORE_NIL -->
<g id="edge25" class="edge">
<title>PTR_MORE&#45;&gt;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"/>
+<path fill="none" stroke="#555555" d="M729.86,-328.89C739.5,-318.54 752.38,-304.85 764,-292.97 782,-274.55 802.85,-254.03 818.15,-239.12"/>
+<polygon fill="#555555" stroke="#555555" points="819.6,-240.64 822.44,-234.95 816.67,-237.63 819.6,-240.64"/>
</g>
<!-- PTR_PERSON_NIL&#45;&gt;PTR_BOT -->
<g id="edge30" class="edge">
@@ -396,8 +396,8 @@
<!-- STRUCT_TOP&#45;&gt;STRUCT_PERSON -->
<g id="edge36" class="edge">
<title>STRUCT_TOP&#45;&gt;STRUCT_PERSON</title>
-<path fill="none" stroke="#555555" d="M987.95,-375.71C977.84,-340.23 960.61,-279.78 950.68,-244.92"/>
-<polygon fill="#555555" stroke="#555555" points="952.75,-244.54 949.09,-239.35 948.72,-245.69 952.75,-244.54"/>
+<path fill="none" stroke="#555555" d="M990.23,-446.07C979.97,-396.53 958.69,-293.77 948.58,-244.91"/>
+<polygon fill="#555555" stroke="#555555" points="950.69,-244.75 947.42,-239.3 946.58,-245.61 950.69,-244.75"/>
</g>
<!-- STRUCT_S1 -->
<g id="node23" class="node">
@@ -409,8 +409,8 @@
<!-- STRUCT_TOP&#45;&gt;STRUCT_S1 -->
<g id="edge37" class="edge">
<title>STRUCT_TOP&#45;&gt;STRUCT_S1</title>
-<path fill="none" stroke="#555555" d="M999.65,-375.71C1004.22,-340.23 1012.02,-279.78 1016.52,-244.92"/>
-<polygon fill="#555555" stroke="#555555" points="1018.55,-245.61 1017.24,-239.39 1014.39,-245.07 1018.55,-245.61"/>
+<path fill="none" stroke="#555555" d="M998.66,-445.58C1003.32,-395.91 1012.89,-293.85 1017.46,-245.08"/>
+<polygon fill="#555555" stroke="#555555" points="1019.54,-245.41 1018.01,-239.24 1015.36,-245.02 1019.54,-245.41"/>
</g>
<!-- STRUCT_S2 -->
<g id="node24" class="node">
@@ -422,8 +422,8 @@
<!-- STRUCT_TOP&#45;&gt;STRUCT_S2 -->
<g id="edge38" class="edge">
<title>STRUCT_TOP&#45;&gt;STRUCT_S2</title>
-<path fill="none" stroke="#555555" d="M1012.67,-376.51C1034.35,-340.84 1071.93,-278.99 1093.18,-244.02"/>
-<polygon fill="#555555" stroke="#555555" points="1094.84,-245.33 1096.16,-239.11 1091.25,-243.14 1094.84,-245.33"/>
+<path fill="none" stroke="#555555" d="M1008.31,-446.07C1030.23,-396.43 1075.73,-293.34 1097.24,-244.61"/>
+<polygon fill="#555555" stroke="#555555" points="1099.12,-245.54 1099.63,-239.21 1095.28,-243.85 1099.12,-245.54"/>
</g>
<!-- STRUCT_MORE -->
<g id="node25" class="node">
@@ -434,8 +434,8 @@
<!-- STRUCT_TOP&#45;&gt;STRUCT_MORE -->
<g id="edge39" class="edge">
<title>STRUCT_TOP&#45;&gt;STRUCT_MORE</title>
-<path fill="none" stroke="#555555" d="M1023.66,-378.49C1063.42,-341.89 1136.06,-275.01 1173.79,-240.26"/>
-<polygon fill="#555555" stroke="#555555" points="1174.99,-242.01 1177.99,-236.4 1172.15,-238.92 1174.99,-242.01"/>
+<path fill="none" stroke="#555555" d="M1018.31,-447.4C1030.2,-433.14 1045.02,-415.19 1058,-398.97 1102.92,-342.8 1154.32,-275.57 1180.57,-241"/>
+<polygon fill="#555555" stroke="#555555" points="1182,-242.59 1183.95,-236.54 1178.65,-240.05 1182,-242.59"/>
</g>
<!-- STRUCT_TOP&#45;&gt;MEM_TOP -->
<!-- STRUCT_BOT -->
@@ -487,7 +487,7 @@
<!-- MEM_TOP&#45;&gt;MEM_BOT -->
<g id="edge44" class="edge">
<title>MEM_TOP&#45;&gt;MEM_BOT</title>
-<path fill="none" stroke="#555555" d="M1294,-386.63C1294,-337.1 1294,-194.83 1294,-137.43"/>
+<path fill="none" stroke="#555555" d="M1294,-456.55C1294,-397.7 1294,-206.04 1294,-137.58"/>
<polygon fill="#555555" stroke="#555555" points="1296.1,-137.74 1294,-131.74 1291.9,-137.74 1296.1,-137.74"/>
</g>
<!-- MEM_TOP&#45;&gt;TUPLE_TOP -->
@@ -500,70 +500,94 @@
<!-- TUPLE1 -->
<g id="node30" class="node">
<title>TUPLE1</title>
-<polygon fill="#ffc0cb" stroke="black" points="1418.88,-328.97 1363.12,-328.97 1363.12,-292.97 1418.88,-292.97 1418.88,-328.97"/>
-<text text-anchor="middle" x="1391" y="-306.87" font-family="DejaVu Sans" font-size="13.00">(type)</text>
+<polygon fill="#ffc0cb" stroke="none" points="1406.88,-372.47 1363.12,-372.47 1363.12,-319.47 1406.88,-319.47 1406.88,-372.47"/>
+<polygon fill="#ffc0cb" stroke="none" points="1363.12,-345.97 1363.12,-372.47 1406.88,-372.47 1406.88,-345.97 1363.12,-345.97"/>
+<polygon fill="none" stroke="black" points="1363.12,-345.97 1363.12,-372.47 1406.88,-372.47 1406.88,-345.97 1363.12,-345.97"/>
+<text text-anchor="start" x="1368.12" y="-355.12" font-family="DejaVu Sans" font-size="13.00">tuple</text>
+<polygon fill="black" stroke="none" points="1363.12,-319.47 1363.12,-345.97 1406.88,-345.97 1406.88,-319.47 1363.12,-319.47"/>
+<polygon fill="none" stroke="black" points="1363.12,-319.47 1363.12,-345.97 1406.88,-345.97 1406.88,-319.47 1363.12,-319.47"/>
+<text text-anchor="start" x="1370.38" y="-328.62" font-family="DejaVu Sans" font-size="13.00" fill="white">type</text>
</g>
<!-- TUPLE_TOP&#45;&gt;TUPLE1 -->
<g id="edge47" class="edge">
<title>TUPLE_TOP&#45;&gt;TUPLE1</title>
-<path fill="none" stroke="#555555" d="M1404.8,-386.65C1402.19,-372.52 1398.47,-352.37 1395.54,-336.56"/>
-<polygon fill="#555555" stroke="#555555" points="1397.61,-336.2 1394.46,-330.68 1393.48,-336.96 1397.61,-336.2"/>
+<path fill="none" stroke="#555555" d="M1404.85,-456.57C1401.26,-436.74 1395.31,-403.91 1390.86,-379.34"/>
+<polygon fill="#555555" stroke="#555555" points="1392.94,-379.03 1389.81,-373.5 1388.81,-379.77 1392.94,-379.03"/>
</g>
<!-- TUPLE2 -->
<g id="node31" class="node">
<title>TUPLE2</title>
-<polygon fill="#ffc0cb" stroke="black" points="1529,-328.97 1435,-328.97 1435,-292.97 1529,-292.97 1529,-328.97"/>
-<text text-anchor="middle" x="1482" y="-306.87" font-family="DejaVu Sans" font-size="13.00">(type, type)</text>
+<polygon fill="#ffc0cb" stroke="none" points="1466.88,-385.72 1423.12,-385.72 1423.12,-306.22 1466.88,-306.22 1466.88,-385.72"/>
+<polygon fill="#ffc0cb" stroke="none" points="1423.12,-359.22 1423.12,-385.72 1466.88,-385.72 1466.88,-359.22 1423.12,-359.22"/>
+<polygon fill="none" stroke="black" points="1423.12,-359.22 1423.12,-385.72 1466.88,-385.72 1466.88,-359.22 1423.12,-359.22"/>
+<text text-anchor="start" x="1428.12" y="-368.37" font-family="DejaVu Sans" font-size="13.00">tuple</text>
+<polygon fill="black" stroke="none" points="1423.12,-332.72 1423.12,-359.22 1466.88,-359.22 1466.88,-332.72 1423.12,-332.72"/>
+<polygon fill="none" stroke="black" points="1423.12,-332.72 1423.12,-359.22 1466.88,-359.22 1466.88,-332.72 1423.12,-332.72"/>
+<text text-anchor="start" x="1430.38" y="-341.87" font-family="DejaVu Sans" font-size="13.00" fill="white">type</text>
+<polygon fill="black" stroke="none" points="1423.12,-306.22 1423.12,-332.72 1466.88,-332.72 1466.88,-306.22 1423.12,-306.22"/>
+<polygon fill="none" stroke="black" points="1423.12,-306.22 1423.12,-332.72 1466.88,-332.72 1466.88,-306.22 1423.12,-306.22"/>
+<text text-anchor="start" x="1430.38" y="-315.37" font-family="DejaVu Sans" font-size="13.00" fill="white">type</text>
</g>
<!-- TUPLE_TOP&#45;&gt;TUPLE2 -->
<g id="edge48" class="edge">
<title>TUPLE_TOP&#45;&gt;TUPLE2</title>
-<path fill="none" stroke="#555555" d="M1421.23,-387.51C1433,-372.87 1450.35,-351.31 1463.5,-334.97"/>
-<polygon fill="#555555" stroke="#555555" points="1465.06,-336.38 1467.18,-330.39 1461.79,-333.74 1465.06,-336.38"/>
+<path fill="none" stroke="#555555" d="M1412.99,-456.83C1417.76,-440.46 1425.17,-415.04 1431.65,-392.78"/>
+<polygon fill="#555555" stroke="#555555" points="1433.64,-393.48 1433.3,-387.13 1429.6,-392.3 1433.64,-393.48"/>
</g>
<!-- TUPLEN -->
<g id="node32" class="node">
<title>TUPLEN</title>
-<polygon fill="#ffc0cb" stroke="black" points="1622.75,-328.97 1545.25,-328.97 1545.25,-292.97 1622.75,-292.97 1622.75,-328.97"/>
-<text text-anchor="middle" x="1584" y="-306.87" font-family="DejaVu Sans" font-size="13.00">(type, …)</text>
+<polygon fill="#ffc0cb" stroke="none" points="1526.88,-398.97 1483.12,-398.97 1483.12,-292.97 1526.88,-292.97 1526.88,-398.97"/>
+<polygon fill="#ffc0cb" stroke="none" points="1483.12,-372.47 1483.12,-398.97 1526.88,-398.97 1526.88,-372.47 1483.12,-372.47"/>
+<polygon fill="none" stroke="black" points="1483.12,-372.47 1483.12,-398.97 1526.88,-398.97 1526.88,-372.47 1483.12,-372.47"/>
+<text text-anchor="start" x="1488.12" y="-381.62" font-family="DejaVu Sans" font-size="13.00">tuple</text>
+<polygon fill="black" stroke="none" points="1483.12,-345.97 1483.12,-372.47 1526.88,-372.47 1526.88,-345.97 1483.12,-345.97"/>
+<polygon fill="none" stroke="black" points="1483.12,-345.97 1483.12,-372.47 1526.88,-372.47 1526.88,-345.97 1483.12,-345.97"/>
+<text text-anchor="start" x="1490.38" y="-355.12" font-family="DejaVu Sans" font-size="13.00" fill="white">type</text>
+<polygon fill="black" stroke="none" points="1483.12,-319.47 1483.12,-345.97 1526.88,-345.97 1526.88,-319.47 1483.12,-319.47"/>
+<polygon fill="none" stroke="black" points="1483.12,-319.47 1483.12,-345.97 1526.88,-345.97 1526.88,-319.47 1483.12,-319.47"/>
+<text text-anchor="start" x="1498.62" y="-328.62" font-family="DejaVu Sans" font-size="13.00" fill="white">…</text>
+<polygon fill="black" stroke="none" points="1483.12,-292.97 1483.12,-319.47 1526.88,-319.47 1526.88,-292.97 1483.12,-292.97"/>
+<polygon fill="none" stroke="black" points="1483.12,-292.97 1483.12,-319.47 1526.88,-319.47 1526.88,-292.97 1483.12,-292.97"/>
+<text text-anchor="start" x="1490.38" y="-302.12" font-family="DejaVu Sans" font-size="13.00" fill="white">type</text>
</g>
<!-- TUPLE_TOP&#45;&gt;TUPLEN -->
<g id="edge49" class="edge">
<title>TUPLE_TOP&#45;&gt;TUPLEN</title>
-<path fill="none" stroke="#555555" d="M1434.35,-390.18C1463.39,-375.01 1510.42,-350.42 1544.01,-332.87"/>
-<polygon fill="#555555" stroke="#555555" points="1544.85,-334.8 1549.2,-330.16 1542.91,-331.07 1544.85,-334.8"/>
+<path fill="none" stroke="#555555" d="M1424.21,-457.82C1438.47,-443.17 1459.34,-420.61 1475,-398.97 1476.4,-397.02 1477.8,-395.01 1479.17,-392.95"/>
+<polygon fill="#555555" stroke="#555555" points="1480.91,-394.14 1482.41,-387.96 1477.38,-391.86 1480.91,-394.14"/>
</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>
+<ellipse fill="#ffc0cb" stroke="black" cx="1426" cy="-111.99" rx="44.7" ry="18"/>
+<text text-anchor="middle" x="1426" y="-107.89" font-family="DejaVu Sans" font-size="13.00">⊥:tuple</text>
</g>
<!-- TUPLE_BOT&#45;&gt;BOT -->
<g id="edge64" class="edge">
<title>TUPLE_BOT&#45;&gt;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"/>
+<path fill="none" stroke="#555555" d="M1401.48,-96.69C1387.21,-89.03 1368.61,-80.13 1351,-75 1193.97,-29.27 996.8,-20.82 918.41,-19.3"/>
+<polygon fill="#555555" stroke="#555555" points="918.75,-17.21 912.72,-19.21 918.68,-21.41 918.75,-17.21"/>
</g>
<!-- TUPLE1&#45;&gt;TUPLE_BOT -->
<g id="edge50" class="edge">
<title>TUPLE1&#45;&gt;TUPLE_BOT</title>
-<path fill="none" stroke="#555555" d="M1395.79,-292.49C1405.46,-257.21 1427.33,-177.44 1438.37,-137.19"/>
-<polygon fill="#555555" stroke="#555555" points="1440.33,-137.95 1439.9,-131.61 1436.28,-136.84 1440.33,-137.95"/>
+<path fill="none" stroke="#555555" d="M1389.47,-319.7C1397.39,-274.85 1413.82,-181.9 1421.66,-137.56"/>
+<polygon fill="#555555" stroke="#555555" points="1423.69,-138.14 1422.66,-131.86 1419.55,-137.4 1423.69,-138.14"/>
</g>
<!-- TUPLE1&#45;&gt;TUPLE2 -->
<!-- TUPLE2&#45;&gt;TUPLE_BOT -->
<g id="edge51" class="edge">
<title>TUPLE2&#45;&gt;TUPLE_BOT</title>
-<path fill="none" stroke="#555555" d="M1478.72,-292.49C1472.12,-257.37 1457.24,-178.17 1449.65,-137.74"/>
-<polygon fill="#555555" stroke="#555555" points="1451.73,-137.43 1448.56,-131.92 1447.6,-138.2 1451.73,-137.43"/>
+<path fill="none" stroke="#555555" d="M1441.84,-306.4C1437.91,-258.37 1431.3,-177.75 1428,-137.46"/>
+<polygon fill="#555555" stroke="#555555" points="1430.13,-137.74 1427.55,-131.93 1425.95,-138.08 1430.13,-137.74"/>
</g>
<!-- TUPLE2&#45;&gt;TUPLEN -->
<!-- TUPLEN&#45;&gt;TUPLE_BOT -->
<g id="edge52" class="edge">
<title>TUPLEN&#45;&gt;TUPLE_BOT</title>
-<path fill="none" stroke="#555555" d="M1571.66,-292.49C1546.44,-256.74 1488.97,-175.3 1460.95,-135.59"/>
-<polygon fill="#555555" stroke="#555555" points="1462.76,-134.52 1457.59,-130.83 1459.33,-136.94 1462.76,-134.52"/>
+<path fill="none" stroke="#555555" d="M1487.37,-293.19C1470.79,-244.5 1446.69,-173.75 1434.2,-137.07"/>
+<polygon fill="#555555" stroke="#555555" points="1436.27,-136.64 1432.35,-131.63 1432.3,-137.99 1436.27,-136.64"/>
</g>
</g>
</svg>
diff --git a/graph/README.md b/graph/README.md
index 915746e9..7479aaf9 100644
--- a/graph/README.md
+++ b/graph/README.md
@@ -143,7 +143,7 @@ The chapter `docs/Makefile` rules select JSON before a same-named legacy DOT
file. They accept `PYTHON` and `DOC_BROWSER` overrides, for example
`make -C chapter01/docs PYTHON=python DOC_BROWSER=msedge` from the repository root.
-The compact memory-dependency schematics in 10a/10b retain `.gv` sources to
+The compact memory-dependency schematics in 10b retain `.gv` sources to
preserve their deliberate horizontal layouts. They use the viewer palette and
node shapes; definitions are left of uses horizontally and above uses vertically,
with arrows from use to definition. Type lattices use their own domain colors