summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2024-06-30 01:58:08 +0100
committerCliff Click <cliffc@acm.org>2026-10-08 16:52:27 -0700
commit1659b2591654acd3679c2779fd62281e496fdaf6 (patch)
tree82cb6d11ee31e58b38b8e3eab0141d336501ba1a
parent2a643205cc0b4adff394c6bed272e4b9f158f89f (diff)
downloadSimple-1659b2591654acd3679c2779fd62281e496fdaf6.tar.gz
Simple-1659b2591654acd3679c2779fd62281e496fdaf6.zip
Chapter 11: Splitting Memory
Co-authored-by: Hsiang-Ying Fu <66062782+fuhsnn@users.noreply.github.com> Co-authored-by: Dibyendu Majumdar <mobile@majumdar.org.uk> Co-authored-by: Thalia Archibald <thalia@archibald.dev> Co-authored-by: Tamás Hadházy <85063808+Hels15@users.noreply.github.com> Co-authored-by: tannal <tannal2409@gmail.com> Co-authored-by: hadha <helsgithub15@gmail.com> Co-authored-by: fuhsnn <66062782+fuhsnn@users.noreply.github.com> Co-authored-by: Symious <14933944+symious@users.noreply.github.com> Co-authored-by: RobertObkircher <robert.obkircher@gmail.com> Co-authored-by: Robert Obkircher <robert.obkircher@gmail.com> Co-authored-by: Jun Kawasaki <jun784@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
-rw-r--r--README.ja.md2
-rw-r--r--README.md2
-rw-r--r--chapter11.iml (renamed from chapter10.iml)4
-rw-r--r--docs/chapter11/Makefile1
-rw-r--r--docs/chapter11/README.ja.md168
-rw-r--r--docs/chapter11/README.md270
-rw-r--r--docs/chapter11/example1.gv20
-rw-r--r--docs/chapter11/example1.json31
-rw-r--r--docs/chapter11/example1.svg155
-rw-r--r--docs/chapter11/example2a.gv17
-rw-r--r--docs/chapter11/example2a.json20
-rw-r--r--docs/chapter11/example2a.svg103
-rw-r--r--docs/chapter11/example2b-final.json251
-rw-r--r--docs/chapter11/example2b-final.svg110
-rw-r--r--docs/chapter11/example2b-merged.json339
-rw-r--r--docs/chapter11/example2b-merged.svg130
-rw-r--r--docs/chapter11/example2b-split-y.json486
-rw-r--r--docs/chapter11/example2b-split-y.svg157
-rw-r--r--docs/chapter11/example2b.gv19
-rw-r--r--docs/chapter11/example2b.json30
-rw-r--r--docs/chapter11/example2b.svg150
-rw-r--r--docs/chapter11/example2c.gv15
-rw-r--r--docs/chapter11/example2c.json23
-rw-r--r--docs/chapter11/example2c.svg121
-rw-r--r--docs/chapter11/lattice.gv133
-rw-r--r--docs/chapter11/lattice.svg644
-rw-r--r--src/main/java/com/seaofnodes/simple/IRPrinter.java1
-rw-r--r--src/main/java/com/seaofnodes/simple/Parser.java22
-rw-r--r--src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java237
-rw-r--r--src/main/java/com/seaofnodes/simple/node/LoadNode.java32
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MemMergeNode.java61
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MemOpNode.java13
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MemPhiNode.java36
-rw-r--r--src/main/java/com/seaofnodes/simple/node/Node.java10
-rw-r--r--src/main/java/com/seaofnodes/simple/node/PhiNode.java21
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ScopeNode.java4
-rw-r--r--src/main/java/com/seaofnodes/simple/node/StoreNode.java18
-rw-r--r--src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/type/Type.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeMem.java72
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter10Test.java31
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter11Test.java64
-rw-r--r--src/test/java/com/seaofnodes/simple/TypeTest.java21
-rw-r--r--src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java1
-rw-r--r--src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java13
45 files changed, 3991 insertions, 71 deletions
diff --git a/README.ja.md b/README.ja.md
index 26698a4a..faa963f4 100644
--- a/README.ja.md
+++ b/README.ja.md
@@ -47,7 +47,7 @@ Simple 言語のスタイルは、C または Java のサブセットに倣っã�
* [第8章](docs/chapter08/README.ja.md):ループ構造の続き。必要時の Phi の生成、`break` 文と `continue` 文。
* [第9章](docs/chapter09/README.ja.md):大域的値番号付け。不動点に達するまでのピープホール最適化の反復。ワークリスト。
* [第10章](docs/chapter10/README.ja.md):ユーザー定義の構造体、ポインタ、null 解析。SSA で表す単一のメモリ値。ロード、ストア、実行可能な評価器。
-* 第11章:同値類によるエイリアス解析。`MemMerge`、`MemPhi`、`BulkMemPhi` による必要時のメモリ分割。
+* [第11章](docs/chapter11/README.ja.md):同値類によるエイリアス解析。`MemMerge`、`MemPhi`、`BulkMemPhi` による必要時のメモリ分割。
* 第12章:参照フィールド、前方参照、再帰的な構造体。
* 第13章:大域的コード移動とスケジューリング。
* 第14章:数値型。浮動小数点数、狭い整数型、範囲、`f32` への丸め。
diff --git a/README.md b/README.md
index ce0d3e52..3ca676ee 100644
--- a/README.md
+++ b/README.md
@@ -55,7 +55,7 @@ The Simple language is styled after a subset of C or Java.
* [Chapter 9](docs/chapter09/README.md): Global Value Numbering. Iterative peepholes to fixpoint. Worklists.
* [Chapter 10](docs/chapter10/README.md): User defined structs, pointers and null
analysis. One memory value in SSA. Loads, stores, and an executable evaluator.
-* Chapter 11: Equivalence class aliasing. Lazy memory
+* [Chapter 11](docs/chapter11/README.md): Equivalence class aliasing. Lazy memory
partitioning with `MemMerge`, `MemPhi`, and `BulkMemPhi`.
* Chapter 12: Reference fields, forward references and recursive structs.
* Chapter 13: Global Code Motion - Scheduling.
diff --git a/chapter10.iml b/chapter11.iml
index a352db38..92b9e5ca 100644
--- a/chapter10.iml
+++ b/chapter11.iml
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="false">
- <output url="file://$MODULE_DIR$/../build/idea/production/chapter10" />
- <output-test url="file://$MODULE_DIR$/../build/idea/test/chapter10" />
+ <output url="file://$MODULE_DIR$/../build/idea/production/chapter11" />
+ <output-test url="file://$MODULE_DIR$/../build/idea/test/chapter11" />
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
diff --git a/docs/chapter11/Makefile b/docs/chapter11/Makefile
new file mode 100644
index 00000000..6fa0f057
--- /dev/null
+++ b/docs/chapter11/Makefile
@@ -0,0 +1 @@
+include ../../graph/docs.mk
diff --git a/docs/chapter11/README.ja.md b/docs/chapter11/README.ja.md
new file mode 100644
index 00000000..3ef66442
--- /dev/null
+++ b/docs/chapter11/README.ja.md
@@ -0,0 +1,168 @@
+# 第11章: メモリの分割
+
+[English](README.md) | 日本語
+
+[前: 第10章](../chapter10/README.ja.md) |
+[次: 第12章](../chapter12/README.ja.md)
+
+この文書は [原文](README.md) の日本語訳です。
+
+第10章では、1つの SSA メモリ値でメモリへの作用を明示しました。この章ではパーサーのモデルをそのまま維持し、グラフの書き換えによって独立したメモリ鎖を復元します。新しい言語構文はありません。[文法](../chapter10/10-grammar.ja.md)、ポインタ型、null チェックは変わりません。
+
+## なぜメモリを分割するのか
+
+```java
+struct Point { int x; int y; }
+Point p = new Point;
+p.x = 42;
+while (arg > 0) {
+ p.y = p.y + arg;
+ arg = arg - 1;
+}
+return p.x;
+```
+
+ループが変更するのは `y` なので、`x` に影響できません。メモリ鎖が1本だと、`x` のロードはループのストアの後に続きます。フィールドを分割すれば、通常のストア後のロードへの値の転送によって、戻り値を 42 にできます。
+
+![独立したフィールドの依存関係](example1.svg)
+
+グラフは、最後の Load を畳み込む前の分割された鎖を示します。そのメモリ入力は、ループの `y` の鎖を迂回して 42 の Store に直接つながります。第10章と同様、小さく切り離された True/False 射影は、Load と Store を制御するソースの分岐を示します。Loop とその Phi 束縛を含む、ほかの制御フローの文脈は省略しています。グラフが示すのは実行順序ではなく依存関係です。
+
+## 同値類によるエイリアシング
+
+各構造体フィールドは整数のエイリアス識別子を持ちます。別のフィールドには、異なる構造体で同じ綴りを持っていても、別の識別子を与えます。*同じ*構造体の2つのインスタンスは、フィールドのエイリアスを共有します。したがって異なるクラス間では決してエイリアスしませんが、同じクラス内のアクセスは**エイリアスする可能性があります**(同じクラスであることは同じアドレスを意味しません)。ポインタの同一性は、依然として確認する必要があります。
+
+パーサーは構造体の宣言時に、alias#2 から始めてフィールドの識別子を割り当てます。
+
+宣言されたフィールドやエイリアスのそれぞれについて、Start 射影、スコープの束縛、ループ Phi を割り当てることはしません。Start が1つのメモリ値を供給し、Scope が1つの `$mem` 束縛を保持し、Return がメモリ全体を消費します: `{ctrl, $mem, result}`。Start の出力と同様、制御はスロット 0、メモリはスロット 1 にあります。最適化器は、グラフの演算が必要とする箇所にだけスライスを導入します。
+
+## 3つのメモリノード
+
+| ノード | 意味 |
+|---|---|
+| `MemMerge` | デフォルトと精密なエイリアスごとの上書きからなる完全なメモリ状態 |
+| `MemPhi` | 流入する制御経路に従って選択される1つのエイリアス |
+| `BulkMemPhi` | 明示した除外集合を除くすべてのエイリアスを、制御によって選択する |
+
+`MemMerge` は、あるプログラム位置でメモリの独立した部分を組み合わせます。制御フローの合流ではありません。たとえばエイリアス `x` へのストア後は、次のようになります。
+
+```text
+prior = current memory
+store = Store_x(prior, pointer, value)
+current memory = MemMerge(default=prior, x=store)
+```
+
+MemMerge はスロット 1 にデフォルトのメモリを持ち、それと異なる場合にはほかのスロットに精密なエイリアスを持ちます。Load、Store、MemPhi は、入力の MemMerge から精密な入力を選択し、可能なら精密なエイリアス入力を使います。
+
+明示的なエイリアスはデフォルトを上書きします。エイリアスのスライスは互いに交わらず、その和集合はすべてのメモリを覆います。積み重なった MemMerge を平坦化するときは、外側の MemMerge が置き換えていないエントリを保持します。
+
+以下のグラフは、冗長な初期化ストアを除去した後の `p.x = arg; p.y = 42; return p;` を示します。MemMerge はスロット 1 にデフォルトのメモリを、スロット 2 と 3 に `x` と `y` の上書きを受け取ります。
+
+![集約が両方の更新を保持する](example2a.svg)
+
+## メモリ Phi の分割
+
+パーサーは最初に、通常の SSA アルゴリズムでメモリの合流が必要な箇所に BulkMemPhi を作ります。合流点の確定中は、除外集合を空としてすべてのエイリアスを覆うところから始めます。その後、ピープホール最適化によって、必要に応じて遅延的に、ひとまとめのメモリから精密なメモリスライスを分離します。
+
+この変換により、ひとまとめのメモリから精密な単一エイリアスを分離できます。
+
+```text
+BulkPhi(R, left, right)
+ becomes
+MemMerge(
+ default = BulkPhi_except{x}(R, left, right),
+ x = MemPhi_x(R, slice(left,x), slice(right,x)))
+```
+
+「BulkPhi は、(そのエイリアスを除いた)BulkPhi と(そのエイリアスの)MemPhi をまとめる Merge になる」。
+
+新しい精密な Phi と残りのひとまとめの Phi は、Region(図では省略)と一致する制御経路の添字を共有します。ループでも同じ変換を使い、入力の中に後退エッジが含まれます。さらに分割しても、すでに抽出したスライスをすべて保持します(積み重なった分割はピープホール最適化で整理します)。
+
+ここでは2つの分岐が `p.x` に 3 または 4 を格納してから `p` を返します。グラフは、ひとまとめの Phi とその入力の集約をさらに簡略化する前、`x` を抽出した直後の分割を示します。
+
+```java
+struct Point { int x; int y; }
+Point p = new Point;
+if (arg) p.x = 3;
+else p.x = 4;
+return p;
+```
+
+![精密な Phi とひとまとめの Phi が制御の合流を共有する](example2b.svg)
+
+このグラフから先に進めると、たとえば次の順でピープホール最適化が発動します。
+
+**入力の MemMerge を平坦化してから、`y` を分割します。** MemMerge #17 と #18 は #9 から `y` のスライスを継承しますが、それぞれ自身の `x` の上書きが優先します。両者のデフォルトは Start のメモリとなり、#9 は使われなくなります。BulkMemPhi #21 は `x` を除外しますが、まだ `y` を覆うので、これらの MemMerge を単に迂回することはできません。`y` の MemPhi #24 を抽出し、両フィールドを除外した BulkMemPhi #25 を作ります。追加の MemMerge #26 がこれらのスライスを組み合わせます。
+
+![入力の集約を平坦化し y を抽出した後](example2b-split-y.svg)
+
+**変更のないメモリを畳み込みます。** MemPhi #24 の両入力は同じ初期化 Store #8 なので、その Store になります。BulkMemPhi #25 は、今度は #17 と #18 を透かして見ることができます。それらの明示的なエイリアスはすべて、その領域から除外されているためです。残る両入力は Start のメモリになり、このひとまとめの Phi も畳み込まれます。出力側の集約を平坦化すると、MemMerge #22 には Start からのデフォルトメモリ、MemPhi #20 からの `x`、Store #8 からの `y` が残ります。
+
+![分岐の合流が必要なのは x のメモリ Phi だけ](example2b-merged.svg)
+
+**Store をくくり出してから、上書きされた初期化を取り除きます。** ひとまとめの Phi と入力の集約がなくなると、Store #14 と #16 を使うのは MemPhi #20 だけになります。これらは同じポインタとエイリアスに格納し、同じ入力メモリを共有します。`PhiNode` は Store を合流の下に引き下げられます。
+
+```text
+MemPhi_x(Store_x(mem, p, 3), Store_x(mem, p, 4))
+ becomes
+Store_x(mem, p, Phi(3, 4))
+```
+
+一般に Phi の組み替えでは、第10章と同じく、Load や Store を押し上げたり引き下げたりして組み合わせられます。Phi の新しいメモリオペランドには、そのエイリアスの精密な MemPhi を使えます。このピープホール変換は精密なエイリアスを必要とし、BulkMem があると妨げられます。そのため、BulkMem を分割するきっかけにもなります。
+
+例の続きを見ると、新しい値の Phi #28 は true 側で 3、false 側で 4 を選びます。Store #27 が両分岐の Store とそのメモリ Phi を置き換えます。初期の `p.x = 0` の Store #7 は、この1つの Store だけをユーザーに持つようになり、連続するストアの除去で迂回されます。`p.y = 0` の Store #8 は残ります。オブジェクトを返すので、その `y` フィールドは引き続き初期化が必要です。
+
+![最終グラフ: 値の Phi を格納する1つの x Store と y の初期化](example2b-final.svg)
+
+したがって最終グラフには、2つの Store、通常の値の Phi が1つ、MemMerge が1つあり、MemPhi も BulkMemPhi も残りません。ワークリストの順序が違えば、たとえば元の入力 MemMerge を平坦化前に迂回し、一時的な `y` の分割を避けられる場合もあります。
+
+このメモリ分割変換は大きくグラフに介入するため、再帰的に呼び出すことを禁止します。ループの周囲を分割することはよいのですが、その途中にあるループ先頭を分割しようとすると問題になります。分割で生まれた新しいノードをワークリストに入れ、現在の分割が完了してから訪問します。これによって、ループを巡るメモリを安全に完全分割できます。
+
+ひとまとめの Phi は複数のエイリアスを合流するため、MemMerge に対してピープホール最適化をするときに注意が必要です。MemMerge の精密な合流と、ひとまとめの Phi が重複してはいけません。
+
+これは第9章のワークリストの教訓も拡張します。ひとまとめの Phi は自分のユーザーを調べるので、ユーザーの分割が変わったら生成側を起こす必要があります。それが `addDep()` の仕事です。MemMerge からスライスを選ぶと、新しいひとまとめの Phi のユーザーが露出することがあります。その選択された定義もキューに入れなければなりません。つまり MemMerge、MemPhi、BulkMemPhi を扱うときには、非局所的な依存関係を想定する必要があります。
+
+メモリ分割でノード数が増えることもありますが、Phi(Mem、Bulk、その他)はコードを生成しません。前進は、ひとまとめの Phi からエイリアスを抽出し、一部の Load と Store を除去することで得られます。すべての書き換えにグラフの縮小を求めるのではありません。言い換えれば、MemPhi 数が増えても、Load/Store の総数は減っています。
+
+## 型
+
+型の束は、第10章の6つの領域、色、記法を維持します。`⊤` はトップ、`⊥` はボトムを意味し、接尾辞は領域を示します。ほかの5領域は変わりませんが、メモリには、メモリトップ(`TypeMem.TOP`)とメモリボトム(`TypeMem.BOT`)の間に、精密なエイリアスごとの平坦な要素 `MEM#N` が加わります。
+
+![精密なメモリエイリアスを含む型の領域](lattice.svg)
+
+並列の `MEM#2`、`MEM#3`、さらにほかのエイリアス要素は、独立したフィールドスライスを表します。上の最初の `Point` 宣言では、この2つのエイリアスは `Point.x` と `Point.y` を識別します。異なるエイリアスの meet はメモリボトム、join はメモリトップです。精密なエイリアスはそれぞれ自分自身の双対です。(省略記号は並列なエイリアスがさらにあることを示し、エイリアスの集合を表す型ではありません。)
+
+## 評価と順序付け
+
+MemMerge は依存関係のノードであり、メモリを上書きしません。コードも出力しません。評価器での結果はメモリトークンですが、Store は実際にオブジェクトのフィールドを変更します。スケジューラは、メモリをまとめることと上書きすることを区別しなければなりません。異なるエイリアス類の Store はロード/ストアの反依存を課しませんが、同じ類の Load には反依存が必要です。
+
+`int old = p.x; p.x = arg; p.y = arg; return old;` では、Load は後続の `x` の Store より前でなければならず、`y` の Store は独立したメモリ鎖を持ちます。グラフでは、局所的な畳み込み前のこのスケジューリング要件を示すため、Load を残しています。
+
+![順序付けは影響を受けるエイリアス内に適用される](example2c.svg)
+
+この章には多くのテストがあります。独立したフィールドの畳み込み、並列のメモリ Phi、複数のフィールド更新が Return で保持されること、早期 return、break/continue を伴う入れ子のループ、エイリアスする可能性のあるポインタ経由の後続ストアより前の読み出しを扱います。
+
+## 後の章に残ること
+
+第11章では、パース中にすべてのアクセスのエイリアスがわかっています。ここで遅延するのは、メモリ分割の構築です。第25章では(同じ表現を使いながら)既存のアクセスのエイリアスが*後になって*わかることも許し、さらに不完全な型、モジュールのエイリアス、分割コンパイルなど、多くの利点を可能にします。
+
+第12〜24章はこの表現を使います。第13章では大域的コード移動とロード/ストアの反依存を追加します。第15章では部分メモリと型付きエイリアス内容を伴う割り当てを追加します。第16章では初期化をコンストラクタの値に拡張し、第18章では関数呼び出しをまたいでメモリ全体を運びます。
+
+## ビルドとテスト
+
+JDK 21 以降を使い、このディレクトリで実行します。
+
+```sh
+make lib
+make tests
+make release
+make view
+```
+
+Make は Maven を使わず、この完全なスナップショットをビルドします。`make release` は `build/release/simple.jar` を書き出し、`make view` は共通のグラフビューアを起動します。ルートの Maven reactor と提供される IDEA のモジュール記述の両方に本章が含まれます。リポジトリのルートから、前後両方をテストするには次を実行します。
+
+```sh
+make tests CHAPTERS="chapter10 chapter11"
+```
+
+次の[第12章](../chapter12/README.ja.md)では、このメモリ表現を維持したまま参照フィールドと再帰構造体を追加します。
diff --git a/docs/chapter11/README.md b/docs/chapter11/README.md
new file mode 100644
index 00000000..804bcb3c
--- /dev/null
+++ b/docs/chapter11/README.md
@@ -0,0 +1,270 @@
+# Chapter 11: Splitting Memory
+
+English | [日本語](README.ja.md)
+
+[Previous: Chapter 10](../chapter10/README.md) |
+[Next: Chapter 12](../chapter12/README.md)
+
+Chapter 10 made memory effects explicit with one SSA memory value. This
+chapter keeps exactly that parser model and recovers independent memory chains
+through graph rewrites. There is no new language syntax; the
+[grammar](../chapter10/10-grammar.md), pointer types, and null checks are unchanged.
+
+## Why split memory?
+
+```java
+struct Point { int x; int y; }
+Point p = new Point;
+p.x = 42;
+while (arg > 0) {
+ p.y = p.y + arg;
+ arg = arg - 1;
+}
+return p.x;
+```
+
+The loop changes `y`, so it cannot affect `x`. With one memory chain the load
+of `x` follows the loop's stores. Splitting the fields lets ordinary
+load-after-store forwarding turn the return value into 42.
+
+![Independent field dependencies](example1.svg)
+
+The graph shows the split chains before the final Load folds. Its memory input
+goes straight to the Store of 42, bypassing the loop's `y` chain. As in
+Chapter 10, small, detached True/False projections show the source branches
+gating Loads and Stores. Other control-flow context, including Loops and their
+Phi bindings, is omitted. The graph shows dependencies, not execution order.
+
+## Equivalence-class aliasing
+
+Each struct field has an integer alias identity. Two different fields receive
+different identities, even if they have the same spelling in different structs.
+Two instances of the *same* struct share their field aliases. Thus different
+classes never alias; accesses in the same class **may alias** (same-class does
+not mean same-address). You still need to check for pointer identity.
+
+The parser assigns field identities at struct declarations, beginning at alias#2.
+
+We do not allocate a Start projection, scope binding, or loop Phi for every
+declared field or alias. Start supplies one memory value, Scope holds one
+`$mem` binding, and Return consumes all of memory: `{ctrl, $mem, result}`. As
+with Start's outputs, control is in slot 0 and memory in slot 1. The optimizer
+introduces slices only where graph operations require them.
+
+## Three memory nodes
+
+| Node | Meaning |
+|---|---|
+| `MemMerge` | A complete memory state: a default plus precise alias overrides |
+| `MemPhi` | One alias selected according to the incoming control path |
+| `BulkMemPhi` | All aliases except an explicit exclusion set, selected by control |
+
+`MemMerge` combines independent pieces of memory at one program point. It is
+not a control-flow merge. For example, after storing to alias `x`:
+
+```text
+prior = current memory
+store = Store_x(prior, pointer, value)
+current memory = MemMerge(default=prior, x=store)
+```
+
+MemMerges have a default memory in slot 1, and precise aliases in other slots
+if they differ from the default. Loads, Stores and MemPhis select their
+precise input from any incoming MemMerge, using a precise alias input if
+possible.
+
+The explicit aliases override the default: the alias slices are disjoint,
+and their union covers all memory. Flattening stacked MemMerges preserves
+entries that the outer MemMerge has not replaced.
+
+The graph below shows `p.x = arg; p.y = 42; return p;` after redundant
+initialization stores have been removed. MemMerge takes the default memory
+in slot 1 and the `x` and `y` overrides in slots 2 and 3.
+
+![An aggregate preserves both updates](example2a.svg)
+
+
+## Splitting a memory Phi
+
+The Parser initially creates a BulkMemPhi wherever the ordinary SSA algorithm needs
+to merge memory. It starts by covering all aliases and an empty exclusion set
+while the merge point is finalized. Later, peeps will split out precise memory
+slices from the bulk memory, lazily on demand.
+
+Precise single aliases can be split out from the bulk by this transformation:
+
+```text
+BulkPhi(R, left, right)
+ becomes
+MemMerge(
+ default = BulkPhi_except{x}(R, left, right),
+ x = MemPhi_x(R, slice(left,x), slice(right,x)))
+```
+
+"A BulkPhi becomes a Merge of a BulkPhi (minus the alias) and MemPhi (of the alias)".
+
+The new precise Phi and the remaining bulk Phi share a Region (omitted from the
+illustrations) and matching control-path indices. A loop uses the same
+transformation, with the backedge among those inputs. Each further split
+preserves all previously extracted slices (and peephole optimizations tidy up
+the stacked splits).
+
+Here the two branches store 3 or 4 into `p.x`, then return `p`. The graph shows
+the partition immediately after extracting `x`, before further simplification
+of the bulk Phi and its incoming aggregates.
+
+```java
+struct Point { int x; int y; }
+Point p = new Point;
+if (arg) p.x = 3;
+else p.x = 4;
+return p;
+```
+
+![Precise and bulk Phis share a control merge](example2b.svg)
+
+Continuing from this graph, here is one order in which the peepholes can fire:
+
+**Flatten the incoming MemMerges, then split `y`.** MemMerges #17 and #18
+inherit the `y` slice from #9, but their own `x` overrides win. Both now have
+Start's memory as their default, and #9 becomes unused. BulkMemPhi #21 excludes
+`x`, but still covers `y`, so it cannot simply bypass these MemMerges. It
+extracts MemPhi #24 for `y` and creates BulkMemPhi #25, excluding both fields.
+The extra MemMerge #26 combines these slices.
+
+![After flattening the incoming aggregates and extracting y](example2b-split-y.svg)
+
+**Fold the unchanged memory.** Both inputs of MemPhi #24 are the same
+initializing Store #8, so it becomes that Store. BulkMemPhi #25 can now look
+through #17 and #18: their explicit aliases are all excluded from its domain.
+Both remaining inputs become Start's memory, so this bulk Phi also folds.
+Flattening the outgoing aggregates leaves MemMerge #22 with default memory
+from Start, `x` from MemPhi #20, and `y` from Store #8.
+
+![Only the x memory Phi still needs the branch merge](example2b-merged.svg)
+
+**Factor the Stores, then remove the overwritten initialization.** With the
+bulk Phi and the incoming aggregates gone, Stores #14 and #16 are used only
+by MemPhi #20. They store to the same pointer and alias, and share the same
+incoming memory. `PhiNode` can pull the Store below the merge:
+
+```text
+MemPhi_x(Store_x(mem, p, 3), Store_x(mem, p, 4))
+ becomes
+Store_x(mem, p, Phi(3, 4))
+```
+
+In general, Phi refactoring can combine Loads or Stores as in Chapter 10 (pushing
+up or pulling down). The Phi new memory operands can use precise MemPhis for
+the alias. This peep requires precise aliases, and is otherwise blocked by
+BulkMems - and thus is a trigger for splitting a BulkMem.
+
+
+Continuing with the example, the new value Phi #28 chooses 3 on the true arm
+and 4 on the false arm. Store
+#27 replaces both branch Stores and their memory Phi. Now the initial
+`p.x = 0` Store #7 has only that one Store as a user, so store-after-store
+elimination bypasses it. The `p.y = 0` Store #8 remains: we return the object,
+and its `y` field still needs initialization.
+
+![Final graph: one x Store of a value Phi, and the y initialization](example2b-final.svg)
+
+Thus the final graph has two Stores, one ordinary value Phi, and one MemMerge;
+neither a MemPhi nor a BulkMemPhi remains. Other worklists order can, for
+instance, bypass the original incoming MemMerges before flattening them,
+avoiding the temporary `y` split.
+
+This memory splitting transform is invasive, and is disallowed from being
+called recursively; you can end up splitting around a loop (which is good) but
+then trying to split a mid-split loop head (which is bad). The new nodes from
+the split are put on the worklist and visited after the current split
+completes - safely allowing memory to be completely sliced around loops.
+
+Since bulk Phis merge a bunch of aliases, they have to take care when
+peepholing vs MemMerges: there cannot be any overlap between the MemMerge
+precise merges and the bulk Phi.
+
+This also extends the worklist lesson from Chapter 9: a bulk Phi inspects its
+users, so changes to a user's partition must wake the producer - job for
+`addDep()`. Selecting a slice from a MemMerge can expose a new bulk-Phi
+user; that selected definition must be queued too. In short, any time we're
+working with MemMerge, MemPhi or BulkMemPhi we can expect some non-local
+dependencies.
+
+Memory splitting can increase node count - but Phis (Mem, Bulk or otherwise)
+make no code. Progress comes from extracting an alias from a bulk Phi and
+removing some Loads and Stores, rather than from requiring every rewrite
+to shrink the graph. In other words, the total Load/Store count is going down
+even if the MemPhi count is going up.
+
+
+## Types
+
+The type lattice retains Chapter 10's six domains, colors, and notation:
+`⊤` means top, `⊥` means bottom, and a suffix names the domain. The other five
+domains are unchanged; however memory now adds a flat element `MEM#N` for each precise
+alias between memory top (`TypeMem.TOP`) and memory bottom (`TypeMem.BOT`).
+
+![The type domains, including precise memory aliases](lattice.svg)
+
+The parallel `MEM#2`, `MEM#3`, and further alias elements describe independent
+field slices. For the first `Point` declaration above, these two aliases identify
+`Point.x` and `Point.y`. Meeting different aliases gives memory bottom; joining
+them gives memory top. Each precise alias is its own dual. (And the ellipsis stands
+for more parallel aliases, not a type representing a set of aliases).
+
+
+## Evaluation and ordering
+
+MemMerge is a dependency node and not a clobber; it emits no code. Its
+evaluator result is a memory token, while Store actually mutates the object's
+fields. The scheduler must distinguish merging memory from overwriting memory.
+Stores in different alias classes do not impose load/store anti-dependencies,
+but Loads in the same class require anti-dependencies.
+
+In `int old = p.x; p.x = arg; p.y = arg; return old;`, the Load must precede
+the later `x` Store; the `y` Store has an independent memory chain. The graph
+retains the Load to show this scheduling requirement before local folding.
+
+![Ordering applies within the affected alias](example2c.svg)
+
+There are a lot of tests in this chapter, to cover independent-field folding,
+parallel memory Phis, multiple field updates surviving at Return, early
+returns, nested loops with break/continue, and reads before subsequent stores
+through potentially aliased pointers.
+
+## What remains for later chapters?
+
+Chapter 11 knows every access's alias while parsing. What is lazy here is the
+construction of memory partitions. Chapter 25 also allows an existing access's
+alias to become known *later* (using the same representation) and will allow
+more incomplete types, module aliases, seperate compilation and a host of other
+benefits.
+
+Chapters 12-24 use this representation. Chapter 13 adds global code motion
+and load/store anti-dependencies. Chapter 15 adds allocation with partial memory and typed
+alias contents. Chapter 16 extends initialization to constructor values, and
+Chapter 18 carries whole memory across function calls.
+
+## Build and test
+
+With JDK 21 or newer, from this directory:
+
+```sh
+make lib
+make tests
+make release
+make view
+```
+
+Make builds this complete snapshot without Maven. `make release` writes
+`build/release/simple.jar`; `make view` launches the shared graph viewer.
+Both the root Maven reactor and the supplied IDEA module descriptor include
+the chapter. From the repository root, test both halves with:
+
+```sh
+make tests CHAPTERS="chapter10 chapter11"
+```
+
+Next, [Chapter 12](../chapter12/README.md) adds reference fields and recursive
+structs while retaining this memory representation.
diff --git a/docs/chapter11/example1.gv b/docs/chapter11/example1.gv
new file mode 100644
index 00000000..5e059d77
--- /dev/null
+++ b/docs/chapter11/example1.gv
@@ -0,0 +1,20 @@
+digraph Memory {
+ // IR styles match graph/web/render.js; arrows are use -> definition.
+ graph [bgcolor="#fafbfc" fontname="Helvetica" nodesep=0.35 ranksep=0.55 splines=ortho];
+ node [fontname="Helvetica" fontsize=12 color="#758191" fontcolor="#152330" shape=box style="rounded,filled" fillcolor="#edf3f7"];
+ edge [fontname="Helvetica" fontsize=9 color="#536477" fontcolor="#364352" arrowsize=0.7 penwidth=1.5];
+
+ rankdir=BT;
+
+ M [label="Initial memory" shape=octagon style="filled" fillcolor="#dcecff"];
+ X [label="Store x = 42" shape=octagon style="filled" fillcolor="#dcecff"];
+ Y [label="Loop: Store y" shape=octagon style="filled" fillcolor="#dcecff"];
+ L [label="Load x (42)" shape=box style="rounded,filled" fillcolor="#edf3f7"];
+ L -> X -> M [color="#2867b2"]; Y -> M [color="#2867b2"];
+ MemMerge -> X [color="#2867b2"]; MemMerge -> Y [color="#2867b2"];
+ Return -> MemMerge [color="#2867b2"]; Return -> L;
+ { rank=same; MemMerge -> L [style=invis]; }
+ Y -> X [style=invis]; // Layout only: keep the stores in code order.
+ Return [shape=box style="filled" fillcolor="#fff1c4"];
+ MemMerge [shape=octagon style="filled" fillcolor="#dcecff"];
+}
diff --git a/docs/chapter11/example1.json b/docs/chapter11/example1.json
new file mode 100644
index 00000000..c0c28450
--- /dev/null
+++ b/docs/chapter11/example1.json
@@ -0,0 +1,31 @@
+{
+ "ver": 1,
+ "comp": "chapter11/docs/example1.svg",
+ "step": 1,
+ "roots": [24],
+ "scope": 0,
+ "nodes": [
+ {"id": 1, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null},
+ {"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": 0, "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": "#42", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null},
+ {"id": 8, "label": "Store p.x", "type": "MEM#2", "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": 7, "role": "DATA", "label": null}], "proj": null},
+ {"id": 9, "label": "Store p.y", "type": "MEM#3", "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": 11, "label": "MemPhi y", "type": "MEM#3", "kind": "PHI", "edges": [{"idx": 0, "def": 0, "role": "ASSOC", "label": null}, {"idx": 1, "def": 9, "role": "MEM", "label": null}, {"idx": 2, "def": 19, "role": "MEM", "label": null}], "proj": null},
+ {"id": 12, "label": "Phi arg", "type": null, "kind": "PHI", "edges": [{"idx": 0, "def": 0, "role": "ASSOC", "label": null}, {"idx": 1, "def": 4, "role": "DATA", "label": null}, {"idx": 2, "def": 21, "role": "DATA", "label": null}], "proj": null},
+ {"id": 15, "label": "True", "type": null, "kind": "CTRL", "edges": [], "proj": {"par": 0, "idx": 0}},
+ {"id": 16, "label": "False", "type": null, "kind": "CTRL", "edges": [], "proj": {"par": 0, "idx": 1}},
+ {"id": 17, "label": "Load p.y", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 15, "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": 18, "label": "+", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 17, "role": "DATA", "label": null}, {"idx": 2, "def": 12, "role": "DATA", "label": null}], "proj": null},
+ {"id": 19, "label": "Store p.y", "type": "MEM#3", "kind": "MEM", "edges": [{"idx": 0, "def": 15, "role": "CTRL", "label": null}, {"idx": 1, "def": 11, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 18, "role": "DATA", "label": null}], "proj": null},
+ {"id": 20, "label": "#1", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null},
+ {"id": 21, "label": "-", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 12, "role": "DATA", "label": null}, {"idx": 2, "def": 20, "role": "DATA", "label": null}], "proj": null},
+ {"id": 22, "label": "MemMerge", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 8, "role": "MEM", "label": null}, {"idx": 3, "def": 11, "role": "MEM", "label": null}], "proj": null},
+ {"id": 23, "label": "Load p.x", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 16, "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": 24, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 22, "role": "MEM", "label": null}, {"idx": 2, "def": 23, "role": "DATA", "label": null}], "proj": null}
+ ],
+ "groups": [
+ ]
+}
diff --git a/docs/chapter11/example1.svg b/docs/chapter11/example1.svg
new file mode 100644
index 00000000..46faf818
--- /dev/null
+++ b/docs/chapter11/example1.svg
@@ -0,0 +1,155 @@
+<svg aria-label="Compiler graph" width="929" height="674" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 929 674" 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 ASSOC" id="e6i0" d="M435,46 C435,70 682,41 682,65" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#6[0] → #1 · ASSOC</title></path><path class="edge ASSOC" id="e7i0" d="M435,46 C435,70 100,41 100,65" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#7[0] → #1 · ASSOC</title></path><path class="edge MEM" id="e8i1" d="M252,118 L252,168" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#8[1] → #3 · MEM</title></path><path class="edge DATA" id="e8i2" d="M530,118 L530,140 Q530,148 522,148 L284,148 Q276,148 276,156 L276,168" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#8[2] → #5 · DATA</title></path><path class="edge DATA" id="e8i3" d="M100,118 L100,150 Q100,158 108,158 L295,158 Q300,158 300,163 L300,168" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#8[3] → #7 · DATA</title></path><path class="edge MEM" id="e9i1" d="M252,118 L252,130 Q252,138 260,138 L385,138 Q393,138 393,146 L393,168" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#9[1] → #3 · MEM</title></path><path class="edge DATA" id="e9i2" d="M530,118 L530,140 Q530,148 522,148 L425,148 Q417,148 417,156 L417,168" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#9[2] → #5 · DATA</title></path><path class="edge DATA" id="e9i3" d="M682,118 L682,150 Q682,158 674,158 L446,158 Q441,158 441,163 L441,168" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#9[3] → #6 · DATA</title></path><path class="edge MEM" id="e11i1" d="M405,224 L405,272" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#11[1] → #9 · MEM</title></path><path class="edge MEM" id="e11i2" d="M777,640 L777,645 Q777,650 772,650 L32,650 Q24,650 24,642 L24,242 Q24,234 32,234 L437,234 Q445,234 445,242 L445,272" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#11[2] → #19 · MEM</title></path><path class="edge DATA" id="e12i1" d="M372,118 L372,123 Q372,128 377,128 L684,128 Q692,128 692,136 L692,168" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#12[1] → #4 · DATA</title></path><path class="edge DATA" id="e12i2" d="M620,328 L620,333 Q620,338 625,338 L682,338 Q690,338 690,330 L690,252 Q690,244 698,244 L774,244 Q782,244 782,236 L782,166 Q782,158 774,158 L737,158 Q732,158 732,163 L732,168" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#12[2] → #21 · DATA</title></path><path class="edge CTRL" id="e17i0" d="M741,328 L741,340 Q741,348 749,348 L790,348 Q798,348 798,356 L798,376" stroke="#b63838" marker-start="url(#arrow-CTRL)" style="opacity: 1;"><title>#17[0] → #15 · CTRL</title></path><path class="edge MEM" id="e17i1" d="M425,328 L425,350 Q425,358 433,358 L814,358 Q822,358 822,366 L822,376" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#17[1] → #11 · MEM</title></path><path class="edge DATA" id="e17i2" d="M530,118 L530,140 Q530,148 538,148 L784,148 Q792,148 792,156 L792,330 Q792,338 800,338 L838,338 Q846,338 846,346 L846,376" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#17[2] → #5 · DATA</title></path><path class="edge DATA" id="e18i1" d="M834,432 L834,480" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#18[1] → #17 · DATA</title></path><path class="edge DATA" id="e18i2" d="M712,224 L712,229 Q712,234 717,234 L896,234 Q904,234 904,242 L904,434 Q904,442 896,442 L872,442 Q864,442 864,450 L864,480" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#18[2] → #12 · DATA</title></path><path class="edge CTRL" id="e19i0" d="M741,328 L741,584" stroke="#b63838" marker-start="url(#arrow-CTRL)" style="opacity: 1;"><title>#19[0] → #15 · CTRL</title></path><path class="edge MEM" id="e19i1" d="M425,328 L425,350 Q425,358 433,358 L744,358 Q752,358 752,366 L752,549.5 Q752,556 758.5,556 L758.5,556 Q765,556 765,562.5 L765,584" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#19[1] → #11 · MEM</title></path><path class="edge DATA" id="e19i2" d="M530,118 L530,140 Q530,148 538,148 L784,148 Q792,148 792,156 L792,330 Q792,338 784,338 L771,338 Q763,338 763,346 L763,538 Q763,546 771,546 L781,546 Q789,546 789,554 L789,584" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#19[2] → #5 · DATA</title></path><path class="edge DATA" id="e19i3" d="M834,536 L834,541 Q834,546 829,546 L821,546 Q813,546 813,554 L813,584" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#19[3] → #18 · DATA</title></path><path class="edge ASSOC" id="e20i0" d="M435,46 C435,70 557,147 557,171" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#20[0] → #1 · ASSOC</title></path><path class="edge DATA" id="e21i1" d="M712,224 L712,229 Q712,234 707,234 L628,234 Q620,234 620,242 L620,272" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#21[1] → #12 · DATA</title></path><path class="edge DATA" id="e21i2" d="M557,224 L557,236 Q557,244 565,244 L642,244 Q650,244 650,252 L650,272" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#21[2] → #20 · DATA</title></path><path class="edge MEM" id="e22i1" d="M252,118 L252,130 Q252,138 244,138 L82,138 Q74,138 74,146 L74,333.5 Q74,338 78.5,338 L78.5,338 Q83,338 83,342.5 L83,376" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#22[1] → #3 · MEM</title></path><path class="edge MEM" id="e22i2" d="M264,224 L264,236 Q264,244 256,244 L102,244 Q94,244 94,252 L94,331.5 Q94,338 100.5,338 L100.5,338 Q107,338 107,344.5 L107,376" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#22[2] → #8 · MEM</title></path><path class="edge MEM" id="e22i3" d="M425,328 L425,350 Q425,358 417,358 L139,358 Q131,358 131,366 L131,376" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#22[3] → #11 · MEM</title></path><path class="edge CTRL" id="e23i0" d="M129,224 L129,272" stroke="#b63838" marker-start="url(#arrow-CTRL)" style="opacity: 1;"><title>#23[0] → #16 · CTRL</title></path><path class="edge MEM" id="e23i1" d="M264,224 L264,236 Q264,244 256,244 L161,244 Q153,244 153,252 L153,272" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#23[1] → #8 · MEM</title></path><path class="edge DATA" id="e23i2" d="M530,118 L530,140 Q530,148 522,148 L342,148 Q334,148 334,156 L334,246 Q334,254 326,254 L185,254 Q177,254 177,262 L177,272" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#23[2] → #5 · DATA</title></path><path class="edge MEM" id="e24i1" d="M95,432 L95,437 Q95,442 100,442 L127,442 Q135,442 135,450 L135,480" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#24[1] → #22 · MEM</title></path><path class="edge DATA" id="e24i2" d="M165,328 L165,480" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#24[2] → #23 · 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(192,24)" style="opacity: 1;"><path class="box" d="M0,0H240V44H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="120" y="18"><tspan>Start</tspan><tspan class="node-id">  #1</tspan></text><text class="type" text-anchor="middle" y="35" x="120"/><title>#1 Start
+Type not computed
+
+</title></g><g class="node" id="n3" data-id="3" transform="translate(192,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(312,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(470,68)" 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] → — CTRL</title><circle class="port" cx="60" cy="-3" r="3" fill="white"/><text class="slot" x="60" y="-10" text-anchor="middle">0</text></g><g class="node" id="n6" data-id="6" transform="translate(622,68)" 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(40,68)" 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>#42</tspan><tspan class="node-id">  #7</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#7 #42
+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="n8" data-id="8" transform="translate(204,174)" 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">  #8</tspan></text><text class="type" text-anchor="middle" y="35" x="60">MEM#2</text><title>#8 Store p.x
+MEM#2
+
+[0] → — CTRL
+[1] → #3 MEM
+[2] → #5 DATA
+[3] → #7 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(345,174)" 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">  #9</tspan></text><text class="type" text-anchor="middle" y="35" x="60">MEM#3</text><title>#9 Store p.y
+MEM#3
+
+[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="n11" data-id="11" transform="translate(365,278)" 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>MemPhi y</tspan><tspan class="node-id">  #11</tspan></text><text class="type" text-anchor="middle" y="35" x="60">MEM#3</text><title>#11 MemPhi y
+MEM#3
+
+[0] → — ASSOC
+[1] → #9 MEM
+[2] → #19 MEM</title><circle class="port" cx="0" cy="-3" r="3" fill="white"/><circle class="port" cx="40" cy="-3" r="3" fill="#536477"/><circle class="port" cx="80" cy="-3" r="3" fill="#536477"/><text class="slot" x="0" y="-10" 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(652,174)" 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">  #12</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#12 Phi arg
+Type not computed
+
+[0] → — ASSOC
+[1] → #4 DATA
+[2] → #21 DATA</title><circle class="port" cx="0" cy="-3" r="3" fill="white"/><circle class="port" cx="40" cy="-3" r="3" fill="#536477"/><circle class="port" cx="80" cy="-3" r="3" fill="#536477"/><text class="slot" x="0" y="-10" 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="n15" data-id="15" transform="translate(700,296)" style="opacity: 1;"><path class="box" d="M0,0H82V26H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="41" y="17"><tspan>True</tspan><tspan class="node-id">  #15/0</tspan></text><text class="type" text-anchor="middle" y="35" x="41"/><title>#15 True
+Projection 0 of #0
+Type not computed
+
+</title></g><g class="node" id="n16" data-id="16" transform="translate(85,192)" style="opacity: 1;"><path class="box" d="M0,0H87V26H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="43.5" y="17"><tspan>False</tspan><tspan class="node-id">  #16/1</tspan></text><text class="type" text-anchor="middle" y="35" x="43.5"/><title>#16 False
+Projection 1 of #0
+Type not computed
+
+</title></g><g class="node" id="n17" data-id="17" transform="translate(774,382)" 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.y</tspan><tspan class="node-id">  #17</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#17 Load p.y
+Type not computed
+
+[0] → #15 CTRL
+[1] → #11 MEM
+[2] → #5 DATA
+[3] → — DATA</title><circle class="port" cx="24" cy="-3" r="3" fill="#536477"/><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="n18" data-id="18" transform="translate(774,486)" 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">  #18</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#18 +
+Type not computed
+
+[0] → — CTRL
+[1] → #17 DATA
+[2] → #12 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="n19" data-id="19" transform="translate(717,590)" 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">  #19</tspan></text><text class="type" text-anchor="middle" y="35" x="60">MEM#3</text><title>#19 Store p.y
+MEM#3
+
+[0] → #15 CTRL
+[1] → #11 MEM
+[2] → #5 DATA
+[3] → #18 DATA</title><circle class="port" cx="24" cy="-3" r="3" fill="#536477"/><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="n20" data-id="20" transform="translate(497,174)" 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">  #20</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#20 #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="n21" data-id="21" transform="translate(560,278)" 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">  #21</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#21 -
+Type not computed
+
+[0] → — CTRL
+[1] → #12 DATA
+[2] → #20 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="n22" data-id="22" transform="translate(35,382)" 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>MemMerge</tspan><tspan class="node-id">  #22</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#22 MemMerge
+Type not computed
+
+[0] → — CTRL
+[1] → #3 MEM
+[2] → #8 MEM
+[3] → #11 MEM</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="n23" data-id="23" transform="translate(105,278)" 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">  #23</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#23 Load p.x
+Type not computed
+
+[0] → #16 CTRL
+[1] → #8 MEM
+[2] → #5 DATA
+[3] → — DATA</title><circle class="port" cx="24" cy="-3" r="3" fill="#536477"/><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="n24" data-id="24" transform="translate(75,486)" 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">  #24</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#24 Return
+Type not computed
+
+[0] → — CTRL
+[1] → #22 MEM
+[2] → #23 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><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/docs/chapter11/example2a.gv b/docs/chapter11/example2a.gv
new file mode 100644
index 00000000..187e63b9
--- /dev/null
+++ b/docs/chapter11/example2a.gv
@@ -0,0 +1,17 @@
+digraph Memory {
+ // IR styles match graph/web/render.js; arrows are use -> definition.
+ graph [bgcolor="#fafbfc" fontname="Helvetica" nodesep=0.35 ranksep=0.55 splines=ortho];
+ node [fontname="Helvetica" fontsize=12 color="#758191" fontcolor="#152330" shape=box style="rounded,filled" fillcolor="#edf3f7"];
+ edge [fontname="Helvetica" fontsize=9 color="#536477" fontcolor="#364352" arrowsize=0.7 penwidth=1.5];
+
+ rankdir=RL;
+
+ M [label="Default memory" shape=octagon style="filled" fillcolor="#dcecff"];
+ X [label="Store x" shape=octagon style="filled" fillcolor="#dcecff"];
+ Y [label="Store y" shape=octagon style="filled" fillcolor="#dcecff"];
+ X -> M [color="#2867b2"]; Y -> M [color="#2867b2"];
+ MemMerge -> M [xlabel="default",color="#2867b2"]; MemMerge -> X [xlabel="x",color="#2867b2"];
+ MemMerge -> Y [xlabel="y",color="#2867b2"]; Return -> MemMerge [color="#2867b2"];
+ Return [shape=box style="filled" fillcolor="#fff1c4"];
+ MemMerge [shape=octagon style="filled" fillcolor="#dcecff"];
+}
diff --git a/docs/chapter11/example2a.json b/docs/chapter11/example2a.json
new file mode 100644
index 00000000..b9e84d5a
--- /dev/null
+++ b/docs/chapter11/example2a.json
@@ -0,0 +1,20 @@
+{
+ "ver": 1,
+ "comp": "chapter11/docs/example2a.svg",
+ "step": 1,
+ "roots": [11],
+ "scope": 0,
+ "nodes": [
+ {"id": 1, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null},
+ {"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": 0, "role": "CTRL", "label": null}], "proj": null},
+ {"id": 7, "label": "Store p.x", "type": "MEM#2", "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": 4, "role": "DATA", "label": null}], "proj": null},
+ {"id": 8, "label": "#42", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null},
+ {"id": 9, "label": "Store p.y", "type": "MEM#3", "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": 8, "role": "DATA", "label": null}], "proj": null},
+ {"id": 10, "label": "MemMerge", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 7, "role": "MEM", "label": null}, {"idx": 3, "def": 9, "role": "MEM", "label": null}], "proj": null},
+ {"id": 11, "label": "Return", "type": null, "kind": "CTRL", "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}], "proj": null}
+ ],
+ "groups": [
+ ]
+}
diff --git a/docs/chapter11/example2a.svg b/docs/chapter11/example2a.svg
new file mode 100644
index 00000000..b43a55b4
--- /dev/null
+++ b/docs/chapter11/example2a.svg
@@ -0,0 +1,103 @@
+<svg aria-label="Compiler graph" width="598" height="454" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 598 454" 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 MEM" id="e7i1" d="M84,118 L84,140 Q84,148 92,148 L135,148 Q143,148 143,156 L143,166" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#7[1] → #3 · MEM</title></path><path class="edge DATA" id="e7i2" d="M362,118 L362,123 Q362,128 357,128 L175,128 Q167,128 167,136 L167,166" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#7[2] → #5 · DATA</title></path><path class="edge DATA" id="e7i3" d="M204,118 L204,131.5 Q204,138 197.5,138 L197.5,138 Q191,138 191,144.5 L191,166" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#7[3] → #4 · DATA</title></path><path class="edge ASSOC" id="e8i0" d="M267,46 C267,70 514,41 514,65" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#8[0] → #1 · ASSOC</title></path><path class="edge MEM" id="e9i1" d="M84,118 L84,140 Q84,148 92,148 L287,148 Q295,148 295,156 L295,166" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#9[1] → #3 · MEM</title></path><path class="edge DATA" id="e9i2" d="M362,118 L362,123 Q362,128 357,128 L327,128 Q319,128 319,136 L319,166" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#9[2] → #5 · DATA</title></path><path class="edge DATA" id="e9i3" d="M514,118 L514,130 Q514,138 506,138 L351,138 Q343,138 343,146 L343,166" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#9[3] → #8 · DATA</title></path><path class="edge MEM" id="e10i1" d="M84,118 L84,224 Q84,232 92,232 L123,232 Q131,232 131,240 L131,270" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#10[1] → #3 · MEM</title></path><path class="edge MEM" id="e10i2" d="M155,222 L155,270" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#10[2] → #7 · MEM</title></path><path class="edge MEM" id="e10i3" d="M307,222 L307,227 Q307,232 302,232 L187,232 Q179,232 179,240 L179,270" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#10[3] → #9 · MEM</title></path><path class="edge MEM" id="e11i1" d="M143,326 L143,331 Q143,336 148,336 L339,336 Q347,336 347,344 L347,374" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#11[1] → #10 · MEM</title></path><path class="edge DATA" id="e11i2" d="M362,118 L362,123 Q362,128 367,128 L369.5,128 Q377,128 377,135.5 L377,374" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#11[2] → #5 · 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,0H240V44H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="120" y="18"><tspan>Start</tspan><tspan class="node-id">  #1</tspan></text><text class="type" text-anchor="middle" y="35" x="120"/><title>#1 Start
+Type not computed
+
+</title></g><g class="node" id="n3" data-id="3" transform="translate(24,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(144,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(302,68)" 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] → — CTRL</title><circle class="port" cx="60" cy="-3" r="3" fill="white"/><text class="slot" x="60" y="-10" text-anchor="middle">0</text></g><g class="node" id="n7" data-id="7" transform="translate(95,172)" 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">MEM#2</text><title>#7 Store p.x
+MEM#2
+
+[0] → — CTRL
+[1] → #3 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="n8" data-id="8" transform="translate(454,68)" 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>#42</tspan><tspan class="node-id">  #8</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#8 #42
+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="n9" data-id="9" transform="translate(247,172)" 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">  #9</tspan></text><text class="type" text-anchor="middle" y="35" x="60">MEM#3</text><title>#9 Store p.y
+MEM#3
+
+[0] → — CTRL
+[1] → #3 MEM
+[2] → #5 DATA
+[3] → #8 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="n10" data-id="10" transform="translate(83,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>MemMerge</tspan><tspan class="node-id">  #10</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#10 MemMerge
+Type not computed
+
+[0] → — CTRL
+[1] → #3 MEM
+[2] → #7 MEM
+[3] → #9 MEM</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(287,380)" 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] → — CTRL
+[1] → #10 MEM
+[2] → #5 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><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/docs/chapter11/example2b-final.json b/docs/chapter11/example2b-final.json
new file mode 100644
index 00000000..b0a7a2f3
--- /dev/null
+++ b/docs/chapter11/example2b-final.json
@@ -0,0 +1,251 @@
+{
+ "ver": 1,
+ "comp": "chapter11/docs/example2b-final.svg",
+ "step": 100,
+ "roots": [
+ 23
+ ],
+ "scope": 0,
+ "nodes": [
+ {
+ "id": 1,
+ "label": "Start",
+ "type": null,
+ "kind": "CTRL",
+ "edges": [],
+ "proj": null
+ },
+ {
+ "id": 3,
+ "label": "$mem",
+ "type": null,
+ "kind": "MEM",
+ "edges": [
+ {
+ "idx": 0,
+ "def": 1,
+ "role": "MEM",
+ "label": null
+ }
+ ],
+ "proj": {
+ "par": 1,
+ "idx": 1
+ }
+ },
+ {
+ "id": 5,
+ "label": "new Point",
+ "type": null,
+ "kind": "DATA",
+ "edges": [
+ {
+ "idx": 0,
+ "def": 0,
+ "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": 8,
+ "label": "Store p.y",
+ "type": "MEM#3",
+ "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": 13,
+ "label": "#3",
+ "type": null,
+ "kind": "DATA",
+ "edges": [
+ {
+ "idx": 0,
+ "def": 1,
+ "role": "ASSOC",
+ "label": null
+ }
+ ],
+ "proj": null
+ },
+ {
+ "id": 15,
+ "label": "#4",
+ "type": null,
+ "kind": "DATA",
+ "edges": [
+ {
+ "idx": 0,
+ "def": 1,
+ "role": "ASSOC",
+ "label": null
+ }
+ ],
+ "proj": null
+ },
+ {
+ "id": 22,
+ "label": "MemMerge",
+ "type": null,
+ "kind": "MEM",
+ "edges": [
+ {
+ "idx": 0,
+ "def": 0,
+ "role": "CTRL",
+ "label": null
+ },
+ {
+ "idx": 1,
+ "def": 3,
+ "role": "MEM",
+ "label": null
+ },
+ {
+ "idx": 2,
+ "def": 27,
+ "role": "MEM",
+ "label": null
+ },
+ {
+ "idx": 3,
+ "def": 8,
+ "role": "MEM",
+ "label": null
+ }
+ ],
+ "proj": null
+ },
+ {
+ "id": 23,
+ "label": "Return",
+ "type": null,
+ "kind": "CTRL",
+ "edges": [
+ {
+ "idx": 0,
+ "def": 0,
+ "role": "CTRL",
+ "label": null
+ },
+ {
+ "idx": 1,
+ "def": 22,
+ "role": "MEM",
+ "label": null
+ },
+ {
+ "idx": 2,
+ "def": 5,
+ "role": "DATA",
+ "label": null
+ }
+ ],
+ "proj": null
+ },
+ {
+ "id": 27,
+ "label": "Store p.x",
+ "type": "MEM#2",
+ "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": 28,
+ "role": "DATA",
+ "label": null
+ }
+ ],
+ "proj": null
+ },
+ {
+ "id": 28,
+ "label": "Phi x value",
+ "type": null,
+ "kind": "PHI",
+ "edges": [
+ {
+ "idx": 0,
+ "def": 0,
+ "role": "ASSOC",
+ "label": null
+ },
+ {
+ "idx": 1,
+ "def": 13,
+ "role": "DATA",
+ "label": null
+ },
+ {
+ "idx": 2,
+ "def": 15,
+ "role": "DATA",
+ "label": null
+ }
+ ],
+ "proj": null
+ }
+ ],
+ "groups": []
+}
diff --git a/docs/chapter11/example2b-final.svg b/docs/chapter11/example2b-final.svg
new file mode 100644
index 00000000..163d7fce
--- /dev/null
+++ b/docs/chapter11/example2b-final.svg
@@ -0,0 +1,110 @@
+<svg aria-label="Compiler graph" width="633" height="558" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 633 558" 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 ASSOC" id="e6i0" d="M147,150 C147,174 549,145 549,169" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#6[0] → #1 · ASSOC</title></path><path class="edge MEM" id="e8i1" d="M84,222 L84,244 Q84,252 92,252 L276,252 Q284,252 284,260 L284,270" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#8[1] → #3 · MEM</title></path><path class="edge DATA" id="e8i2" d="M242,222 L242,234 Q242,242 250,242 L300,242 Q308,242 308,250 L308,270" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#8[2] → #5 · DATA</title></path><path class="edge DATA" id="e8i3" d="M549,222 L549,234 Q549,242 541,242 L340,242 Q332,242 332,250 L332,270" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#8[3] → #6 · DATA</title></path><path class="edge ASSOC" id="e13i0" d="M147,150 C147,174 265,3 265,27" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#13[0] → #1 · ASSOC</title></path><path class="edge ASSOC" id="e15i0" d="M147,150 C147,174 417,3 417,27" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#15[0] → #1 · ASSOC</title></path><path class="edge MEM" id="e22i1" d="M84,222 L84,328 Q84,336 92,336 L123,336 Q131,336 131,344 L131,374" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#22[1] → #3 · MEM</title></path><path class="edge MEM" id="e22i2" d="M155,326 L155,374" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#22[2] → #27 · MEM</title></path><path class="edge MEM" id="e22i3" d="M296,326 L296,331 Q296,336 291,336 L187,336 Q179,336 179,344 L179,374" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#22[3] → #8 · MEM</title></path><path class="edge MEM" id="e23i1" d="M143,430 L143,435 Q143,440 148,440 L187,440 Q195,440 195,448 L195,478" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#23[1] → #22 · MEM</title></path><path class="edge DATA" id="e23i2" d="M242,222 L242,234 Q242,242 234,242 L233,242 Q225,242 225,250 L225,478" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#23[2] → #5 · DATA</title></path><path class="edge MEM" id="e27i1" d="M84,222 L84,244 Q84,252 92,252 L135,252 Q143,252 143,260 L143,270" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#27[1] → #3 · MEM</title></path><path class="edge DATA" id="e27i2" d="M242,222 L242,234 Q242,242 234,242 L175,242 Q167,242 167,250 L167,270" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#27[2] → #5 · DATA</title></path><path class="edge DATA" id="e27i3" d="M397,196.66666666666666 L397,224 Q397,232 389,232 L199,232 Q191,232 191,240 L191,270" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#27[3] → #28 · DATA</title></path><path class="edge DATA" id="e28i1" d="M265,80 L265,85 Q265,90 270,90 L369,90 Q377,90 377,98 L377,140.66666666666666" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#28[1] → #13 · DATA</title></path><path class="edge DATA" id="e28i2" d="M417,80 L417,140.66666666666666" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#28[2] → #15 · 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,128)" style="opacity: 1;"><path class="box" d="M0,0H120V44H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>Start</tspan><tspan class="node-id">  #1</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#1 Start
+Type not computed
+
+</title></g><g class="node" id="n3" data-id="3" transform="translate(24,172)" 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="n5" data-id="5" transform="translate(182,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] → — CTRL</title><circle class="port" cx="60" cy="-3" r="3" fill="white"/><text class="slot" x="60" y="-10" text-anchor="middle">0</text></g><g class="node" id="n6" data-id="6" transform="translate(489,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="n8" data-id="8" transform="translate(236,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.y</tspan><tspan class="node-id">  #8</tspan></text><text class="type" text-anchor="middle" y="35" x="60">MEM#3</text><title>#8 Store p.y
+MEM#3
+
+[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="n13" data-id="13" transform="translate(205,30)" 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">  #13</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#13 #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="n15" data-id="15" transform="translate(357,30)" 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">  #15</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#15 #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="n22" data-id="22" transform="translate(83,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>MemMerge</tspan><tspan class="node-id">  #22</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#22 MemMerge
+Type not computed
+
+[0] → — CTRL
+[1] → #3 MEM
+[2] → #27 MEM
+[3] → #8 MEM</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="n23" data-id="23" transform="translate(135,484)" 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">  #23</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#23 Return
+Type not computed
+
+[0] → — CTRL
+[1] → #22 MEM
+[2] → #5 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="n27" data-id="27" 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">  #27</tspan></text><text class="type" text-anchor="middle" y="35" x="60">MEM#2</text><title>#27 Store p.x
+MEM#2
+
+[0] → — CTRL
+[1] → #3 MEM
+[2] → #5 DATA
+[3] → #28 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="n28" data-id="28" transform="translate(337,146.66666666666666)" 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 x value</tspan><tspan class="node-id">  #28</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#28 Phi x value
+Type not computed
+
+[0] → — ASSOC
+[1] → #13 DATA
+[2] → #15 DATA</title><circle class="port" cx="0" cy="-3" r="3" fill="white"/><circle class="port" cx="40" cy="-3" r="3" fill="#536477"/><circle class="port" cx="80" cy="-3" r="3" fill="#536477"/><text class="slot" x="0" y="-10" 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><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/docs/chapter11/example2b-merged.json b/docs/chapter11/example2b-merged.json
new file mode 100644
index 00000000..914b7cfb
--- /dev/null
+++ b/docs/chapter11/example2b-merged.json
@@ -0,0 +1,339 @@
+{
+ "ver": 1,
+ "comp": "chapter11/docs/example2b-merged.svg",
+ "step": 8,
+ "roots": [
+ 23
+ ],
+ "scope": 0,
+ "nodes": [
+ {
+ "id": 1,
+ "label": "Start",
+ "type": null,
+ "kind": "CTRL",
+ "edges": [],
+ "proj": null
+ },
+ {
+ "id": 3,
+ "label": "$mem",
+ "type": null,
+ "kind": "MEM",
+ "edges": [
+ {
+ "idx": 0,
+ "def": 1,
+ "role": "MEM",
+ "label": null
+ }
+ ],
+ "proj": {
+ "par": 1,
+ "idx": 1
+ }
+ },
+ {
+ "id": 5,
+ "label": "new Point",
+ "type": null,
+ "kind": "DATA",
+ "edges": [
+ {
+ "idx": 0,
+ "def": 0,
+ "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": "MEM#2",
+ "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": "MEM#3",
+ "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": 11,
+ "label": "True",
+ "type": null,
+ "kind": "CTRL",
+ "edges": [],
+ "proj": {
+ "par": 0,
+ "idx": 0
+ }
+ },
+ {
+ "id": 12,
+ "label": "False",
+ "type": null,
+ "kind": "CTRL",
+ "edges": [],
+ "proj": {
+ "par": 0,
+ "idx": 1
+ }
+ },
+ {
+ "id": 13,
+ "label": "#3",
+ "type": null,
+ "kind": "DATA",
+ "edges": [
+ {
+ "idx": 0,
+ "def": 1,
+ "role": "ASSOC",
+ "label": null
+ }
+ ],
+ "proj": null
+ },
+ {
+ "id": 14,
+ "label": "Store p.x",
+ "type": "MEM#2",
+ "kind": "MEM",
+ "edges": [
+ {
+ "idx": 0,
+ "def": 11,
+ "role": "CTRL",
+ "label": null
+ },
+ {
+ "idx": 1,
+ "def": 7,
+ "role": "MEM",
+ "label": null
+ },
+ {
+ "idx": 2,
+ "def": 5,
+ "role": "DATA",
+ "label": null
+ },
+ {
+ "idx": 3,
+ "def": 13,
+ "role": "DATA",
+ "label": null
+ }
+ ],
+ "proj": null
+ },
+ {
+ "id": 15,
+ "label": "#4",
+ "type": null,
+ "kind": "DATA",
+ "edges": [
+ {
+ "idx": 0,
+ "def": 1,
+ "role": "ASSOC",
+ "label": null
+ }
+ ],
+ "proj": null
+ },
+ {
+ "id": 16,
+ "label": "Store p.x",
+ "type": "MEM#2",
+ "kind": "MEM",
+ "edges": [
+ {
+ "idx": 0,
+ "def": 12,
+ "role": "CTRL",
+ "label": null
+ },
+ {
+ "idx": 1,
+ "def": 7,
+ "role": "MEM",
+ "label": null
+ },
+ {
+ "idx": 2,
+ "def": 5,
+ "role": "DATA",
+ "label": null
+ },
+ {
+ "idx": 3,
+ "def": 15,
+ "role": "DATA",
+ "label": null
+ }
+ ],
+ "proj": null
+ },
+ {
+ "id": 20,
+ "label": "MemPhi x",
+ "type": "MEM#2",
+ "kind": "PHI",
+ "edges": [
+ {
+ "idx": 0,
+ "def": 0,
+ "role": "ASSOC",
+ "label": null
+ },
+ {
+ "idx": 1,
+ "def": 14,
+ "role": "MEM",
+ "label": null
+ },
+ {
+ "idx": 2,
+ "def": 16,
+ "role": "MEM",
+ "label": null
+ }
+ ],
+ "proj": null
+ },
+ {
+ "id": 22,
+ "label": "MemMerge",
+ "type": null,
+ "kind": "MEM",
+ "edges": [
+ {
+ "idx": 0,
+ "def": 0,
+ "role": "CTRL",
+ "label": null
+ },
+ {
+ "idx": 1,
+ "def": 3,
+ "role": "MEM",
+ "label": null
+ },
+ {
+ "idx": 2,
+ "def": 20,
+ "role": "MEM",
+ "label": null
+ },
+ {
+ "idx": 3,
+ "def": 8,
+ "role": "MEM",
+ "label": null
+ }
+ ],
+ "proj": null
+ },
+ {
+ "id": 23,
+ "label": "Return",
+ "type": null,
+ "kind": "CTRL",
+ "edges": [
+ {
+ "idx": 0,
+ "def": 0,
+ "role": "CTRL",
+ "label": null
+ },
+ {
+ "idx": 1,
+ "def": 22,
+ "role": "MEM",
+ "label": null
+ },
+ {
+ "idx": 2,
+ "def": 5,
+ "role": "DATA",
+ "label": null
+ }
+ ],
+ "proj": null
+ }
+ ],
+ "groups": []
+}
diff --git a/docs/chapter11/example2b-merged.svg b/docs/chapter11/example2b-merged.svg
new file mode 100644
index 00000000..21ae615e
--- /dev/null
+++ b/docs/chapter11/example2b-merged.svg
@@ -0,0 +1,130 @@
+<svg aria-label="Compiler graph" width="885" height="662" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 885 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 ASSOC" id="e6i0" d="M248,46 C248,70 690,41 690,65" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#6[0] → #1 · ASSOC</title></path><path class="edge MEM" id="e7i1" d="M185,118 L185,166" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#7[1] → #3 · MEM</title></path><path class="edge DATA" id="e7i2" d="M538,118 L538,140 Q538,148 530,148 L217,148 Q209,148 209,156 L209,166" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#7[2] → #5 · DATA</title></path><path class="edge DATA" id="e7i3" d="M690,118 L690,123 Q690,128 685,128 L241,128 Q233,128 233,136 L233,166" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#7[3] → #6 · DATA</title></path><path class="edge MEM" id="e8i1" d="M185,118 L185,130 Q185,138 193,138 L741,138 Q749,138 749,146 L749,166" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#8[1] → #3 · MEM</title></path><path class="edge DATA" id="e8i2" d="M538,118 L538,140 Q538,148 546,148 L765,148 Q773,148 773,156 L773,166" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#8[2] → #5 · DATA</title></path><path class="edge DATA" id="e8i3" d="M690,118 L690,123 Q690,128 695,128 L789,128 Q797,128 797,136 L797,166" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#8[3] → #6 · DATA</title></path><path class="edge ASSOC" id="e13i0" d="M248,46 C248,70 349,145 349,169" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#13[0] → #1 · ASSOC</title></path><path class="edge CTRL" id="e14i0" d="M76,222 L76,270" stroke="#b63838" marker-start="url(#arrow-CTRL)" style="opacity: 1;"><title>#14[0] → #11 · CTRL</title></path><path class="edge MEM" id="e14i1" d="M197,222 L197,227 Q197,232 192,232 L108,232 Q100,232 100,240 L100,270" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#14[1] → #7 · MEM</title></path><path class="edge DATA" id="e14i2" d="M538,118 L538,140 Q538,148 530,148 L134,148 Q126,148 126,156 L126,241 Q126,242 125,242 L125,242 Q124,242 124,243 L124,270" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#14[2] → #5 · DATA</title></path><path class="edge DATA" id="e14i3" d="M349,222 L349,234 Q349,242 341,242 L156,242 Q148,242 148,250 L148,270" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#14[3] → #13 · DATA</title></path><path class="edge ASSOC" id="e15i0" d="M248,46 C248,70 609,145 609,169" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#15[0] → #1 · ASSOC</title></path><path class="edge CTRL" id="e16i0" d="M485,222 L485,270" stroke="#b63838" marker-start="url(#arrow-CTRL)" style="opacity: 1;"><title>#16[0] → #12 · CTRL</title></path><path class="edge MEM" id="e16i1" d="M197,222 L197,227 Q197,232 202,232 L501,232 Q509,232 509,240 L509,270" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#16[1] → #7 · MEM</title></path><path class="edge DATA" id="e16i2" d="M538,118 L538,229.5 Q538,232 535.5,232 L535.5,232 Q533,232 533,234.5 L533,270" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#16[2] → #5 · DATA</title></path><path class="edge DATA" id="e16i3" d="M609,222 L609,227 Q609,232 604,232 L565,232 Q557,232 557,240 L557,270" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#16[3] → #15 · DATA</title></path><path class="edge MEM" id="e20i1" d="M112,326 L112,331 Q112,336 117,336 L473,336 Q481,336 481,344 L481,374" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#20[1] → #14 · MEM</title></path><path class="edge MEM" id="e20i2" d="M521,326 L521,374" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#20[2] → #16 · MEM</title></path><path class="edge MEM" id="e22i1" d="M185,118 L185,130 Q185,138 177,138 L32,138 Q24,138 24,146 L24,442 Q24,450 32,450 L705,450 Q713,450 713,458 L713,478" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#22[1] → #3 · MEM</title></path><path class="edge MEM" id="e22i2" d="M501,430 L501,435 Q501,440 506,440 L729,440 Q737,440 737,448 L737,478" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#22[2] → #20 · MEM</title></path><path class="edge MEM" id="e22i3" d="M761,222 L761,478" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#22[3] → #8 · MEM</title></path><path class="edge MEM" id="e23i1" d="M725,534 L725,539 Q725,544 730,544 L793,544 Q801,544 801,552 L801,582" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#23[1] → #22 · MEM</title></path><path class="edge DATA" id="e23i2" d="M538,118 L538,140 Q538,148 546,148 L823,148 Q831,148 831,156 L831,582" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#23[2] → #5 · 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(125,24)" style="opacity: 1;"><path class="box" d="M0,0H120V44H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>Start</tspan><tspan class="node-id">  #1</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#1 Start
+Type not computed
+
+</title></g><g class="node" id="n3" data-id="3" transform="translate(125,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="n5" data-id="5" transform="translate(478,68)" 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] → — CTRL</title><circle class="port" cx="60" cy="-3" r="3" fill="white"/><text class="slot" x="60" y="-10" text-anchor="middle">0</text></g><g class="node" id="n6" data-id="6" transform="translate(630,68)" 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(137,172)" 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">MEM#2</text><title>#7 Store p.x
+MEM#2
+
+[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(701,172)" 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">MEM#3</text><title>#8 Store p.y
+MEM#3
+
+[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="n11" data-id="11" transform="translate(35,190)" style="opacity: 1;"><path class="box" d="M0,0H81V26H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="40.5" y="17"><tspan>True</tspan><tspan class="node-id">  #11/0</tspan></text><text class="type" text-anchor="middle" y="35" x="40.5"/><title>#11 True
+Projection 0 of #0
+Type not computed
+
+</title></g><g class="node" id="n12" data-id="12" transform="translate(441,190)" style="opacity: 1;"><path class="box" d="M0,0H87V26H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="43.5" y="17"><tspan>False</tspan><tspan class="node-id">  #12/1</tspan></text><text class="type" text-anchor="middle" y="35" x="43.5"/><title>#12 False
+Projection 1 of #0
+Type not computed
+
+</title></g><g class="node" id="n13" data-id="13" transform="translate(289,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">  #13</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#13 #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="n14" data-id="14" transform="translate(52,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">  #14</tspan></text><text class="type" text-anchor="middle" y="35" x="60">MEM#2</text><title>#14 Store p.x
+MEM#2
+
+[0] → #11 CTRL
+[1] → #7 MEM
+[2] → #5 DATA
+[3] → #13 DATA</title><circle class="port" cx="24" cy="-3" r="3" fill="#536477"/><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="n15" data-id="15" transform="translate(549,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">  #15</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#15 #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="n16" data-id="16" transform="translate(461,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">  #16</tspan></text><text class="type" text-anchor="middle" y="35" x="60">MEM#2</text><title>#16 Store p.x
+MEM#2
+
+[0] → #12 CTRL
+[1] → #7 MEM
+[2] → #5 DATA
+[3] → #15 DATA</title><circle class="port" cx="24" cy="-3" r="3" fill="#536477"/><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="n20" data-id="20" transform="translate(441,380)" 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>MemPhi x</tspan><tspan class="node-id">  #20</tspan></text><text class="type" text-anchor="middle" y="35" x="60">MEM#2</text><title>#20 MemPhi x
+MEM#2
+
+[0] → — ASSOC
+[1] → #14 MEM
+[2] → #16 MEM</title><circle class="port" cx="0" cy="-3" r="3" fill="white"/><circle class="port" cx="40" cy="-3" r="3" fill="#536477"/><circle class="port" cx="80" cy="-3" r="3" fill="#536477"/><text class="slot" x="0" y="-10" 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="n22" data-id="22" transform="translate(665,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>MemMerge</tspan><tspan class="node-id">  #22</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#22 MemMerge
+Type not computed
+
+[0] → — CTRL
+[1] → #3 MEM
+[2] → #20 MEM
+[3] → #8 MEM</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="n23" data-id="23" transform="translate(741,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">  #23</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#23 Return
+Type not computed
+
+[0] → — CTRL
+[1] → #22 MEM
+[2] → #5 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><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/docs/chapter11/example2b-split-y.json b/docs/chapter11/example2b-split-y.json
new file mode 100644
index 00000000..fc3c8758
--- /dev/null
+++ b/docs/chapter11/example2b-split-y.json
@@ -0,0 +1,486 @@
+{
+ "ver": 1,
+ "comp": "chapter11/docs/example2b-split-y.svg",
+ "step": 3,
+ "roots": [
+ 23
+ ],
+ "scope": 0,
+ "nodes": [
+ {
+ "id": 1,
+ "label": "Start",
+ "type": null,
+ "kind": "CTRL",
+ "edges": [],
+ "proj": null
+ },
+ {
+ "id": 3,
+ "label": "$mem",
+ "type": null,
+ "kind": "MEM",
+ "edges": [
+ {
+ "idx": 0,
+ "def": 1,
+ "role": "MEM",
+ "label": null
+ }
+ ],
+ "proj": {
+ "par": 1,
+ "idx": 1
+ }
+ },
+ {
+ "id": 5,
+ "label": "new Point",
+ "type": null,
+ "kind": "DATA",
+ "edges": [
+ {
+ "idx": 0,
+ "def": 0,
+ "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": "MEM#2",
+ "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": "MEM#3",
+ "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": 11,
+ "label": "True",
+ "type": null,
+ "kind": "CTRL",
+ "edges": [],
+ "proj": {
+ "par": 0,
+ "idx": 0
+ }
+ },
+ {
+ "id": 12,
+ "label": "False",
+ "type": null,
+ "kind": "CTRL",
+ "edges": [],
+ "proj": {
+ "par": 0,
+ "idx": 1
+ }
+ },
+ {
+ "id": 13,
+ "label": "#3",
+ "type": null,
+ "kind": "DATA",
+ "edges": [
+ {
+ "idx": 0,
+ "def": 1,
+ "role": "ASSOC",
+ "label": null
+ }
+ ],
+ "proj": null
+ },
+ {
+ "id": 14,
+ "label": "Store p.x",
+ "type": "MEM#2",
+ "kind": "MEM",
+ "edges": [
+ {
+ "idx": 0,
+ "def": 11,
+ "role": "CTRL",
+ "label": null
+ },
+ {
+ "idx": 1,
+ "def": 7,
+ "role": "MEM",
+ "label": null
+ },
+ {
+ "idx": 2,
+ "def": 5,
+ "role": "DATA",
+ "label": null
+ },
+ {
+ "idx": 3,
+ "def": 13,
+ "role": "DATA",
+ "label": null
+ }
+ ],
+ "proj": null
+ },
+ {
+ "id": 15,
+ "label": "#4",
+ "type": null,
+ "kind": "DATA",
+ "edges": [
+ {
+ "idx": 0,
+ "def": 1,
+ "role": "ASSOC",
+ "label": null
+ }
+ ],
+ "proj": null
+ },
+ {
+ "id": 16,
+ "label": "Store p.x",
+ "type": "MEM#2",
+ "kind": "MEM",
+ "edges": [
+ {
+ "idx": 0,
+ "def": 12,
+ "role": "CTRL",
+ "label": null
+ },
+ {
+ "idx": 1,
+ "def": 7,
+ "role": "MEM",
+ "label": null
+ },
+ {
+ "idx": 2,
+ "def": 5,
+ "role": "DATA",
+ "label": null
+ },
+ {
+ "idx": 3,
+ "def": 15,
+ "role": "DATA",
+ "label": null
+ }
+ ],
+ "proj": null
+ },
+ {
+ "id": 17,
+ "label": "MemMerge",
+ "type": null,
+ "kind": "MEM",
+ "edges": [
+ {
+ "idx": 0,
+ "def": 0,
+ "role": "CTRL",
+ "label": null
+ },
+ {
+ "idx": 1,
+ "def": 3,
+ "role": "MEM",
+ "label": null
+ },
+ {
+ "idx": 2,
+ "def": 14,
+ "role": "MEM",
+ "label": null
+ },
+ {
+ "idx": 3,
+ "def": 8,
+ "role": "MEM",
+ "label": null
+ }
+ ],
+ "proj": null
+ },
+ {
+ "id": 18,
+ "label": "MemMerge",
+ "type": null,
+ "kind": "MEM",
+ "edges": [
+ {
+ "idx": 0,
+ "def": 0,
+ "role": "CTRL",
+ "label": null
+ },
+ {
+ "idx": 1,
+ "def": 3,
+ "role": "MEM",
+ "label": null
+ },
+ {
+ "idx": 2,
+ "def": 16,
+ "role": "MEM",
+ "label": null
+ },
+ {
+ "idx": 3,
+ "def": 8,
+ "role": "MEM",
+ "label": null
+ }
+ ],
+ "proj": null
+ },
+ {
+ "id": 20,
+ "label": "MemPhi x",
+ "type": "MEM#2",
+ "kind": "PHI",
+ "edges": [
+ {
+ "idx": 0,
+ "def": 0,
+ "role": "ASSOC",
+ "label": null
+ },
+ {
+ "idx": 1,
+ "def": 14,
+ "role": "MEM",
+ "label": null
+ },
+ {
+ "idx": 2,
+ "def": 16,
+ "role": "MEM",
+ "label": null
+ }
+ ],
+ "proj": null
+ },
+ {
+ "id": 22,
+ "label": "MemMerge",
+ "type": null,
+ "kind": "MEM",
+ "edges": [
+ {
+ "idx": 0,
+ "def": 0,
+ "role": "CTRL",
+ "label": null
+ },
+ {
+ "idx": 1,
+ "def": 26,
+ "role": "MEM",
+ "label": null
+ },
+ {
+ "idx": 2,
+ "def": 20,
+ "role": "MEM",
+ "label": null
+ }
+ ],
+ "proj": null
+ },
+ {
+ "id": 23,
+ "label": "Return",
+ "type": null,
+ "kind": "CTRL",
+ "edges": [
+ {
+ "idx": 0,
+ "def": 0,
+ "role": "CTRL",
+ "label": null
+ },
+ {
+ "idx": 1,
+ "def": 22,
+ "role": "MEM",
+ "label": null
+ },
+ {
+ "idx": 2,
+ "def": 5,
+ "role": "DATA",
+ "label": null
+ }
+ ],
+ "proj": null
+ },
+ {
+ "id": 24,
+ "label": "MemPhi y",
+ "type": "MEM#3",
+ "kind": "PHI",
+ "edges": [
+ {
+ "idx": 0,
+ "def": 0,
+ "role": "ASSOC",
+ "label": null
+ },
+ {
+ "idx": 1,
+ "def": 8,
+ "role": "MEM",
+ "label": null
+ },
+ {
+ "idx": 2,
+ "def": 8,
+ "role": "MEM",
+ "label": null
+ }
+ ],
+ "proj": null
+ },
+ {
+ "id": 25,
+ "label": "BulkMemPhi except x,y",
+ "type": null,
+ "kind": "PHI",
+ "edges": [
+ {
+ "idx": 0,
+ "def": 0,
+ "role": "ASSOC",
+ "label": null
+ },
+ {
+ "idx": 1,
+ "def": 17,
+ "role": "MEM",
+ "label": null
+ },
+ {
+ "idx": 2,
+ "def": 18,
+ "role": "MEM",
+ "label": null
+ }
+ ],
+ "proj": null
+ },
+ {
+ "id": 26,
+ "label": "MemMerge",
+ "type": null,
+ "kind": "MEM",
+ "edges": [
+ {
+ "idx": 0,
+ "def": 0,
+ "role": "CTRL",
+ "label": null
+ },
+ {
+ "idx": 1,
+ "def": 25,
+ "role": "MEM",
+ "label": null
+ },
+ {
+ "idx": 2,
+ "def": 20,
+ "role": "MEM",
+ "label": null
+ },
+ {
+ "idx": 3,
+ "def": 24,
+ "role": "MEM",
+ "label": null
+ }
+ ],
+ "proj": null
+ }
+ ],
+ "groups": []
+}
diff --git a/docs/chapter11/example2b-split-y.svg b/docs/chapter11/example2b-split-y.svg
new file mode 100644
index 00000000..f4d4b9b6
--- /dev/null
+++ b/docs/chapter11/example2b-split-y.svg
@@ -0,0 +1,157 @@
+<svg aria-label="Compiler graph" width="945" height="872" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 945 872" 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 ASSOC" id="e6i0" d="M147,46 C147,70 716,41 716,65" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#6[0] → #1 · ASSOC</title></path><path class="edge MEM" id="e7i1" d="M84,118 L84,140 Q84,148 92,148 L660,148 Q668,148 668,156 L668,166" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#7[1] → #3 · MEM</title></path><path class="edge DATA" id="e7i2" d="M564,118 L564,123 Q564,128 569,128 L684,128 Q692,128 692,136 L692,166" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#7[2] → #5 · DATA</title></path><path class="edge DATA" id="e7i3" d="M716,118 L716,166" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#7[3] → #6 · DATA</title></path><path class="edge MEM" id="e8i1" d="M84,118 L84,140 Q84,148 92,148 L389,148 Q397,148 397,156 L397,166" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#8[1] → #3 · MEM</title></path><path class="edge DATA" id="e8i2" d="M564,118 L564,123 Q564,128 559,128 L429,128 Q421,128 421,136 L421,166" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#8[2] → #5 · DATA</title></path><path class="edge DATA" id="e8i3" d="M716,118 L716,130 Q716,138 708,138 L453,138 Q445,138 445,146 L445,166" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#8[3] → #6 · DATA</title></path><path class="edge ASSOC" id="e13i0" d="M147,46 C147,70 257,145 257,169" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#13[0] → #1 · ASSOC</title></path><path class="edge CTRL" id="e14i0" d="M136,222 L136,270" stroke="#b63838" marker-start="url(#arrow-CTRL)" style="opacity: 1;"><title>#14[0] → #11 · CTRL</title></path><path class="edge MEM" id="e14i1" d="M680,222 L680,227 Q680,232 675,232 L168,232 Q160,232 160,240 L160,270" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#14[1] → #7 · MEM</title></path><path class="edge DATA" id="e14i2" d="M564,118 L564,123 Q564,128 559,128 L194,128 Q186,128 186,136 L186,241 Q186,242 185,242 L185,242 Q184,242 184,243 L184,270" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#14[2] → #5 · DATA</title></path><path class="edge DATA" id="e14i3" d="M257,222 L257,234 Q257,242 249,242 L216,242 Q208,242 208,250 L208,270" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#14[3] → #13 · DATA</title></path><path class="edge ASSOC" id="e15i0" d="M147,46 C147,70 821,145 821,169" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#15[0] → #1 · ASSOC</title></path><path class="edge CTRL" id="e16i0" d="M545,222 L545,270" stroke="#b63838" marker-start="url(#arrow-CTRL)" style="opacity: 1;"><title>#16[0] → #12 · CTRL</title></path><path class="edge MEM" id="e16i1" d="M680,222 L680,227 Q680,232 675,232 L577,232 Q569,232 569,240 L569,270" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#16[1] → #7 · MEM</title></path><path class="edge DATA" id="e16i2" d="M564,118 L564,123 Q564,128 569,128 L742,128 Q750,128 750,136 L750,234 Q750,242 742,242 L601,242 Q593,242 593,250 L593,270" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#16[2] → #5 · DATA</title></path><path class="edge DATA" id="e16i3" d="M821,222 L821,244 Q821,252 813,252 L625,252 Q617,252 617,260 L617,270" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#16[3] → #15 · DATA</title></path><path class="edge MEM" id="e17i1" d="M84,118 L84,358 Q84,366 92,366 L143,366 Q148,366 148,371 L148,376" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#17[1] → #3 · MEM</title></path><path class="edge MEM" id="e17i2" d="M172,326 L172,376" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#17[2] → #14 · MEM</title></path><path class="edge MEM" id="e17i3" d="M409,222 L409,234 Q409,242 401,242 L356,242 Q348,242 348,250 L348,348 Q348,356 340,356 L204,356 Q196,356 196,364 L196,376" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#17[3] → #8 · MEM</title></path><path class="edge MEM" id="e18i1" d="M84,118 L84,358 Q84,366 92,366 L295,366 Q300,366 300,371 L300,376" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#18[1] → #3 · MEM</title></path><path class="edge MEM" id="e18i2" d="M581,326 L581,358 Q581,366 573,366 L329,366 Q324,366 324,371 L324,376" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#18[2] → #16 · MEM</title></path><path class="edge MEM" id="e18i3" d="M409,222 L409,234 Q409,242 401,242 L356,242 Q348,242 348,250 L348,376" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#18[3] → #8 · MEM</title></path><path class="edge MEM" id="e20i1" d="M172,326 L172,338 Q172,346 180,346 L439,346 Q447,346 447,354 L447,376" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#20[1] → #14 · MEM</title></path><path class="edge MEM" id="e20i2" d="M581,326 L581,358 Q581,366 573,366 L492,366 Q487,366 487,371 L487,376" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#20[2] → #16 · MEM</title></path><path class="edge MEM" id="e22i1" d="M397,640 L397,645 Q397,650 402,650 L429,650 Q437,650 437,658 L437,688" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#22[1] → #26 · MEM</title></path><path class="edge MEM" id="e22i2" d="M467,432 L467,688" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#22[2] → #20 · MEM</title></path><path class="edge MEM" id="e23i1" d="M437,744 L437,749 Q437,754 442,754 L853,754 Q861,754 861,762 L861,792" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#23[1] → #22 · MEM</title></path><path class="edge DATA" id="e23i2" d="M564,118 L564,123 Q564,128 569,128 L883,128 Q891,128 891,136 L891,792" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#23[2] → #5 · DATA</title></path><path class="edge MEM" id="e24i1" d="M409,222 L409,270" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#24[1] → #8 · MEM</title></path><path class="edge MEM" id="e24i2" d="M409,222 L409,234 Q409,242 417,242 L441,242 Q449,242 449,250 L449,270" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#24[2] → #8 · MEM</title></path><path class="edge MEM" id="e25i1" d="M160,432 L160,437 Q160,442 165,442 L247,442 Q255,442 255,450 L255,480" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#25[1] → #17 · MEM</title></path><path class="edge MEM" id="e25i2" d="M312,432 L312,480" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#25[2] → #18 · MEM</title></path><path class="edge MEM" id="e26i1" d="M284,536 L284,541 Q284,546 289,546 L377,546 Q385,546 385,554 L385,584" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#26[1] → #25 · MEM</title></path><path class="edge MEM" id="e26i2" d="M467,432 L467,538 Q467,546 459,546 L417,546 Q409,546 409,554 L409,584" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#26[2] → #20 · MEM</title></path><path class="edge MEM" id="e26i3" d="M429,326 L429,331 Q429,336 434,336 L529,336 Q537,336 537,344 L537,548 Q537,556 529,556 L441,556 Q433,556 433,564 L433,584" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#26[3] → #24 · MEM</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,0H120V44H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>Start</tspan><tspan class="node-id">  #1</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#1 Start
+Type not computed
+
+</title></g><g class="node" id="n3" data-id="3" transform="translate(24,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="n5" data-id="5" transform="translate(504,68)" 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] → — CTRL</title><circle class="port" cx="60" cy="-3" r="3" fill="white"/><text class="slot" x="60" y="-10" text-anchor="middle">0</text></g><g class="node" id="n6" data-id="6" transform="translate(656,68)" 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(620,172)" 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">MEM#2</text><title>#7 Store p.x
+MEM#2
+
+[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(349,172)" 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">MEM#3</text><title>#8 Store p.y
+MEM#3
+
+[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="n11" data-id="11" transform="translate(95,190)" style="opacity: 1;"><path class="box" d="M0,0H81V26H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="40.5" y="17"><tspan>True</tspan><tspan class="node-id">  #11/0</tspan></text><text class="type" text-anchor="middle" y="35" x="40.5"/><title>#11 True
+Projection 0 of #0
+Type not computed
+
+</title></g><g class="node" id="n12" data-id="12" transform="translate(501,190)" style="opacity: 1;"><path class="box" d="M0,0H87V26H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="43.5" y="17"><tspan>False</tspan><tspan class="node-id">  #12/1</tspan></text><text class="type" text-anchor="middle" y="35" x="43.5"/><title>#12 False
+Projection 1 of #0
+Type not computed
+
+</title></g><g class="node" id="n13" data-id="13" transform="translate(197,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">  #13</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#13 #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="n14" data-id="14" transform="translate(112,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">  #14</tspan></text><text class="type" text-anchor="middle" y="35" x="60">MEM#2</text><title>#14 Store p.x
+MEM#2
+
+[0] → #11 CTRL
+[1] → #7 MEM
+[2] → #5 DATA
+[3] → #13 DATA</title><circle class="port" cx="24" cy="-3" r="3" fill="#536477"/><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="n15" data-id="15" transform="translate(761,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">  #15</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#15 #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="n16" data-id="16" transform="translate(521,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">  #16</tspan></text><text class="type" text-anchor="middle" y="35" x="60">MEM#2</text><title>#16 Store p.x
+MEM#2
+
+[0] → #12 CTRL
+[1] → #7 MEM
+[2] → #5 DATA
+[3] → #15 DATA</title><circle class="port" cx="24" cy="-3" r="3" fill="#536477"/><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="n17" data-id="17" transform="translate(100,382)" 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>MemMerge</tspan><tspan class="node-id">  #17</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#17 MemMerge
+Type not computed
+
+[0] → — CTRL
+[1] → #3 MEM
+[2] → #14 MEM
+[3] → #8 MEM</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="n18" data-id="18" transform="translate(252,382)" 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>MemMerge</tspan><tspan class="node-id">  #18</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#18 MemMerge
+Type not computed
+
+[0] → — CTRL
+[1] → #3 MEM
+[2] → #16 MEM
+[3] → #8 MEM</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="n20" data-id="20" transform="translate(407,382)" 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>MemPhi x</tspan><tspan class="node-id">  #20</tspan></text><text class="type" text-anchor="middle" y="35" x="60">MEM#2</text><title>#20 MemPhi x
+MEM#2
+
+[0] → — ASSOC
+[1] → #14 MEM
+[2] → #16 MEM</title><circle class="port" cx="0" cy="-3" r="3" fill="white"/><circle class="port" cx="40" cy="-3" r="3" fill="#536477"/><circle class="port" cx="80" cy="-3" r="3" fill="#536477"/><text class="slot" x="0" y="-10" 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="n22" data-id="22" transform="translate(377,694)" 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>MemMerge</tspan><tspan class="node-id">  #22</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#22 MemMerge
+Type not computed
+
+[0] → — CTRL
+[1] → #26 MEM
+[2] → #20 MEM</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="n23" data-id="23" transform="translate(801,798)" 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">  #23</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#23 Return
+Type not computed
+
+[0] → — CTRL
+[1] → #22 MEM
+[2] → #5 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="n24" data-id="24" transform="translate(369,276)" 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>MemPhi y</tspan><tspan class="node-id">  #24</tspan></text><text class="type" text-anchor="middle" y="35" x="60">MEM#3</text><title>#24 MemPhi y
+MEM#3
+
+[0] → — ASSOC
+[1] → #8 MEM
+[2] → #8 MEM</title><circle class="port" cx="0" cy="-3" r="3" fill="white"/><circle class="port" cx="40" cy="-3" r="3" fill="#536477"/><circle class="port" cx="80" cy="-3" r="3" fill="#536477"/><text class="slot" x="0" y="-10" 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="n25" data-id="25" transform="translate(197,486)" style="opacity: 1;"><path class="box" d="M12,0H161L173,22L161,44H12L0,22Z" fill="#f5e4fa"/><text class="label" text-anchor="middle" x="86.5" y="18"><tspan>BulkMemPhi except x,y</tspan><tspan class="node-id">  #25</tspan></text><text class="type" text-anchor="middle" y="35" x="86.5"/><title>#25 BulkMemPhi except x,y
+Type not computed
+
+[0] → — ASSOC
+[1] → #17 MEM
+[2] → #18 MEM</title><circle class="port" cx="0" cy="-3" r="3" fill="white"/><circle class="port" cx="58" cy="-3" r="3" fill="#536477"/><circle class="port" cx="115" cy="-3" r="3" fill="#536477"/><text class="slot" x="0" y="-10" text-anchor="middle">0</text><text class="slot" x="58" y="-10" text-anchor="middle">1</text><text class="slot" x="115" y="-10" text-anchor="middle">2</text></g><g class="node" id="n26" data-id="26" transform="translate(337,590)" 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>MemMerge</tspan><tspan class="node-id">  #26</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#26 MemMerge
+Type not computed
+
+[0] → — CTRL
+[1] → #25 MEM
+[2] → #20 MEM
+[3] → #24 MEM</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><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/docs/chapter11/example2b.gv b/docs/chapter11/example2b.gv
new file mode 100644
index 00000000..39b87a6a
--- /dev/null
+++ b/docs/chapter11/example2b.gv
@@ -0,0 +1,19 @@
+digraph Memory {
+ // IR styles match graph/web/render.js; arrows are use -> definition.
+ graph [bgcolor="#fafbfc" fontname="Helvetica" nodesep=0.35 ranksep=0.55 splines=ortho];
+ node [fontname="Helvetica" fontsize=12 color="#758191" fontcolor="#152330" shape=box style="rounded,filled" fillcolor="#edf3f7"];
+ edge [fontname="Helvetica" fontsize=9 color="#536477" fontcolor="#364352" arrowsize=0.7 penwidth=1.5];
+
+ rankdir=RL;
+
+ P [label="MemPhi(x)" shape=hexagon style="filled" fillcolor="#f5e4fa"];
+ B [label="BulkMemPhi(All except x)" shape=hexagon style="filled" fillcolor="#f5e4fa"];
+ P -> Left [xlabel="slice x",color="#2867b2"]; P -> Right [xlabel="slice x",color="#2867b2"];
+ B -> Left [color="#2867b2"]; B -> Right [color="#2867b2"];
+ P -> Region [style=dashed,color="#9396a3"]; B -> Region [style=dashed,color="#9396a3"];
+ MemMerge -> P [xlabel="x",color="#2867b2"]; MemMerge -> B [xlabel="default",color="#2867b2"];
+ MemMerge [shape=octagon style="filled" fillcolor="#dcecff"];
+ Left [shape=octagon style="filled" fillcolor="#dcecff"];
+ Right [shape=octagon style="filled" fillcolor="#dcecff"];
+ Region [shape=trapezium style="filled" fillcolor="#fff1c4"];
+}
diff --git a/docs/chapter11/example2b.json b/docs/chapter11/example2b.json
new file mode 100644
index 00000000..702773ea
--- /dev/null
+++ b/docs/chapter11/example2b.json
@@ -0,0 +1,30 @@
+{
+ "ver": 1,
+ "comp": "chapter11/docs/example2b.svg",
+ "step": 1,
+ "roots": [23],
+ "scope": 0,
+ "nodes": [
+ {"id": 1, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null},
+ {"id": 3, "label": "$mem", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 1, "role": "MEM", "label": null}], "proj": {"par": 1, "idx": 1}},
+ {"id": 5, "label": "new Point", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 0, "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": "MEM#2", "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": "MEM#3", "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": 9, "label": "MemMerge", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 7, "role": "MEM", "label": null}, {"idx": 3, "def": 8, "role": "MEM", "label": null}], "proj": null},
+ {"id": 11, "label": "True", "type": null, "kind": "CTRL", "edges": [], "proj": {"par": 0, "idx": 0}},
+ {"id": 12, "label": "False", "type": null, "kind": "CTRL", "edges": [], "proj": {"par": 0, "idx": 1}},
+ {"id": 13, "label": "#3", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null},
+ {"id": 14, "label": "Store p.x", "type": "MEM#2", "kind": "MEM", "edges": [{"idx": 0, "def": 11, "role": "CTRL", "label": null}, {"idx": 1, "def": 7, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 13, "role": "DATA", "label": null}], "proj": null},
+ {"id": 15, "label": "#4", "type": null, "kind": "DATA", "edges": [{"idx": 0, "def": 1, "role": "ASSOC", "label": null}], "proj": null},
+ {"id": 16, "label": "Store p.x", "type": "MEM#2", "kind": "MEM", "edges": [{"idx": 0, "def": 12, "role": "CTRL", "label": null}, {"idx": 1, "def": 7, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}, {"idx": 3, "def": 15, "role": "DATA", "label": null}], "proj": null},
+ {"id": 17, "label": "MemMerge", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 9, "role": "MEM", "label": null}, {"idx": 2, "def": 14, "role": "MEM", "label": null}], "proj": null},
+ {"id": 18, "label": "MemMerge", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 9, "role": "MEM", "label": null}, {"idx": 2, "def": 16, "role": "MEM", "label": null}], "proj": null},
+ {"id": 20, "label": "MemPhi x", "type": "MEM#2", "kind": "PHI", "edges": [{"idx": 0, "def": 0, "role": "ASSOC", "label": null}, {"idx": 1, "def": 14, "role": "MEM", "label": null}, {"idx": 2, "def": 16, "role": "MEM", "label": null}], "proj": null},
+ {"id": 21, "label": "BulkMemPhi except x", "type": null, "kind": "PHI", "edges": [{"idx": 0, "def": 0, "role": "ASSOC", "label": null}, {"idx": 1, "def": 17, "role": "MEM", "label": null}, {"idx": 2, "def": 18, "role": "MEM", "label": null}], "proj": null},
+ {"id": 22, "label": "MemMerge", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 21, "role": "MEM", "label": null}, {"idx": 2, "def": 20, "role": "MEM", "label": null}], "proj": null},
+ {"id": 23, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 22, "role": "MEM", "label": null}, {"idx": 2, "def": 5, "role": "DATA", "label": null}], "proj": null}
+ ],
+ "groups": [
+ ]
+}
diff --git a/docs/chapter11/example2b.svg b/docs/chapter11/example2b.svg
new file mode 100644
index 00000000..54ddad9d
--- /dev/null
+++ b/docs/chapter11/example2b.svg
@@ -0,0 +1,150 @@
+<svg aria-label="Compiler graph" width="886" height="768" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 886 768" 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 ASSOC" id="e6i0" d="M147,46 C147,70 279,41 279,65" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#6[0] → #1 · ASSOC</title></path><path class="edge MEM" id="e7i1" d="M84,118 L84,140 Q84,148 92,148 L341,148 Q349,148 349,156 L349,166" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#7[1] → #3 · MEM</title></path><path class="edge DATA" id="e7i2" d="M431,118 L431,130 Q431,138 423,138 L381,138 Q373,138 373,146 L373,166" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#7[2] → #5 · DATA</title></path><path class="edge DATA" id="e7i3" d="M279,118 L279,123 Q279,128 284,128 L389,128 Q397,128 397,136 L397,166" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#7[3] → #6 · DATA</title></path><path class="edge MEM" id="e8i1" d="M84,118 L84,166" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#8[1] → #3 · MEM</title></path><path class="edge DATA" id="e8i2" d="M431,118 L431,130 Q431,138 423,138 L116,138 Q108,138 108,146 L108,166" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#8[2] → #5 · DATA</title></path><path class="edge DATA" id="e8i3" d="M279,118 L279,123 Q279,128 274,128 L140,128 Q132,128 132,136 L132,166" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#8[3] → #6 · DATA</title></path><path class="edge MEM" id="e9i1" d="M84,118 L84,140 Q84,148 76,148 L33,148 Q25,148 25,156 L25,234 Q25,242 33,242 L93,242 Q101,242 101,250 L101,272" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#9[1] → #3 · MEM</title></path><path class="edge MEM" id="e9i2" d="M361,222 L361,234 Q361,242 353,242 L133,242 Q125,242 125,250 L125,272" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#9[2] → #7 · MEM</title></path><path class="edge MEM" id="e9i3" d="M96,222 L96,227 Q96,232 101,232 L141,232 Q149,232 149,240 L149,272" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#9[3] → #8 · MEM</title></path><path class="edge ASSOC" id="e13i0" d="M147,46 C147,70 502,145 502,169" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#13[0] → #1 · ASSOC</title></path><path class="edge CTRL" id="e14i0" d="M229,222 L229,272" stroke="#b63838" marker-start="url(#arrow-CTRL)" style="opacity: 1;"><title>#14[0] → #11 · CTRL</title></path><path class="edge MEM" id="e14i1" d="M361,222 L361,234 Q361,242 353,242 L261,242 Q253,242 253,250 L253,272" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#14[1] → #7 · MEM</title></path><path class="edge DATA" id="e14i2" d="M431,118 L431,244 Q431,252 423,252 L285,252 Q277,252 277,260 L277,272" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#14[2] → #5 · DATA</title></path><path class="edge DATA" id="e14i3" d="M502,222 L502,254 Q502,262 494,262 L306,262 Q301,262 301,267 L301,272" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#14[3] → #13 · DATA</title></path><path class="edge ASSOC" id="e15i0" d="M147,46 C147,70 762,145 762,169" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#15[0] → #1 · ASSOC</title></path><path class="edge CTRL" id="e16i0" d="M638,222 L638,272" stroke="#b63838" marker-start="url(#arrow-CTRL)" style="opacity: 1;"><title>#16[0] → #12 · CTRL</title></path><path class="edge MEM" id="e16i1" d="M361,222 L361,234 Q361,242 369,242 L654,242 Q662,242 662,250 L662,272" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#16[1] → #7 · MEM</title></path><path class="edge DATA" id="e16i2" d="M431,118 L431,130 Q431,138 439,138 L683,138 Q691,138 691,146 L691,229.5 Q691,232 688.5,232 L688.5,232 Q686,232 686,234.5 L686,272" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#16[2] → #5 · DATA</title></path><path class="edge DATA" id="e16i3" d="M762,222 L762,227 Q762,232 757,232 L718,232 Q710,232 710,240 L710,272" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#16[3] → #15 · DATA</title></path><path class="edge MEM" id="e17i1" d="M113,328 L113,340 Q113,348 121,348 L227,348 Q235,348 235,356 L235,376" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#17[1] → #9 · MEM</title></path><path class="edge MEM" id="e17i2" d="M265,328 L265,376" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#17[2] → #14 · MEM</title></path><path class="edge MEM" id="e18i1" d="M113,328 L113,340 Q113,348 121,348 L491,348 Q499,348 499,356 L499,376" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#18[1] → #9 · MEM</title></path><path class="edge MEM" id="e18i2" d="M674,328 L674,340 Q674,348 666,348 L537,348 Q529,348 529,356 L529,376" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#18[2] → #16 · MEM</title></path><path class="edge MEM" id="e20i1" d="M265,328 L265,333 Q265,338 270,338 L626,338 Q634,338 634,346 L634,376" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#20[1] → #14 · MEM</title></path><path class="edge MEM" id="e20i2" d="M674,328 L674,376" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#20[2] → #16 · MEM</title></path><path class="edge MEM" id="e21i1" d="M235,432 L235,437 Q235,442 240,442 L436,442 Q444,442 444,450 L444,480" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#21[1] → #17 · MEM</title></path><path class="edge MEM" id="e21i2" d="M499,432 L499,480" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#21[2] → #18 · MEM</title></path><path class="edge MEM" id="e22i1" d="M472,536 L472,541 Q472,546 477,546 L616,546 Q624,546 624,554 L624,584" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#22[1] → #21 · MEM</title></path><path class="edge MEM" id="e22i2" d="M654,432 L654,584" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#22[2] → #20 · MEM</title></path><path class="edge MEM" id="e23i1" d="M624,640 L624,645 Q624,650 629,650 L794,650 Q802,650 802,658 L802,688" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#23[1] → #22 · MEM</title></path><path class="edge DATA" id="e23i2" d="M431,118 L431,130 Q431,138 439,138 L824,138 Q832,138 832,146 L832,688" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#23[2] → #5 · 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,0H120V44H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="60" y="18"><tspan>Start</tspan><tspan class="node-id">  #1</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#1 Start
+Type not computed
+
+</title></g><g class="node" id="n3" data-id="3" transform="translate(24,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="n5" data-id="5" transform="translate(371,68)" 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] → — CTRL</title><circle class="port" cx="60" cy="-3" r="3" fill="white"/><text class="slot" x="60" y="-10" text-anchor="middle">0</text></g><g class="node" id="n6" data-id="6" transform="translate(219,68)" 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(301,172)" 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">MEM#2</text><title>#7 Store p.x
+MEM#2
+
+[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(36,172)" 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">MEM#3</text><title>#8 Store p.y
+MEM#3
+
+[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="n9" data-id="9" transform="translate(53,278)" 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>MemMerge</tspan><tspan class="node-id">  #9</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#9 MemMerge
+Type not computed
+
+[0] → — CTRL
+[1] → #3 MEM
+[2] → #7 MEM
+[3] → #8 MEM</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(188,190)" style="opacity: 1;"><path class="box" d="M0,0H81V26H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="40.5" y="17"><tspan>True</tspan><tspan class="node-id">  #11/0</tspan></text><text class="type" text-anchor="middle" y="35" x="40.5"/><title>#11 True
+Projection 0 of #0
+Type not computed
+
+</title></g><g class="node" id="n12" data-id="12" transform="translate(594,190)" style="opacity: 1;"><path class="box" d="M0,0H87V26H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="43.5" y="17"><tspan>False</tspan><tspan class="node-id">  #12/1</tspan></text><text class="type" text-anchor="middle" y="35" x="43.5"/><title>#12 False
+Projection 1 of #0
+Type not computed
+
+</title></g><g class="node" id="n13" data-id="13" transform="translate(442,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">  #13</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#13 #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="n14" data-id="14" transform="translate(205,278)" 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">  #14</tspan></text><text class="type" text-anchor="middle" y="35" x="60">MEM#2</text><title>#14 Store p.x
+MEM#2
+
+[0] → #11 CTRL
+[1] → #7 MEM
+[2] → #5 DATA
+[3] → #13 DATA</title><circle class="port" cx="24" cy="-3" r="3" fill="#536477"/><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="n15" data-id="15" transform="translate(702,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">  #15</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#15 #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="n16" data-id="16" transform="translate(614,278)" 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">  #16</tspan></text><text class="type" text-anchor="middle" y="35" x="60">MEM#2</text><title>#16 Store p.x
+MEM#2
+
+[0] → #12 CTRL
+[1] → #7 MEM
+[2] → #5 DATA
+[3] → #15 DATA</title><circle class="port" cx="24" cy="-3" r="3" fill="#536477"/><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="n17" data-id="17" transform="translate(175,382)" 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>MemMerge</tspan><tspan class="node-id">  #17</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#17 MemMerge
+Type not computed
+
+[0] → — CTRL
+[1] → #9 MEM
+[2] → #14 MEM</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(439,382)" 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>MemMerge</tspan><tspan class="node-id">  #18</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#18 MemMerge
+Type not computed
+
+[0] → — CTRL
+[1] → #9 MEM
+[2] → #16 MEM</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="n20" data-id="20" transform="translate(594,382)" 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>MemPhi x</tspan><tspan class="node-id">  #20</tspan></text><text class="type" text-anchor="middle" y="35" x="60">MEM#2</text><title>#20 MemPhi x
+MEM#2
+
+[0] → — ASSOC
+[1] → #14 MEM
+[2] → #16 MEM</title><circle class="port" cx="0" cy="-3" r="3" fill="white"/><circle class="port" cx="40" cy="-3" r="3" fill="#536477"/><circle class="port" cx="80" cy="-3" r="3" fill="#536477"/><text class="slot" x="0" y="-10" 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="n21" data-id="21" transform="translate(390,486)" style="opacity: 1;"><path class="box" d="M12,0H151L163,22L151,44H12L0,22Z" fill="#f5e4fa"/><text class="label" text-anchor="middle" x="81.5" y="18"><tspan>BulkMemPhi except x</tspan><tspan class="node-id">  #21</tspan></text><text class="type" text-anchor="middle" y="35" x="81.5"/><title>#21 BulkMemPhi except x
+Type not computed
+
+[0] → — ASSOC
+[1] → #17 MEM
+[2] → #18 MEM</title><circle class="port" cx="0" cy="-3" r="3" fill="white"/><circle class="port" cx="54" cy="-3" r="3" fill="#536477"/><circle class="port" cx="109" cy="-3" r="3" fill="#536477"/><text class="slot" x="0" y="-10" text-anchor="middle">0</text><text class="slot" x="54" y="-10" text-anchor="middle">1</text><text class="slot" x="109" y="-10" text-anchor="middle">2</text></g><g class="node" id="n22" data-id="22" transform="translate(564,590)" 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>MemMerge</tspan><tspan class="node-id">  #22</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#22 MemMerge
+Type not computed
+
+[0] → — CTRL
+[1] → #21 MEM
+[2] → #20 MEM</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="n23" data-id="23" transform="translate(742,694)" 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">  #23</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#23 Return
+Type not computed
+
+[0] → — CTRL
+[1] → #22 MEM
+[2] → #5 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><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/docs/chapter11/example2c.gv b/docs/chapter11/example2c.gv
new file mode 100644
index 00000000..b32d186d
--- /dev/null
+++ b/docs/chapter11/example2c.gv
@@ -0,0 +1,15 @@
+digraph Memory {
+ // IR styles match graph/web/render.js; arrows are use -> definition.
+ graph [bgcolor="#fafbfc" fontname="Helvetica" nodesep=0.35 ranksep=0.55 splines=ortho];
+ node [fontname="Helvetica" fontsize=12 color="#758191" fontcolor="#152330" shape=box style="rounded,filled" fillcolor="#edf3f7"];
+ edge [fontname="Helvetica" fontsize=9 color="#536477" fontcolor="#364352" arrowsize=0.7 penwidth=1.5];
+
+ rankdir=RL;
+
+ M [label="Prior x memory" shape=octagon style="filled" fillcolor="#dcecff"];
+ L [label="Load p.x" shape=box style="rounded,filled" fillcolor="#edf3f7"];
+ S [label="Store p.x" shape=octagon style="filled" fillcolor="#dcecff"];
+ L -> M [color="#2867b2"]; S -> M [color="#2867b2"];
+ S -> L [style=dashed,xlabel="execute load first"];
+ Y [label="Store p.y: independent" shape=octagon style="filled" fillcolor="#dcecff"];
+}
diff --git a/docs/chapter11/example2c.json b/docs/chapter11/example2c.json
new file mode 100644
index 00000000..c25fa5b8
--- /dev/null
+++ b/docs/chapter11/example2c.json
@@ -0,0 +1,23 @@
+{
+ "ver": 1,
+ "comp": "chapter11/docs/example2c.svg",
+ "step": 1,
+ "roots": [13],
+ "scope": 0,
+ "nodes": [
+ {"id": 1, "label": "Start", "type": null, "kind": "CTRL", "edges": [], "proj": null},
+ {"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": 0, "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": "MEM#2", "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": "MEM#3", "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": 9, "label": "Load p.x", "type": null, "kind": "DATA", "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": 0, "role": "DATA", "label": null}], "proj": null},
+ {"id": 10, "label": "Store p.x", "type": "MEM#2", "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": 4, "role": "DATA", "label": null}], "proj": null},
+ {"id": 11, "label": "Store p.y", "type": "MEM#3", "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": 12, "label": "MemMerge", "type": null, "kind": "MEM", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 3, "role": "MEM", "label": null}, {"idx": 2, "def": 10, "role": "MEM", "label": null}, {"idx": 3, "def": 11, "role": "MEM", "label": null}], "proj": null},
+ {"id": 13, "label": "Return", "type": null, "kind": "CTRL", "edges": [{"idx": 0, "def": 0, "role": "CTRL", "label": null}, {"idx": 1, "def": 12, "role": "MEM", "label": null}, {"idx": 2, "def": 9, "role": "DATA", "label": null}], "proj": null}
+ ],
+ "groups": [
+ ]
+}
diff --git a/docs/chapter11/example2c.svg b/docs/chapter11/example2c.svg
new file mode 100644
index 00000000..01bbe10a
--- /dev/null
+++ b/docs/chapter11/example2c.svg
@@ -0,0 +1,121 @@
+<svg aria-label="Compiler graph" width="598" height="572" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 598 572" 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 ASSOC" id="e6i0" d="M267,46 C267,70 514,41 514,65" stroke="#9396a3" marker-start="url(#arrow-ASSOC)" style="opacity: 1;"><title>#6[0] → #1 · ASSOC</title></path><path class="edge MEM" id="e7i1" d="M84,118 L84,150 Q84,158 92,158 L181,158 Q186,158 186,163 L186,168" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#7[1] → #3 · MEM</title></path><path class="edge DATA" id="e7i2" d="M362,118 L362,140 Q362,148 354,148 L218,148 Q210,148 210,156 L210,168" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#7[2] → #5 · DATA</title></path><path class="edge DATA" id="e7i3" d="M514,118 L514,130 Q514,138 506,138 L242,138 Q234,138 234,146 L234,168" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#7[3] → #6 · DATA</title></path><path class="edge MEM" id="e8i1" d="M84,118 L84,150 Q84,158 92,158 L333,158 Q338,158 338,163 L338,168" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#8[1] → #3 · MEM</title></path><path class="edge DATA" id="e8i2" d="M362,118 L362,168" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#8[2] → #5 · DATA</title></path><path class="edge DATA" id="e8i3" d="M514,118 L514,130 Q514,138 506,138 L394,138 Q386,138 386,146 L386,168" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#8[3] → #6 · DATA</title></path><path class="edge MEM" id="e9i1" d="M198,224 L198,256 Q198,264 206,264 L439,264 Q447,264 447,272 L447,284" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#9[1] → #7 · MEM</title></path><path class="edge DATA" id="e9i2" d="M362,118 L362,140 Q362,148 370,148 L463,148 Q471,148 471,156 L471,284" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#9[2] → #5 · DATA</title></path><path class="edge MEM" id="e10i1" d="M198,224 L198,256 Q198,264 190,264 L151,264 Q143,264 143,272 L143,284" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#10[1] → #7 · MEM</title></path><path class="edge DATA" id="e10i2" d="M362,118 L362,140 Q362,148 354,148 L124,148 Q116,148 116,156 L116,266 Q116,274 124,274 L162,274 Q167,274 167,279 L167,284" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#10[2] → #5 · DATA</title></path><path class="edge DATA" id="e10i3" d="M204,118 L204,123 Q204,128 199,128 L135,128 Q127,128 127,136 L127,226 Q127,234 135,234 L183,234 Q191,234 191,242 L191,284" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#10[3] → #4 · DATA</title></path><path class="edge MEM" id="e11i1" d="M350,224 L350,229 Q350,234 345,234 L303,234 Q295,234 295,242 L295,284" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#11[1] → #8 · MEM</title></path><path class="edge DATA" id="e11i2" d="M362,118 L362,140 Q362,148 370,148 L412,148 Q420,148 420,156 L420,236 Q420,244 412,244 L327,244 Q319,244 319,252 L319,284" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#11[2] → #5 · DATA</title></path><path class="edge DATA" id="e11i3" d="M204,118 L204,123 Q204,128 209,128 L423,128 Q431,128 431,136 L431,246 Q431,254 423,254 L351,254 Q343,254 343,262 L343,284" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#11[3] → #4 · DATA</title></path><path class="edge MEM" id="e12i1" d="M84,118 L84,342 Q84,350 92,350 L123,350 Q131,350 131,358 L131,388" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#12[1] → #3 · MEM</title></path><path class="edge MEM" id="e12i2" d="M155,340 L155,388" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#12[2] → #10 · MEM</title></path><path class="edge MEM" id="e12i3" d="M307,340 L307,345 Q307,350 302,350 L187,350 Q179,350 179,358 L179,388" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#12[3] → #11 · MEM</title></path><path class="edge MEM" id="e13i1" d="M143,444 L143,449 Q143,454 148,454 L421,454 Q429,454 429,462 L429,492" stroke="#2867b2" marker-start="url(#arrow-MEM)" style="opacity: 1;"><title>#13[1] → #12 · MEM</title></path><path class="edge DATA" id="e13i2" d="M459,340 L459,492" stroke="#536477" marker-start="url(#arrow-DATA)" style="opacity: 1;"><title>#13[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,0H240V44H0Z" fill="#fff1c4"/><text class="label" text-anchor="middle" x="120" y="18"><tspan>Start</tspan><tspan class="node-id">  #1</tspan></text><text class="type" text-anchor="middle" y="35" x="120"/><title>#1 Start
+Type not computed
+
+</title></g><g class="node" id="n3" data-id="3" transform="translate(24,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(144,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(302,68)" 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] → — CTRL</title><circle class="port" cx="60" cy="-3" r="3" fill="white"/><text class="slot" x="60" y="-10" text-anchor="middle">0</text></g><g class="node" id="n6" data-id="6" transform="translate(454,68)" 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(138,174)" 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">MEM#2</text><title>#7 Store p.x
+MEM#2
+
+[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(290,174)" 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">MEM#3</text><title>#8 Store p.y
+MEM#3
+
+[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="n9" data-id="9" transform="translate(399,290)" 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] → #7 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(95,290)" 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">MEM#2</text><title>#10 Store p.x
+MEM#2
+
+[0] → — CTRL
+[1] → #7 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(247,290)" 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">MEM#3</text><title>#11 Store p.y
+MEM#3
+
+[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="n12" data-id="12" transform="translate(83,394)" 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>MemMerge</tspan><tspan class="node-id">  #12</tspan></text><text class="type" text-anchor="middle" y="35" x="60"/><title>#12 MemMerge
+Type not computed
+
+[0] → — CTRL
+[1] → #3 MEM
+[2] → #10 MEM
+[3] → #11 MEM</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="n13" data-id="13" transform="translate(369,498)" 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] → — CTRL
+[1] → #12 MEM
+[2] → #9 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><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/docs/chapter11/lattice.gv b/docs/chapter11/lattice.gv
new file mode 100644
index 00000000..93d99dd8
--- /dev/null
+++ b/docs/chapter11/lattice.gv
@@ -0,0 +1,133 @@
+digraph Lattice {
+ rankdir=TB;
+ newrank=true;
+ // Preserve the same-rank ordering edges through cluster layout.
+ remincross=false;
+ nodesep=0.22;
+ ranksep=0.65;
+ node [shape=ellipse, style=filled, fontname="DejaVu Sans", fontsize=13];
+ edge [color="#555555", arrowsize=0.6];
+
+ // These arrows are lattice-order edges, not use/definition edges.
+ TOP [label="⊤", fillcolor=black, fontcolor=white];
+ BOT [label="⊥", fillcolor=black, fontcolor=white];
+
+ subgraph cluster_control {
+ label="Control"; fontname="Arial"; color="#cccccc";
+ node [fillcolor="#ffff00"];
+ XCTRL [label="⊤:ctrl"];
+ CTRL [label="⊥:ctrl"];
+ XCTRL -> CTRL;
+ }
+
+ subgraph cluster_integer {
+ label="Integer"; fontname="Arial"; color="#cccccc";
+ node [fillcolor="#add8e6"];
+ INT_TOP [label="⊤:int"];
+ INT_BOT [label="⊥:int"];
+ INT_NEG [label="… −2, −1"];
+ INT_ZERO [label="0"];
+ INT_POS [label="1, 2, …"];
+ {rank=same; INT_NEG; INT_ZERO; INT_POS;}
+ INT_NEG -> INT_ZERO -> INT_POS [style=invis];
+ INT_TOP -> {INT_NEG INT_ZERO INT_POS};
+ {INT_NEG INT_ZERO INT_POS} -> INT_BOT;
+ }
+
+ subgraph cluster_pointer {
+ label="Pointer = nullability × struct type";
+ fontname="Arial"; color="#cccccc";
+ node [fillcolor="#008000", fontcolor=white];
+ PTR_TOP [label="⊤:ptr\n[*⊤:struct]"];
+ PTR_BOT [label="⊥:ptr\n[*⊥:struct?]"];
+ PTR_NULL [label="null"];
+ PTR_ANY [label="*⊥:struct\n[ptr any struct]"];
+ PTR_PERSON [label="*Person"];
+ PTR_S1 [label="*s1"];
+ PTR_MORE [label="*s2, …"];
+ PTR_PERSON_NIL [label="*Person?"];
+ PTR_S1_NIL [label="*s1?"];
+ PTR_MORE_NIL [label="*s2?, …"];
+ {rank=same; PTR_PERSON; PTR_S1; PTR_MORE; PTR_NULL;}
+ {rank=same; PTR_ANY; PTR_PERSON_NIL; PTR_S1_NIL; PTR_MORE_NIL;}
+ PTR_PERSON -> PTR_S1 -> PTR_MORE -> PTR_NULL [style=invis];
+ PTR_ANY -> PTR_PERSON_NIL -> PTR_S1_NIL -> PTR_MORE_NIL [style=invis];
+ PTR_TOP -> {PTR_PERSON PTR_S1 PTR_MORE PTR_NULL};
+ {PTR_PERSON PTR_S1 PTR_MORE} -> PTR_ANY;
+ PTR_PERSON -> PTR_PERSON_NIL;
+ PTR_S1 -> PTR_S1_NIL;
+ PTR_MORE -> PTR_MORE_NIL;
+ PTR_NULL -> {PTR_PERSON_NIL PTR_S1_NIL PTR_MORE_NIL};
+ {PTR_ANY PTR_PERSON_NIL PTR_S1_NIL PTR_MORE_NIL} -> PTR_BOT;
+ }
+
+ subgraph cluster_struct {
+ label="Struct\nnamed collection of named scalar types";
+ fontname="Arial"; color="#cccccc";
+ node [fillcolor="#90ee90"];
+ STRUCT_TOP [label="⊤:struct\n[struct top]"];
+ STRUCT_BOT [label="⊥:struct\n[struct bottom]"];
+ STRUCT_PERSON [label="Person\nage: int", shape=box];
+ STRUCT_S1 [label="s1\nx: int", shape=box];
+ STRUCT_S2 [label="s2\nx: int, y: int", shape=box];
+ STRUCT_MORE [label="…", shape=box];
+ {rank=same; STRUCT_PERSON; STRUCT_S1; STRUCT_S2; STRUCT_MORE;}
+ STRUCT_PERSON -> STRUCT_S1 -> STRUCT_S2 -> STRUCT_MORE [style=invis];
+ STRUCT_TOP -> {STRUCT_PERSON STRUCT_S1 STRUCT_S2 STRUCT_MORE};
+ {STRUCT_PERSON STRUCT_S1 STRUCT_S2 STRUCT_MORE} -> STRUCT_BOT;
+ }
+
+ subgraph cluster_memory {
+ label="Memory\nprecise alias slices"; fontname="Arial"; color="#cccccc";
+ node [fillcolor="#00008b", fontcolor=white];
+ MEM_TOP [label="⊤:mem"];
+ MEM_BOT [label="⊥:mem"];
+ MEM_ALIAS2 [label="MEM#2"];
+ MEM_ALIAS3 [label="MEM#3"];
+ MEM_MORE [label="…"];
+ {rank=same; MEM_ALIAS2; MEM_ALIAS3; MEM_MORE;}
+ MEM_ALIAS2 -> MEM_ALIAS3 -> MEM_MORE [style=invis];
+ MEM_TOP -> {MEM_ALIAS2 MEM_ALIAS3 MEM_MORE};
+ {MEM_ALIAS2 MEM_ALIAS3 MEM_MORE} -> MEM_BOT;
+ }
+
+ subgraph cluster_tuple {
+ label="Tuple\ncollection of types";
+ fontname="Arial"; color="#cccccc";
+ node [fillcolor="#ffc0cb"];
+ TUPLE_TOP [label="⊤:tuple"];
+ TUPLE_BOT [label="⊥:tuple"];
+ // 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};
+ {TUPLE1 TUPLE2 TUPLEN} -> TUPLE_BOT;
+ }
+
+ TOP -> {XCTRL INT_TOP PTR_TOP STRUCT_TOP MEM_TOP TUPLE_TOP};
+ {CTRL INT_BOT PTR_BOT STRUCT_BOT MEM_BOT TUPLE_BOT} -> BOT;
+ {rank=same; XCTRL; INT_TOP; PTR_TOP; STRUCT_TOP; MEM_TOP; TUPLE_TOP;}
+ {rank=same; CTRL; INT_BOT; PTR_BOT; STRUCT_BOT; MEM_BOT; TUPLE_BOT;}
+ XCTRL -> INT_TOP -> PTR_TOP -> STRUCT_TOP -> MEM_TOP -> TUPLE_TOP [style=invis];
+}
diff --git a/docs/chapter11/lattice.svg b/docs/chapter11/lattice.svg
new file mode 100644
index 00000000..8d2e2875
--- /dev/null
+++ b/docs/chapter11/lattice.svg
@@ -0,0 +1,644 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 12.2.0 (20241103.1931)
+ -->
+<!-- Title: Lattice Pages: 1 -->
+<svg width="1727pt" height="603pt"
+ viewBox="0.00 0.00 1727.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,-599.45 1723,-599.45 1723,4 -4,4"/>
+<g id="clust1" class="cluster">
+<title>cluster_control</title>
+<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,-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,-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,-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,-540.46 1523,-540.46 1523,-85.99 1241,-85.99"/>
+<text text-anchor="middle" x="1382" y="-523.16" font-family="Arial" font-size="14.00">Memory</text>
+<text text-anchor="middle" x="1382" y="-507.41" font-family="Arial" font-size="14.00">precise alias slices</text>
+</g>
+<g id="clust21" class="cluster">
+<title>cluster_tuple</title>
+<polygon fill="none" stroke="#cccccc" points="1531,-85.99 1531,-540.46 1711,-540.46 1711,-85.99 1531,-85.99"/>
+<text text-anchor="middle" x="1621" y="-523.16" font-family="Arial" font-size="14.00">Tuple</text>
+<text text-anchor="middle" x="1621" 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="887" cy="-577.45" rx="27" ry="18"/>
+<text text-anchor="middle" x="887" 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="-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="edge60" class="edge">
+<title>TOP&#45;&gt;XCTRL</title>
+<path fill="none" stroke="#555555" d="M859.92,-576.29C725.56,-575.4 135.66,-570.43 102,-551.45 82.17,-540.27 69.05,-517.37 61.41,-499.71"/>
+<polygon fill="#555555" stroke="#555555" points="63.45,-499.15 59.23,-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="-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="edge61" class="edge">
+<title>TOP&#45;&gt;INT_TOP</title>
+<path fill="none" stroke="#555555" d="M859.8,-576.38C764.27,-575.97 447.72,-572.97 408,-551.45 387.23,-540.19 372.29,-517.31 363.27,-499.67"/>
+<polygon fill="#555555" stroke="#555555" points="365.18,-498.79 360.66,-494.32 361.41,-500.63 365.18,-498.79"/>
+</g>
+<!-- PTR_TOP -->
+<g id="node10" class="node">
+<title>PTR_TOP</title>
+<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="edge62" class="edge">
+<title>TOP&#45;&gt;PTR_TOP</title>
+<path fill="none" stroke="#555555" d="M867.27,-564.92C841.1,-549.63 794.17,-522.21 759.3,-501.84"/>
+<polygon fill="#555555" stroke="#555555" points="760.48,-500.1 754.24,-498.88 758.36,-503.72 760.48,-500.1"/>
+</g>
+<!-- STRUCT_TOP -->
+<g id="node20" class="node">
+<title>STRUCT_TOP</title>
+<ellipse fill="#90ee90" stroke="black" cx="1020" cy="-474.96" rx="64.35" ry="28.99"/>
+<text text-anchor="middle" x="1020" y="-479.11" font-family="DejaVu Sans" font-size="13.00">⊤:struct</text>
+<text text-anchor="middle" x="1020" y="-462.61" font-family="DejaVu Sans" font-size="13.00">[struct top]</text>
+</g>
+<!-- TOP&#45;&gt;STRUCT_TOP -->
+<g id="edge63" class="edge">
+<title>TOP&#45;&gt;STRUCT_TOP</title>
+<path fill="none" stroke="#555555" d="M904.39,-563.31C923.88,-548.58 956.09,-524.25 981.56,-505"/>
+<polygon fill="#555555" stroke="#555555" points="982.82,-506.68 986.34,-501.39 980.29,-503.33 982.82,-506.68"/>
+</g>
+<!-- MEM_TOP -->
+<g id="node26" class="node">
+<title>MEM_TOP</title>
+<ellipse fill="#00008b" stroke="black" cx="1347" cy="-474.96" rx="44.7" ry="18"/>
+<text text-anchor="middle" x="1347" y="-470.86" font-family="DejaVu Sans" font-size="13.00" fill="white">⊤:mem</text>
+</g>
+<!-- TOP&#45;&gt;MEM_TOP -->
+<g id="edge64" class="edge">
+<title>TOP&#45;&gt;MEM_TOP</title>
+<path fill="none" stroke="#555555" d="M914.28,-576.34C985.84,-575.75 1177.55,-572.23 1237,-551.45 1270.57,-539.71 1303.45,-514.89 1324.38,-496.91"/>
+<polygon fill="#555555" stroke="#555555" points="1325.69,-498.56 1328.82,-493.03 1322.92,-495.39 1325.69,-498.56"/>
+</g>
+<!-- TUPLE_TOP -->
+<g id="node31" class="node">
+<title>TUPLE_TOP</title>
+<ellipse fill="#ffc0cb" stroke="black" cx="1584" cy="-474.96" rx="44.7" ry="18"/>
+<text text-anchor="middle" x="1584" y="-470.86" font-family="DejaVu Sans" font-size="13.00">⊤:tuple</text>
+</g>
+<!-- TOP&#45;&gt;TUPLE_TOP -->
+<g id="edge65" class="edge">
+<title>TOP&#45;&gt;TUPLE_TOP</title>
+<path fill="none" stroke="#555555" d="M914.19,-576.75C1029.19,-577.83 1472.18,-580.1 1527,-551.45 1548.39,-540.27 1564,-517.05 1573.43,-499.3"/>
+<polygon fill="#555555" stroke="#555555" points="1575.08,-500.68 1575.93,-494.38 1571.33,-498.78 1575.08,-500.68"/>
+</g>
+<!-- BOT -->
+<g id="node2" class="node">
+<title>BOT</title>
+<ellipse fill="black" stroke="black" cx="884" cy="-18" rx="27" ry="18"/>
+<text text-anchor="middle" x="884" y="-13.9" font-family="DejaVu Sans" font-size="13.00" fill="white">⊥</text>
+</g>
+<!-- CTRL -->
+<g id="node4" class="node">
+<title>CTRL</title>
+<ellipse fill="#ffff00" stroke="black" cx="53" cy="-111.99" rx="36.51" ry="18"/>
+<text text-anchor="middle" x="53" y="-107.89" font-family="DejaVu Sans" font-size="13.00">⊥:ctrl</text>
+</g>
+<!-- XCTRL&#45;&gt;CTRL -->
+<g id="edge1" class="edge">
+<title>XCTRL&#45;&gt;CTRL</title>
+<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 -->
+<!-- CTRL&#45;&gt;BOT -->
+<g id="edge66" class="edge">
+<title>CTRL&#45;&gt;BOT</title>
+<path fill="none" stroke="#555555" d="M69.29,-95.39C78.22,-87.83 89.91,-79.45 102,-75 242.26,-23.4 717.31,-19.12 849.57,-18.95"/>
+<polygon fill="#555555" stroke="#555555" points="849.19,-21.05 855.19,-18.94 849.18,-16.85 849.19,-21.05"/>
+</g>
+<!-- INT_NEG -->
+<g id="node7" class="node">
+<title>INT_NEG</title>
+<ellipse fill="#add8e6" stroke="black" cx="169" cy="-345.97" rx="55.45" ry="18"/>
+<text text-anchor="middle" x="169" y="-341.87" font-family="DejaVu Sans" font-size="13.00">… −2, −1</text>
+</g>
+<!-- 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="M330.56,-461.28C305.82,-447.02 265.13,-422.74 232,-398.97 218.63,-389.37 204.44,-377.8 192.92,-368"/>
+<polygon fill="#555555" stroke="#555555" points="194.57,-366.65 188.64,-364.34 191.83,-369.84 194.57,-366.65"/>
+</g>
+<!-- INT_ZERO -->
+<g id="node8" class="node">
+<title>INT_ZERO</title>
+<ellipse fill="#add8e6" stroke="black" cx="267" cy="-345.97" rx="27" ry="18"/>
+<text text-anchor="middle" x="267" y="-341.87" font-family="DejaVu Sans" font-size="13.00">0</text>
+</g>
+<!-- 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="M341.83,-457.59C331.49,-442.41 315.68,-419.18 302,-398.97 295.36,-389.15 288.04,-378.28 281.81,-369.03"/>
+<polygon fill="#555555" stroke="#555555" points="283.64,-367.97 278.55,-364.17 280.15,-370.32 283.64,-367.97"/>
+</g>
+<!-- INT_POS -->
+<g id="node9" class="node">
+<title>INT_POS</title>
+<ellipse fill="#add8e6" stroke="black" cx="353" cy="-345.97" rx="43.16" ry="18"/>
+<text text-anchor="middle" x="353" y="-341.87" font-family="DejaVu Sans" font-size="13.00">1, 2, …</text>
+</g>
+<!-- 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,-456.57C353,-434.53 353,-396.41 353,-371.44"/>
+<polygon fill="#555555" stroke="#555555" points="355.1,-371.66 353,-365.66 350.9,-371.66 355.1,-371.66"/>
+</g>
+<!-- INT_TOP&#45;&gt;PTR_TOP -->
+<!-- INT_BOT -->
+<g id="node6" class="node">
+<title>INT_BOT</title>
+<ellipse fill="#add8e6" stroke="black" cx="289" cy="-111.99" rx="33.44" ry="18"/>
+<text text-anchor="middle" x="289" y="-107.89" font-family="DejaVu Sans" font-size="13.00">⊥:int</text>
+</g>
+<!-- INT_BOT&#45;&gt;BOT -->
+<g id="edge67" class="edge">
+<title>INT_BOT&#45;&gt;BOT</title>
+<path fill="none" stroke="#555555" d="M316.06,-101.31C339.95,-93.13 375.88,-81.69 408,-75 571.35,-40.97 770.86,-25.89 849.65,-20.95"/>
+<polygon fill="#555555" stroke="#555555" points="849.52,-23.07 855.38,-20.6 849.26,-18.87 849.52,-23.07"/>
+</g>
+<!-- INT_NEG&#45;&gt;INT_BOT -->
+<g id="edge7" class="edge">
+<title>INT_NEG&#45;&gt;INT_BOT</title>
+<path fill="none" stroke="#555555" d="M177.82,-327.91C199.03,-286.92 252.87,-182.84 276.99,-136.21"/>
+<polygon fill="#555555" stroke="#555555" points="278.74,-137.4 279.63,-131.11 275.01,-135.47 278.74,-137.4"/>
+</g>
+<!-- INT_NEG&#45;&gt;INT_ZERO -->
+<!-- INT_ZERO&#45;&gt;INT_BOT -->
+<g id="edge8" class="edge">
+<title>INT_ZERO&#45;&gt;INT_BOT</title>
+<path fill="none" stroke="#555555" d="M268.65,-327.53C272.53,-286.71 282.19,-184.82 286.66,-137.66"/>
+<polygon fill="#555555" stroke="#555555" points="288.74,-137.99 287.21,-131.82 284.56,-137.59 288.74,-137.99"/>
+</g>
+<!-- INT_ZERO&#45;&gt;INT_POS -->
+<!-- INT_POS&#45;&gt;INT_BOT -->
+<g id="edge9" class="edge">
+<title>INT_POS&#45;&gt;INT_BOT</title>
+<path fill="none" stroke="#555555" d="M348.29,-327.91C337.06,-287.19 308.65,-184.2 295.66,-137.13"/>
+<polygon fill="#555555" stroke="#555555" points="297.74,-136.76 294.12,-131.53 293.69,-137.88 297.74,-136.76"/>
+</g>
+<!-- PTR_NULL -->
+<g id="node12" class="node">
+<title>PTR_NULL</title>
+<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="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="-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="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="-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="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="-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,-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 -->
+<g id="node11" class="node">
+<title>PTR_BOT</title>
+<ellipse fill="#008000" stroke="black" cx="748" cy="-111.99" rx="66.47" ry="28.99"/>
+<text text-anchor="middle" x="748" y="-116.14" font-family="DejaVu Sans" font-size="13.00" fill="white">⊥:ptr</text>
+<text text-anchor="middle" x="748" y="-99.64" font-family="DejaVu Sans" font-size="13.00" fill="white">[*⊥:struct?]</text>
+</g>
+<!-- PTR_BOT&#45;&gt;BOT -->
+<g id="edge68" class="edge">
+<title>PTR_BOT&#45;&gt;BOT</title>
+<path fill="none" stroke="#555555" d="M783.38,-87.06C807.3,-70.88 838.29,-49.92 859.52,-35.56"/>
+<polygon fill="#555555" stroke="#555555" points="860.46,-37.46 864.25,-32.36 858.1,-33.98 860.46,-37.46"/>
+</g>
+<!-- PTR_PERSON_NIL -->
+<g id="node17" class="node">
+<title>PTR_PERSON_NIL</title>
+<ellipse fill="#008000" stroke="black" cx="651" cy="-216.97" rx="50.33" ry="18"/>
+<text text-anchor="middle" x="651" y="-212.87" font-family="DejaVu Sans" font-size="13.00" fill="white">*Person?</text>
+</g>
+<!-- PTR_NULL&#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="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">
+<title>PTR_S1_NIL</title>
+<ellipse fill="#008000" stroke="black" cx="748" cy="-216.97" rx="30.37" ry="18"/>
+<text text-anchor="middle" x="748" y="-212.87" font-family="DejaVu Sans" font-size="13.00" fill="white">*s1?</text>
+</g>
+<!-- PTR_NULL&#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="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">
+<title>PTR_MORE_NIL</title>
+<ellipse fill="#008000" stroke="black" cx="840" cy="-216.97" rx="45.21" ry="18"/>
+<text text-anchor="middle" x="840" y="-212.87" font-family="DejaVu Sans" font-size="13.00" fill="white">*s2?, …</text>
+</g>
+<!-- PTR_NULL&#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="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">
+<title>PTR_ANY</title>
+<ellipse fill="#008000" stroke="black" cx="502" cy="-216.97" rx="82.38" ry="28.99"/>
+<text text-anchor="middle" x="502" y="-221.12" font-family="DejaVu Sans" font-size="13.00" fill="white">*⊥:struct</text>
+<text text-anchor="middle" x="502" y="-204.62" font-family="DejaVu Sans" font-size="13.00" fill="white">[ptr any struct]</text>
+</g>
+<!-- PTR_ANY&#45;&gt;PTR_BOT -->
+<g id="edge29" class="edge">
+<title>PTR_ANY&#45;&gt;PTR_BOT</title>
+<path fill="none" stroke="#555555" d="M554.17,-194.13C595.32,-176.91 652.51,-152.97 694.1,-135.56"/>
+<polygon fill="#555555" stroke="#555555" points="694.78,-137.55 699.5,-133.29 693.15,-133.67 694.78,-137.55"/>
+</g>
+<!-- PTR_ANY&#45;&gt;PTR_PERSON_NIL -->
+<!-- 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="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="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="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="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="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="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">
+<title>PTR_PERSON_NIL&#45;&gt;PTR_BOT</title>
+<path fill="none" stroke="#555555" d="M666.58,-199.43C680.58,-184.56 701.51,-162.35 718.65,-144.16"/>
+<polygon fill="#555555" stroke="#555555" points="719.75,-146.05 722.33,-140.24 716.69,-143.17 719.75,-146.05"/>
+</g>
+<!-- PTR_PERSON_NIL&#45;&gt;PTR_S1_NIL -->
+<!-- PTR_S1_NIL&#45;&gt;PTR_BOT -->
+<g id="edge31" class="edge">
+<title>PTR_S1_NIL&#45;&gt;PTR_BOT</title>
+<path fill="none" stroke="#555555" d="M748,-198.5C748,-184.97 748,-165.76 748,-148.97"/>
+<polygon fill="#555555" stroke="#555555" points="750.1,-148.97 748,-142.97 745.9,-148.97 750.1,-148.97"/>
+</g>
+<!-- PTR_S1_NIL&#45;&gt;PTR_MORE_NIL -->
+<!-- PTR_MORE_NIL&#45;&gt;PTR_BOT -->
+<g id="edge32" class="edge">
+<title>PTR_MORE_NIL&#45;&gt;PTR_BOT</title>
+<path fill="none" stroke="#555555" d="M825.62,-199.88C812.53,-185.22 792.8,-163.14 776.51,-144.9"/>
+<polygon fill="#555555" stroke="#555555" points="778.14,-143.58 772.58,-140.51 775.01,-146.38 778.14,-143.58"/>
+</g>
+<!-- STRUCT_PERSON -->
+<g id="node22" class="node">
+<title>STRUCT_PERSON</title>
+<polygon fill="#90ee90" stroke="black" points="976.5,-237.47 909.5,-237.47 909.5,-196.47 976.5,-196.47 976.5,-237.47"/>
+<text text-anchor="middle" x="943" y="-221.12" font-family="DejaVu Sans" font-size="13.00">Person</text>
+<text text-anchor="middle" x="943" y="-204.62" font-family="DejaVu Sans" font-size="13.00">age: int</text>
+</g>
+<!-- STRUCT_TOP&#45;&gt;STRUCT_PERSON -->
+<g id="edge36" class="edge">
+<title>STRUCT_TOP&#45;&gt;STRUCT_PERSON</title>
+<path fill="none" stroke="#555555" d="M1011.61,-446.07C996.71,-396.53 965.8,-293.77 951.1,-244.91"/>
+<polygon fill="#555555" stroke="#555555" points="953.15,-244.41 949.41,-239.27 949.12,-245.62 953.15,-244.41"/>
+</g>
+<!-- STRUCT_S1 -->
+<g id="node23" class="node">
+<title>STRUCT_S1</title>
+<polygon fill="#90ee90" stroke="black" points="1047,-237.47 993,-237.47 993,-196.47 1047,-196.47 1047,-237.47"/>
+<text text-anchor="middle" x="1020" y="-221.12" font-family="DejaVu Sans" font-size="13.00">s1</text>
+<text text-anchor="middle" x="1020" y="-204.62" font-family="DejaVu Sans" font-size="13.00">x: int</text>
+</g>
+<!-- STRUCT_TOP&#45;&gt;STRUCT_S1 -->
+<g id="edge37" class="edge">
+<title>STRUCT_TOP&#45;&gt;STRUCT_S1</title>
+<path fill="none" stroke="#555555" d="M1020,-445.58C1020,-395.91 1020,-293.85 1020,-245.08"/>
+<polygon fill="#555555" stroke="#555555" points="1022.1,-245.25 1020,-239.25 1017.9,-245.25 1022.1,-245.25"/>
+</g>
+<!-- STRUCT_S2 -->
+<g id="node24" class="node">
+<title>STRUCT_S2</title>
+<polygon fill="#90ee90" stroke="black" points="1154.88,-237.47 1063.12,-237.47 1063.12,-196.47 1154.88,-196.47 1154.88,-237.47"/>
+<text text-anchor="middle" x="1109" y="-221.12" font-family="DejaVu Sans" font-size="13.00">s2</text>
+<text text-anchor="middle" x="1109" y="-204.62" font-family="DejaVu Sans" font-size="13.00">x: int, y: int</text>
+</g>
+<!-- STRUCT_TOP&#45;&gt;STRUCT_S2 -->
+<g id="edge38" class="edge">
+<title>STRUCT_TOP&#45;&gt;STRUCT_S2</title>
+<path fill="none" stroke="#555555" d="M1029.69,-446.07C1046.92,-396.53 1082.65,-293.77 1099.64,-244.91"/>
+<polygon fill="#555555" stroke="#555555" points="1101.62,-245.61 1101.6,-239.25 1097.65,-244.23 1101.62,-245.61"/>
+</g>
+<!-- STRUCT_MORE -->
+<g id="node25" class="node">
+<title>STRUCT_MORE</title>
+<polygon fill="#90ee90" stroke="black" points="1225,-234.97 1171,-234.97 1171,-198.97 1225,-198.97 1225,-234.97"/>
+<text text-anchor="middle" x="1198" y="-212.87" font-family="DejaVu Sans" font-size="13.00">…</text>
+</g>
+<!-- STRUCT_TOP&#45;&gt;STRUCT_MORE -->
+<g id="edge39" class="edge">
+<title>STRUCT_TOP&#45;&gt;STRUCT_MORE</title>
+<path fill="none" stroke="#555555" d="M1038.71,-447.05C1073.74,-396.68 1148.63,-288.97 1181.71,-241.39"/>
+<polygon fill="#555555" stroke="#555555" points="1183.35,-242.73 1185.05,-236.6 1179.9,-240.33 1183.35,-242.73"/>
+</g>
+<!-- STRUCT_TOP&#45;&gt;MEM_TOP -->
+<!-- STRUCT_BOT -->
+<g id="node21" class="node">
+<title>STRUCT_BOT</title>
+<ellipse fill="#90ee90" stroke="black" cx="1020" cy="-111.99" rx="82.91" ry="28.99"/>
+<text text-anchor="middle" x="1020" y="-116.14" font-family="DejaVu Sans" font-size="13.00">⊥:struct</text>
+<text text-anchor="middle" x="1020" y="-99.64" font-family="DejaVu Sans" font-size="13.00">[struct bottom]</text>
+</g>
+<!-- STRUCT_BOT&#45;&gt;BOT -->
+<g id="edge69" class="edge">
+<title>STRUCT_BOT&#45;&gt;BOT</title>
+<path fill="none" stroke="#555555" d="M982.84,-85.85C959.17,-69.85 929.19,-49.57 908.49,-35.56"/>
+<polygon fill="#555555" stroke="#555555" points="909.78,-33.9 903.63,-32.28 907.42,-37.38 909.78,-33.9"/>
+</g>
+<!-- STRUCT_PERSON&#45;&gt;STRUCT_BOT -->
+<g id="edge40" class="edge">
+<title>STRUCT_PERSON&#45;&gt;STRUCT_BOT</title>
+<path fill="none" stroke="#555555" d="M957.85,-196.12C968.43,-181.96 982.92,-162.59 995.24,-146.11"/>
+<polygon fill="#555555" stroke="#555555" points="996.85,-147.46 998.76,-141.4 993.49,-144.94 996.85,-147.46"/>
+</g>
+<!-- STRUCT_PERSON&#45;&gt;STRUCT_S1 -->
+<!-- STRUCT_S1&#45;&gt;STRUCT_BOT -->
+<g id="edge41" class="edge">
+<title>STRUCT_S1&#45;&gt;STRUCT_BOT</title>
+<path fill="none" stroke="#555555" d="M1020,-196.12C1020,-182.64 1020,-164.43 1020,-148.49"/>
+<polygon fill="#555555" stroke="#555555" points="1022.1,-148.87 1020,-142.87 1017.9,-148.87 1022.1,-148.87"/>
+</g>
+<!-- STRUCT_S1&#45;&gt;STRUCT_S2 -->
+<!-- STRUCT_S2&#45;&gt;STRUCT_BOT -->
+<g id="edge42" class="edge">
+<title>STRUCT_S2&#45;&gt;STRUCT_BOT</title>
+<path fill="none" stroke="#555555" d="M1091.84,-196.12C1079.5,-181.83 1062.57,-162.25 1048.25,-145.68"/>
+<polygon fill="#555555" stroke="#555555" points="1050,-144.5 1044.49,-141.33 1046.82,-147.24 1050,-144.5"/>
+</g>
+<!-- STRUCT_S2&#45;&gt;STRUCT_MORE -->
+<!-- STRUCT_MORE&#45;&gt;STRUCT_BOT -->
+<g id="edge43" class="edge">
+<title>STRUCT_MORE&#45;&gt;STRUCT_BOT</title>
+<path fill="none" stroke="#555555" d="M1177.76,-198.72C1173.05,-195 1167.95,-191.22 1163,-187.98 1135.19,-169.81 1102.49,-152.39 1075.38,-138.93"/>
+<polygon fill="#555555" stroke="#555555" points="1076.64,-137.21 1070.33,-136.44 1074.78,-140.97 1076.64,-137.21"/>
+</g>
+<!-- MEM_ALIAS2 -->
+<g id="node28" class="node">
+<title>MEM_ALIAS2</title>
+<ellipse fill="#00008b" stroke="black" cx="1294" cy="-216.97" rx="45.21" ry="18"/>
+<text text-anchor="middle" x="1294" y="-212.87" font-family="DejaVu Sans" font-size="13.00" fill="white">MEM#2</text>
+</g>
+<!-- MEM_TOP&#45;&gt;MEM_ALIAS2 -->
+<g id="edge46" class="edge">
+<title>MEM_TOP&#45;&gt;MEM_ALIAS2</title>
+<path fill="none" stroke="#555555" d="M1343.44,-456.76C1334.24,-412.32 1309.64,-293.5 1299.01,-242.16"/>
+<polygon fill="#555555" stroke="#555555" points="1301.12,-242.02 1297.85,-236.57 1297.01,-242.88 1301.12,-242.02"/>
+</g>
+<!-- MEM_ALIAS3 -->
+<g id="node29" class="node">
+<title>MEM_ALIAS3</title>
+<ellipse fill="#00008b" stroke="black" cx="1400" cy="-216.97" rx="45.21" ry="18"/>
+<text text-anchor="middle" x="1400" y="-212.87" font-family="DejaVu Sans" font-size="13.00" fill="white">MEM#3</text>
+</g>
+<!-- MEM_TOP&#45;&gt;MEM_ALIAS3 -->
+<g id="edge47" class="edge">
+<title>MEM_TOP&#45;&gt;MEM_ALIAS3</title>
+<path fill="none" stroke="#555555" d="M1350.56,-456.76C1359.76,-412.32 1384.36,-293.5 1394.99,-242.16"/>
+<polygon fill="#555555" stroke="#555555" points="1396.99,-242.88 1396.15,-236.57 1392.88,-242.02 1396.99,-242.88"/>
+</g>
+<!-- MEM_MORE -->
+<g id="node30" class="node">
+<title>MEM_MORE</title>
+<ellipse fill="#00008b" stroke="black" cx="1488" cy="-216.97" rx="27" ry="18"/>
+<text text-anchor="middle" x="1488" y="-212.87" font-family="DejaVu Sans" font-size="13.00" fill="white">…</text>
+</g>
+<!-- MEM_TOP&#45;&gt;MEM_MORE -->
+<g id="edge48" class="edge">
+<title>MEM_TOP&#45;&gt;MEM_MORE</title>
+<path fill="none" stroke="#555555" d="M1356.25,-457.16C1380.82,-412.56 1447.72,-291.1 1475.57,-240.54"/>
+<polygon fill="#555555" stroke="#555555" points="1477.29,-241.76 1478.35,-235.49 1473.62,-239.74 1477.29,-241.76"/>
+</g>
+<!-- MEM_TOP&#45;&gt;TUPLE_TOP -->
+<!-- MEM_BOT -->
+<g id="node27" class="node">
+<title>MEM_BOT</title>
+<ellipse fill="#00008b" stroke="black" cx="1347" cy="-111.99" rx="44.7" ry="18"/>
+<text text-anchor="middle" x="1347" y="-107.89" font-family="DejaVu Sans" font-size="13.00" fill="white">⊥:mem</text>
+</g>
+<!-- MEM_BOT&#45;&gt;BOT -->
+<g id="edge70" class="edge">
+<title>MEM_BOT&#45;&gt;BOT</title>
+<path fill="none" stroke="#555555" d="M1315.14,-99.08C1293.38,-91.31 1263.76,-81.46 1237,-75 1121.65,-47.17 981.66,-29.67 918.21,-22.6"/>
+<polygon fill="#555555" stroke="#555555" points="918.55,-20.52 912.36,-21.95 918.09,-24.7 918.55,-20.52"/>
+</g>
+<!-- MEM_ALIAS2&#45;&gt;MEM_BOT -->
+<g id="edge49" class="edge">
+<title>MEM_ALIAS2&#45;&gt;MEM_BOT</title>
+<path fill="none" stroke="#555555" d="M1302.75,-198.97C1311.5,-181.97 1325.02,-155.71 1334.81,-136.68"/>
+<polygon fill="#555555" stroke="#555555" points="1336.66,-137.66 1337.54,-131.37 1332.93,-135.74 1336.66,-137.66"/>
+</g>
+<!-- MEM_ALIAS2&#45;&gt;MEM_ALIAS3 -->
+<!-- MEM_ALIAS3&#45;&gt;MEM_BOT -->
+<g id="edge50" class="edge">
+<title>MEM_ALIAS3&#45;&gt;MEM_BOT</title>
+<path fill="none" stroke="#555555" d="M1391.25,-198.97C1382.5,-181.97 1368.98,-155.71 1359.19,-136.68"/>
+<polygon fill="#555555" stroke="#555555" points="1361.07,-135.74 1356.46,-131.37 1357.34,-137.66 1361.07,-135.74"/>
+</g>
+<!-- MEM_ALIAS3&#45;&gt;MEM_MORE -->
+<!-- MEM_MORE&#45;&gt;MEM_BOT -->
+<g id="edge51" class="edge">
+<title>MEM_MORE&#45;&gt;MEM_BOT</title>
+<path fill="none" stroke="#555555" d="M1471.42,-202.39C1465.65,-197.74 1459.09,-192.57 1453,-187.98 1426.93,-168.37 1396.47,-146.96 1374.9,-132.06"/>
+<polygon fill="#555555" stroke="#555555" points="1376.17,-130.38 1370.03,-128.71 1373.78,-133.84 1376.17,-130.38"/>
+</g>
+<!-- TUPLE1 -->
+<g id="node33" class="node">
+<title>TUPLE1</title>
+<polygon fill="#ffc0cb" stroke="none" points="1582.88,-372.47 1539.12,-372.47 1539.12,-319.47 1582.88,-319.47 1582.88,-372.47"/>
+<polygon fill="#ffc0cb" stroke="none" points="1539.12,-345.97 1539.12,-372.47 1582.88,-372.47 1582.88,-345.97 1539.12,-345.97"/>
+<polygon fill="none" stroke="black" points="1539.12,-345.97 1539.12,-372.47 1582.88,-372.47 1582.88,-345.97 1539.12,-345.97"/>
+<text text-anchor="start" x="1544.12" y="-355.12" font-family="DejaVu Sans" font-size="13.00">tuple</text>
+<polygon fill="black" stroke="none" points="1539.12,-319.47 1539.12,-345.97 1582.88,-345.97 1582.88,-319.47 1539.12,-319.47"/>
+<polygon fill="none" stroke="black" points="1539.12,-319.47 1539.12,-345.97 1582.88,-345.97 1582.88,-319.47 1539.12,-319.47"/>
+<text text-anchor="start" x="1546.38" y="-328.62" font-family="DejaVu Sans" font-size="13.00" fill="white">type</text>
+</g>
+<!-- TUPLE_TOP&#45;&gt;TUPLE1 -->
+<g id="edge54" class="edge">
+<title>TUPLE_TOP&#45;&gt;TUPLE1</title>
+<path fill="none" stroke="#555555" d="M1580.85,-456.57C1577.26,-436.74 1571.31,-403.91 1566.86,-379.34"/>
+<polygon fill="#555555" stroke="#555555" points="1568.94,-379.03 1565.81,-373.5 1564.81,-379.77 1568.94,-379.03"/>
+</g>
+<!-- TUPLE2 -->
+<g id="node34" class="node">
+<title>TUPLE2</title>
+<polygon fill="#ffc0cb" stroke="none" points="1642.88,-385.72 1599.12,-385.72 1599.12,-306.22 1642.88,-306.22 1642.88,-385.72"/>
+<polygon fill="#ffc0cb" stroke="none" points="1599.12,-359.22 1599.12,-385.72 1642.88,-385.72 1642.88,-359.22 1599.12,-359.22"/>
+<polygon fill="none" stroke="black" points="1599.12,-359.22 1599.12,-385.72 1642.88,-385.72 1642.88,-359.22 1599.12,-359.22"/>
+<text text-anchor="start" x="1604.12" y="-368.37" font-family="DejaVu Sans" font-size="13.00">tuple</text>
+<polygon fill="black" stroke="none" points="1599.12,-332.72 1599.12,-359.22 1642.88,-359.22 1642.88,-332.72 1599.12,-332.72"/>
+<polygon fill="none" stroke="black" points="1599.12,-332.72 1599.12,-359.22 1642.88,-359.22 1642.88,-332.72 1599.12,-332.72"/>
+<text text-anchor="start" x="1606.38" y="-341.87" font-family="DejaVu Sans" font-size="13.00" fill="white">type</text>
+<polygon fill="black" stroke="none" points="1599.12,-306.22 1599.12,-332.72 1642.88,-332.72 1642.88,-306.22 1599.12,-306.22"/>
+<polygon fill="none" stroke="black" points="1599.12,-306.22 1599.12,-332.72 1642.88,-332.72 1642.88,-306.22 1599.12,-306.22"/>
+<text text-anchor="start" x="1606.38" y="-315.37" font-family="DejaVu Sans" font-size="13.00" fill="white">type</text>
+</g>
+<!-- TUPLE_TOP&#45;&gt;TUPLE2 -->
+<g id="edge55" class="edge">
+<title>TUPLE_TOP&#45;&gt;TUPLE2</title>
+<path fill="none" stroke="#555555" d="M1588.99,-456.83C1593.76,-440.46 1601.17,-415.04 1607.65,-392.78"/>
+<polygon fill="#555555" stroke="#555555" points="1609.64,-393.48 1609.3,-387.13 1605.6,-392.3 1609.64,-393.48"/>
+</g>
+<!-- TUPLEN -->
+<g id="node35" class="node">
+<title>TUPLEN</title>
+<polygon fill="#ffc0cb" stroke="none" points="1702.88,-398.97 1659.12,-398.97 1659.12,-292.97 1702.88,-292.97 1702.88,-398.97"/>
+<polygon fill="#ffc0cb" stroke="none" points="1659.12,-372.47 1659.12,-398.97 1702.88,-398.97 1702.88,-372.47 1659.12,-372.47"/>
+<polygon fill="none" stroke="black" points="1659.12,-372.47 1659.12,-398.97 1702.88,-398.97 1702.88,-372.47 1659.12,-372.47"/>
+<text text-anchor="start" x="1664.12" y="-381.62" font-family="DejaVu Sans" font-size="13.00">tuple</text>
+<polygon fill="black" stroke="none" points="1659.12,-345.97 1659.12,-372.47 1702.88,-372.47 1702.88,-345.97 1659.12,-345.97"/>
+<polygon fill="none" stroke="black" points="1659.12,-345.97 1659.12,-372.47 1702.88,-372.47 1702.88,-345.97 1659.12,-345.97"/>
+<text text-anchor="start" x="1666.38" y="-355.12" font-family="DejaVu Sans" font-size="13.00" fill="white">type</text>
+<polygon fill="black" stroke="none" points="1659.12,-319.47 1659.12,-345.97 1702.88,-345.97 1702.88,-319.47 1659.12,-319.47"/>
+<polygon fill="none" stroke="black" points="1659.12,-319.47 1659.12,-345.97 1702.88,-345.97 1702.88,-319.47 1659.12,-319.47"/>
+<text text-anchor="start" x="1674.62" y="-328.62" font-family="DejaVu Sans" font-size="13.00" fill="white">…</text>
+<polygon fill="black" stroke="none" points="1659.12,-292.97 1659.12,-319.47 1702.88,-319.47 1702.88,-292.97 1659.12,-292.97"/>
+<polygon fill="none" stroke="black" points="1659.12,-292.97 1659.12,-319.47 1702.88,-319.47 1702.88,-292.97 1659.12,-292.97"/>
+<text text-anchor="start" x="1666.38" y="-302.12" font-family="DejaVu Sans" font-size="13.00" fill="white">type</text>
+</g>
+<!-- TUPLE_TOP&#45;&gt;TUPLEN -->
+<g id="edge56" class="edge">
+<title>TUPLE_TOP&#45;&gt;TUPLEN</title>
+<path fill="none" stroke="#555555" d="M1600.21,-457.82C1614.47,-443.17 1635.34,-420.61 1651,-398.97 1652.4,-397.02 1653.8,-395.01 1655.17,-392.95"/>
+<polygon fill="#555555" stroke="#555555" points="1656.91,-394.14 1658.41,-387.96 1653.38,-391.86 1656.91,-394.14"/>
+</g>
+<!-- TUPLE_BOT -->
+<g id="node32" class="node">
+<title>TUPLE_BOT</title>
+<ellipse fill="#ffc0cb" stroke="black" cx="1602" cy="-111.99" rx="44.7" ry="18"/>
+<text text-anchor="middle" x="1602" y="-107.89" font-family="DejaVu Sans" font-size="13.00">⊥:tuple</text>
+</g>
+<!-- TUPLE_BOT&#45;&gt;BOT -->
+<g id="edge71" class="edge">
+<title>TUPLE_BOT&#45;&gt;BOT</title>
+<path fill="none" stroke="#555555" d="M1577.54,-96.47C1563.29,-88.74 1544.69,-79.84 1527,-75 1410.53,-43.12 1034.45,-25.23 918.41,-20.37"/>
+<polygon fill="#555555" stroke="#555555" points="918.75,-18.28 912.67,-20.13 918.57,-22.48 918.75,-18.28"/>
+</g>
+<!-- TUPLE1&#45;&gt;TUPLE_BOT -->
+<g id="edge57" class="edge">
+<title>TUPLE1&#45;&gt;TUPLE_BOT</title>
+<path fill="none" stroke="#555555" d="M1565.47,-319.7C1573.39,-274.85 1589.82,-181.9 1597.66,-137.56"/>
+<polygon fill="#555555" stroke="#555555" points="1599.69,-138.14 1598.66,-131.86 1595.55,-137.4 1599.69,-138.14"/>
+</g>
+<!-- TUPLE1&#45;&gt;TUPLE2 -->
+<!-- TUPLE2&#45;&gt;TUPLE_BOT -->
+<g id="edge58" class="edge">
+<title>TUPLE2&#45;&gt;TUPLE_BOT</title>
+<path fill="none" stroke="#555555" d="M1617.84,-306.4C1613.91,-258.37 1607.3,-177.75 1604,-137.46"/>
+<polygon fill="#555555" stroke="#555555" points="1606.13,-137.74 1603.55,-131.93 1601.95,-138.08 1606.13,-137.74"/>
+</g>
+<!-- TUPLE2&#45;&gt;TUPLEN -->
+<!-- TUPLEN&#45;&gt;TUPLE_BOT -->
+<g id="edge59" class="edge">
+<title>TUPLEN&#45;&gt;TUPLE_BOT</title>
+<path fill="none" stroke="#555555" d="M1663.37,-293.19C1646.79,-244.5 1622.69,-173.75 1610.2,-137.07"/>
+<polygon fill="#555555" stroke="#555555" points="1612.27,-136.64 1608.35,-131.63 1608.3,-137.99 1612.27,-136.64"/>
+</g>
+</g>
+</svg>
diff --git a/src/main/java/com/seaofnodes/simple/IRPrinter.java b/src/main/java/com/seaofnodes/simple/IRPrinter.java
index 621bc2d6..d5ab15fe 100644
--- a/src/main/java/com/seaofnodes/simple/IRPrinter.java
+++ b/src/main/java/com/seaofnodes/simple/IRPrinter.java
@@ -9,6 +9,7 @@ public final class IRPrinter extends IRAdapter<Node> {
new com.seaofnodes.print.IRPrinter<>(new IRPrinter());
@Override public String type(Node n) { return n._type==null ? "" : n._type.str(); }
@Override public int inputColumns() { return 3; }
+ @Override public String inputMark(Node n, Node def) { return n instanceof MemMergeNode && def instanceof MemMergeNode ? "^" : " "; }
@Override public Kind kind(Node n) {
if( n instanceof StartNode ) return Kind.START;
if( n instanceof StopNode ) return Kind.STOP;
diff --git a/src/main/java/com/seaofnodes/simple/Parser.java b/src/main/java/com/seaofnodes/simple/Parser.java
index 7734f4df..df8d0b1c 100644
--- a/src/main/java/com/seaofnodes/simple/Parser.java
+++ b/src/main/java/com/seaofnodes/simple/Parser.java
@@ -33,6 +33,10 @@ public class Parser {
public StopNode STOP;
+ // Field identities are independent of the memory graph and Start's tuple.
+ private final HashMap<String,Integer> _aliases = new HashMap<>();
+ private int _alias = 2;
+
// The Lexer. Thin wrapper over a byte[] buffer with a cursor.
private final Lexer _lexer;
@@ -200,6 +204,8 @@ public class Parser {
require("}");
// Build and install the TypeStruct
TypeStruct ts = TypeStruct.make(typeName, fields);
+ _aliases.put(typeName,_alias);
+ _alias += ts._fields.length;
OBJS.put(typeName, ts); // Insert the struct name in the collection of all struct names
return parseStatement();
}
@@ -589,8 +595,10 @@ public class Parser {
private Node newStruct( TypeStruct obj ) {
Node n = new NewNode(TypeMemPtr.make(obj), ctrl()).peephole().keep();
Node initValue = new ConstantNode(TypeInteger.constant(0)).peephole();
+ int alias = _aliases.get(obj._name);
for( Field field : obj._fields ) {
- mem(new StoreNode(field._fname, mem(), n, initValue).peephole());
+ store(field._fname,alias,n,initValue);
+ alias++;
}
return n.unkeep();
}
@@ -599,6 +607,13 @@ public class Parser {
private Node mem() { return _scope.lookup("$mem"); }
private Node mem(Node n) { return _scope.update("$mem",n); }
+ private void store(String name, int alias, Node ptr, Node val) {
+ Node prior = mem().keep();
+ Node st = new StoreNode(name,alias,prior,ptr,val).peephole();
+ mem(new MemMergeNode(prior,alias,st).peephole());
+ if( prior.unkeep().isUnused() ) prior.kill();
+ }
+
/**
* Parse postfix expression. For now this is just a field
* expression, but in future could be array index too.
@@ -616,19 +631,20 @@ public class Parser {
String name = requireId().intern();
int idx = ptr._obj==null ? -1 : ptr._obj.find(name);
if( idx == -1 ) throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'");
+ int alias = _aliases.get(ptr._obj._name)+idx;
if( match("=") ) {
// Disambiguate "obj.fld==x" boolean test from "obj.fld=x" field assignment
if( peek('=') ) _lexer._position--;
else {
Node val = parseExpression();
- mem(new StoreNode(name, mem(), expr, val).peephole());
+ store(name,alias,expr,val);
return expr; // "obj.a = expr" returns the expression while updating memory
}
}
Type declaredType = ptr._obj._fields[idx]._type;
- return parsePostfix(new LoadNode(name, declaredType, mem(), expr).peephole());
+ return parsePostfix(new LoadNode(name, alias, declaredType, mem(), expr).peephole());
}
/**
diff --git a/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
new file mode 100644
index 00000000..8d512f21
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
@@ -0,0 +1,237 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.IterPeeps;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeMem;
+import java.util.BitSet;
+
+/**
+ * A Phi for bulk memory. It covers all aliases except {@link #_aliases};
+ * those aliases must be represented by parallel precise MemPhis at the same
+ * program slice.
+ */
+public class BulkMemPhiNode extends PhiNode {
+
+ private static final BitSet EMPTY = new BitSet();
+
+ // Excluded Aliases; empty means NO exclusions.
+ // Each split clones the exclusions; published sets are never mutated.
+ public final BitSet _aliases;
+
+ public BulkMemPhiNode(String label, Node... inputs) {
+ this(label,EMPTY,inputs);
+ }
+
+ private BulkMemPhiNode(String label, BitSet aliases, Node... inputs) {
+ super(label,TypeMem.BOT,inputs);
+ _aliases = aliases;
+ }
+
+ public boolean isSplit(int alias) { return _aliases.get(alias); }
+
+ @Override public String label() { return "BulkPhi"+_aliases; }
+
+ @Override
+ public Type compute() {
+ Type t = super.compute();
+ return t instanceof TypeMem mem ? mem :
+ t==Type.BOTTOM ? TypeMem.BOT :
+ TypeMem.TOP;
+ }
+
+ @Override
+ public Node idealize() {
+ Node progress = super.idealize();
+ if( progress != null ) return progress;
+ if( ((RegionNode)region()).inProgress() || region().nIns()<=1 )
+ return null; // Input is in-progress
+
+ // "Peek through" a MemMerge that covers this alias set on its default
+ for( int i=1; i<nIns(); i++ )
+ if( in(i) instanceof MemMergeNode mmm && canPeek(mmm) ) {
+ setDef(i,IterPeeps.add(mmm.in(1)));
+ return this;
+ }
+
+ // If any input or output uses or defines a specific alias we cover,
+ // make a private MemPhi for it, and add it to the excluded list.
+ for( int i=1; i<nIns(); i++ ) {
+ int alias = inputAlias(in(i));
+ if( alias!=0 )
+ return slice(alias);
+ }
+
+ for( int i=0; i<_outputs.size(); i++ ) {
+ int alias = outputAlias(_outputs.get(i));
+ if( alias!=0 )
+ return slice(alias);
+ }
+
+ return null;
+ }
+
+ static public boolean checkMem(Node n, int alias) {
+ for( Node m : n._inputs )
+ // Bulk memory supplies this alias
+ if( m instanceof BulkMemPhiNode bulk ) {
+ if( bulk._aliases.get(alias) )
+ return false;
+ } else if( m instanceof MemMergeNode mmm &&
+ mmm.in(1) instanceof BulkMemPhiNode bulk &&
+ bulk._aliases.get(alias) &&
+ (alias >= mmm.nIns() || mmm.in(alias)==null) )
+ return false;
+ return true;
+ }
+
+
+ // True if the MemMerge has no precise slice still covered by this Phi.
+ private boolean canPeek(MemMergeNode mmm) {
+ return missingAlias(mmm,false)==0;
+ }
+
+ // Return an alias required by an input but still covered by this Phi, or 0.
+ private int inputAlias(Node n) {
+ return switch(n) {
+ case ProjNode proj -> 0;
+ case ConstantNode con -> 0;
+ case MemMergeNode mmm -> missingAlias(mmm,false);
+ case MemOpNode mem -> unsplit(mem._alias);
+ case BulkMemPhiNode bulk -> missingAlias(bulk);
+ default -> throw new AssertionError("Unexpected memory input "+n);
+ };
+ }
+
+ // Return an alias required by a user but still covered by this Phi, or 0.
+ private int outputAlias(Node use) {
+ // This decision depends backwards on an immediate user's alias/set.
+ // Normal graph-neighbor enqueueing flows from defs to uses, so retain
+ // an explicit forward dependency to revisit this Phi when the user
+ // sharpens.
+ use.addDepForwards(this);
+ return switch(use) {
+ case ScopeNode scope -> 0;
+ case MemMergeNode mmm -> missingAlias(mmm,true);
+ case BulkMemPhiNode bulk -> missingAlias(bulk);
+ case MemOpNode mem -> unsplit(mem._alias);
+ case MemPhiNode phi -> unsplit(phi._alias);
+ default -> {
+ assert use instanceof ReturnNode
+ : "Unexpected bulk-memory user "+use;
+ yield 0;
+ }
+ };
+ }
+
+ // Alias zero/one is bulk, and an already excluded precise alias cannot
+ // trigger another split of this Phi.
+ private int unsplit(int alias) {
+ if( alias <= 1 ) return 0;
+ assert !_aliases.get(alias);
+ return alias;
+ }
+
+ // First alias excluded by the neighboring bulk Phi but not by this one.
+ private int missingAlias(BulkMemPhiNode bulk) {
+ for( int alias = bulk._aliases.nextSetBit(0); alias >= 0; alias = bulk._aliases.nextSetBit(alias+1) )
+ if( !_aliases.get(alias) )
+ return alias;
+ return 0;
+ }
+
+ // First precise MemMerge slice still covered by this Phi. When inspecting
+ // a user, ignore slots which point back to this Phi.
+ private int missingAlias(MemMergeNode mmm, boolean user) {
+ for( int alias=2; alias<mmm.nIns(); alias++ )
+ if( mmm.in(alias)!=null &&
+ mmm.alias(alias)!=mmm.in(1) &&
+ !_aliases.get(alias) )
+ { assert (!user || mmm.in(alias)!=this); return alias; }
+ return 0;
+ }
+
+ // Slice out given alias
+ private MemMergeNode slice( int alias ) {
+ assert !_aliases.get(alias);
+ // Inputs are the same; MemPhi will sharpen his own inputs. If a
+ // parallel precise Phi already exists, reuse it instead of building a
+ // duplicate for the same Region/alias pair.
+ Node mem = precisePhi(alias);
+
+ BitSet aliases = ((BitSet)_aliases.clone());
+ aliases.set(alias);
+ BulkMemPhiNode bphi = new BulkMemPhiNode(_label,aliases);
+ for( int i=0; i<nIns(); i++ )
+ bphi.addDef(in(i));
+ // Installing bphi creates a new bulk user with a different exclusion
+ // set. Queue the original memory inputs before bphi itself can peep
+ // away and hide those neighbor relationships.
+ for( int i=1; i<bphi.nIns(); i++ )
+ IterPeeps.add(bphi.in(i));
+ // Do not peephole bphi here, as BulkMemPhi can recursively start a
+ // second bulk rewrite before this one has finished. Let the worklist
+ // discover any further splits
+ bphi.setType(TypeMem.BOT);
+ Node bulk = bphi;
+ IterPeeps.add(bulk);
+ IterPeeps.add(mem);
+ return aggregate(bulk,alias,mem);
+ }
+
+ // Build aggregate memory over a bulk Phi, preserving every precise slice
+ // which the bulk has already excluded. The named alias is newly supplied
+ // (or replaces its old slice).
+ MemMergeNode aggregate(Node bulk, int alias, Node precise) {
+ MemMergeNode mmm = new MemMergeNode(bulk);
+ for( int old = _aliases.nextSetBit(0); old >= 0; old = _aliases.nextSetBit(old+1) )
+ { assert old != alias; mmm.alias(old,precisePhi(old));}
+ mmm.alias(alias,precise);
+ assert checkMerge(mmm);
+ return mmm;
+ }
+
+ // Find or make the precise Phi parallel to this bulk Phi at the same merge.
+ private Node precisePhi(int alias) {
+ MemPhiNode mphi = _findPhi(alias);
+ if( mphi!=null ) return mphi;
+
+ mphi = new MemPhiNode("$"+alias,alias);
+ mphi.addDef(region());
+ // Due to cycles, must set before calling peephole
+ mphi.setType(TypeMem.make(alias));
+ for( int i=1; i<nIns(); i++ )
+ mphi.addDef(IterPeeps.add(preciseInput(in(i),alias)));
+ return IterPeeps.add(mphi);
+ }
+
+ MemPhiNode _findPhi(int alias) {
+ for( Node use : region()._outputs )
+ if( use instanceof MemPhiNode mphi && mphi._alias==alias )
+ return mphi;
+ return null;
+ }
+
+ // Select an alias already split out of a predecessor's bulk memory.
+ private Node preciseInput(Node mem, int alias) {
+ if( mem instanceof MemMergeNode mmm )
+ return mmm.alias(alias);
+ if( mem instanceof BulkMemPhiNode bulk && bulk.isSplit(alias) )
+ return bulk.precisePhi(alias);
+ return mem;
+ }
+
+ // Every alias excluded by a bulk default must be supplied explicitly.
+ static boolean checkMerge(MemMergeNode mmm) {
+ if( !(mmm.in(1) instanceof BulkMemPhiNode bulk) ) return true;
+ for( int alias = bulk._aliases.nextSetBit(0); alias >= 0; alias = bulk._aliases.nextSetBit(alias+1) )
+ if( alias >= mmm.nIns() || mmm.in(alias)==null )
+ return false;
+ return true;
+ }
+
+ @Override public boolean eq(Node n) {
+ return _aliases.equals(((BulkMemPhiNode)n)._aliases) && super.eq(n);
+ }
+
+ @Override int hash() { return _aliases.hashCode(); }
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/src/main/java/com/seaofnodes/simple/node/LoadNode.java
index 31c7699c..9bf36459 100644
--- a/src/main/java/com/seaofnodes/simple/node/LoadNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/LoadNode.java
@@ -2,7 +2,10 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.print.ExprPrinter;
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.IterPeeps;
import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.Field;
/**
@@ -16,11 +19,11 @@ public class LoadNode extends MemOpNode {
* Load a value from a ptr.field.
*
* @param name The field we are loading
- * @param memSlice The whole-memory node - this is updated after a Store
+ * @param memSlice The memory alias node - this is updated after a Store
* @param memPtr The ptr to the struct from where we load a field
*/
- public LoadNode(String name, Type glb, Node memSlice, Node memPtr) {
- super(name, memSlice, memPtr, null);
+ public LoadNode(String name, int alias, Type glb, Node memSlice, Node memPtr) {
+ super(name, alias, memSlice, memPtr, null);
_declaredType = glb;
}
@@ -28,15 +31,16 @@ public class LoadNode extends MemOpNode {
return super.canDrop(other,dep) && _declaredType==((LoadNode)other)._declaredType && !clobbered(dep);
}
- // A Store directly clobbers this memory. A Phi might lead to a clobber
- // through a cycle; conservatively stop rather than searching beyond it.
+ // Check only immediate memory users. Stores clobber memory; Phis and
+ // aggregates might lead to a clobber, so stop rather than search further.
private boolean clobbered(Node dep) {
Node mem = mem();
mem.addDepForwards(dep);
for( Node use : mem._outputs ) {
if( use==null ) continue;
use.addDepForwards(dep);
- if( use instanceof StoreNode || use instanceof PhiNode ) return true;
+ if( use instanceof StoreNode || use instanceof PhiNode ||
+ use instanceof MemMergeNode ) return true;
}
return false;
}
@@ -54,10 +58,16 @@ public class LoadNode extends MemOpNode {
@Override
public Node idealize() {
+ if( mem() instanceof MemMergeNode merge ) {
+ setDef(1,IterPeeps.add(merge.alias(_alias)));
+ return this;
+ }
+
// Simple Load-after-Store on same address.
if( mem() instanceof StoreNode st &&
- ptr() == st.ptr() && _name.equals(st._name) ) { // Must check same object
+ ptr() == st.ptr() && _alias==st._alias ) { // Must check same object
+ assert Utils.eq(_name,st._name); // Equiv class aliasing is perfect
return st.val();
}
@@ -68,13 +78,13 @@ public class LoadNode extends MemOpNode {
// if( pred ) ptr.x = e0; val = pred ? e0
// else ptr.x = e1; : e1;
// val = ptr.x; ptr.x = val;
- if( mem() instanceof PhiNode memphi && memphi.region()._type == Type.CONTROL && memphi.nIns()== 3 ) {
+ if( mem() instanceof MemPhiNode memphi && memphi.region()._type == Type.CONTROL && memphi.nIns()== 3 ) {
// Profit on RHS/Loop backedge
if( profit(memphi,2) ||
// Else must not be a loop to count profit on LHS.
(!(memphi.region() instanceof LoopNode) && profit(memphi,1)) ) {
- Node ld1 = new LoadNode(_name,_declaredType,memphi.in(1),ptr()).peephole();
- Node ld2 = new LoadNode(_name,_declaredType,memphi.in(2),ptr()).peephole();
+ Node ld1 = new LoadNode(_name,_alias,_declaredType,memphi.in(1),ptr()).peephole();
+ Node ld2 = new LoadNode(_name,_alias,_declaredType,memphi.in(2),ptr()).peephole();
return new PhiNode(_name,_type,memphi.region(),ld1,ld2);
}
}
@@ -85,6 +95,6 @@ public class LoadNode extends MemOpNode {
// Profitable if we find a matching Store on this Phi arm.
private boolean profit(PhiNode phi, int idx) {
Node px = phi.in(idx);
- return px!=null && px.addDep(this) instanceof StoreNode st1 && ptr()==st1.ptr() && _name.equals(st1._name);
+ return px!=null && px.addDep(this) instanceof StoreNode st1 && ptr()==st1.ptr() && _alias==st1._alias;
}
}
diff --git a/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
new file mode 100644
index 00000000..f3d5b01e
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
@@ -0,0 +1,61 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.IterPeeps;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeMem;
+
+/** All memory: a default slice and explicit overrides for individual aliases. */
+public class MemMergeNode extends Node {
+ public MemMergeNode(Node bulk) { super(null,bulk); _type = TypeMem.BOT; }
+ public MemMergeNode(Node bulk, int alias, Node precise) {
+ this(bulk);
+ alias(alias,precise);
+ }
+
+ public Node alias(int alias) {
+ Node n = alias < nIns() ? in(alias) : null;
+ assert n!=null || !(in(1) instanceof BulkMemPhiNode bulk) || !bulk.isSplit(alias);
+ return n==null ? in(1) : n;
+ }
+
+ public void alias(int alias, Node mem) {
+ assert alias > 1;
+ while( nIns()<=alias ) addDef(null);
+ setDef(alias,mem);
+ }
+
+ @Override public String label() { return "MemMerge"; }
+ @Override public boolean isMem() { return true; }
+ @Override public Type compute() { return TypeMem.BOT; }
+ @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ p.p("MEM[");
+ for( int i=1; i<nIns(); i++ )
+ if( in(i)!=null ) {
+ p.p(i==1 ? "default:" : i+":");
+ p.n(in(i)).p(" ");
+ }
+ return p.p("]");
+ }
+
+ @Override public Node idealize() {
+ boolean progress=false, allDefault=true;
+ for( int i=2; i<nIns(); i++ ) {
+ if( in(i)==in(1) ) { setDef(i,null); progress=true; }
+ if( in(i)!=null ) allDefault=false;
+ }
+ if( allDefault ) return in(1);
+
+ if( in(1) instanceof MemMergeNode mem ) {
+ for( int i=2; i<mem.nIns(); i++ )
+ if( mem.in(i)!=null && (i>=nIns() || in(i)==null) )
+ alias(i,mem.in(i));
+ IterPeeps.add(mem.in(1));
+ setDef(1,mem.in(1));
+ return this;
+ }
+ assert BulkMemPhiNode.checkMerge(this);
+ return progress ? this : null;
+ }
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
index 138f8fa5..44304bcb 100644
--- a/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
@@ -1,5 +1,6 @@
package com.seaofnodes.simple.node;
+import com.seaofnodes.simple.type.Field;
import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeMemPtr;
@@ -9,10 +10,12 @@ import com.seaofnodes.simple.type.TypeMemPtr;
public abstract class MemOpNode extends Node {
public final String _name;
+ public final int _alias;
- public MemOpNode(String name, Node memSlice, Node memPtr, Node value) {
+ public MemOpNode(String name, int alias, Node memSlice, Node memPtr, Node value) {
super(null, memSlice, memPtr, value);
_name = name;
+ _alias = alias;
}
public Node mem() { return in(1); }
@@ -22,19 +25,17 @@ public abstract class MemOpNode extends Node {
// The caller has already checked the opcode, input shape and control.
boolean canDrop(MemOpNode other, Node dep) {
ptr().addDep(dep);
- if( !_name.equals(other._name) || err()!=null ) return false;
- return !(ptr()._type instanceof TypeMemPtr p &&
- other.ptr()._type instanceof TypeMemPtr q && p._obj!=q._obj);
+ return _alias==other._alias && err()==null;
}
@Override
boolean eq(Node n) {
MemOpNode mem = (MemOpNode)n; // Invariant
- return _name.equals(mem._name);
+ return _alias==mem._alias; // When comparing types error to use "equals"; always use "=="
}
@Override
- int hash() { return _name.hashCode(); }
+ int hash() { return _alias; }
@Override
String err() {
diff --git a/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java
new file mode 100644
index 00000000..c806bede
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java
@@ -0,0 +1,36 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.IterPeeps;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeMem;
+
+/** A Phi for one field alias, parallel to a bulk Phi at the same Region. */
+public class MemPhiNode extends PhiNode {
+ public final int _alias;
+
+ public MemPhiNode(String label, int alias, Node... inputs) {
+ super(label,TypeMem.make(alias),inputs);
+ assert alias > 1;
+ _alias = alias;
+ }
+
+ @Override public String label() { return "MemPhi_"+_alias; }
+
+ @Override public Type compute() {
+ assert BulkMemPhiNode.checkMem(this,_alias);
+ Type t = super.compute();
+ return t==Type.TOP || t==TypeMem.TOP ? TypeMem.TOP : TypeMem.make(_alias);
+ }
+
+ @Override public Node idealize() {
+ for( int i=1; i<nIns(); i++ )
+ if( in(i) instanceof MemMergeNode mem ) {
+ setDef(i,IterPeeps.add(mem.alias(_alias)));
+ return this;
+ }
+ return super.idealize();
+ }
+
+ @Override boolean eq(Node n) { return _alias==((MemPhiNode)n)._alias && super.eq(n); }
+ @Override int hash() { return _alias; }
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/Node.java b/src/main/java/com/seaofnodes/simple/node/Node.java
index d0752af1..1d8e23f8 100644
--- a/src/main/java/com/seaofnodes/simple/node/Node.java
+++ b/src/main/java/com/seaofnodes/simple/node/Node.java
@@ -405,8 +405,14 @@ public abstract class Node extends BaseNode<Node> implements Cloneable {
* or output of this node, that is, it is at least one step away. The node
* being added must benefit from this node being peepholed.
*/
- Node addDep(Node dep) { return addDep(dep,false); }
+ Node addDep( Node dep ) {
+ return addDep(dep,false);
+ }
+
+ // A bulk Phi inspects its users' partitions. Def-to-use propagation does
+ // not revisit that producer when an immediate user's partition changes.
Node addDepForwards(Node dep) { return addDep(dep,true); }
+
private Node addDep(Node dep, boolean forwards) {
// Running peepholes during the big assert cannot have side effects
// like adding dependencies.
@@ -415,7 +421,7 @@ public abstract class Node extends BaseNode<Node> implements Cloneable {
if( obs != null ) obs.dep(this, dep);
if( _deps==null ) _deps = new ArrayList<>();
if( Utils.find(_deps ,dep) != -1 ) return this; // Already on list
- if( !forwards && Utils.find(_inputs,dep) != -1 ) return this; // No need for deps on immediate neighbors
+ if( !forwards && Utils.find(_inputs,dep) != -1 ) return this;
if( !forwards && Utils.find(_outputs,dep)!= -1 ) return this;
_deps.add(dep);
return this;
diff --git a/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/src/main/java/com/seaofnodes/simple/node/PhiNode.java
index 24a72aa4..6995cc2f 100644
--- a/src/main/java/com/seaofnodes/simple/node/PhiNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/PhiNode.java
@@ -17,6 +17,10 @@ public class PhiNode extends Node {
public PhiNode(String label, Type declaredType, Node... inputs) { super(inputs); _label = label; assert declaredType!=null; _declaredType = declaredType; }
+ public static PhiNode make(String label, Type type, Node... inputs) {
+ return type instanceof TypeMem ? new BulkMemPhiNode(label,inputs) : new PhiNode(label,type,inputs);
+ }
+
@Override public String label() { return "Phi_"+_label; }
@Override
@@ -95,7 +99,18 @@ public class PhiNode extends Node {
private boolean same_op() {
Node op = in(1);
if( op.isCFG() || op instanceof ConstantNode || op instanceof PhiNode ||
- op instanceof ProjNode || op instanceof NewNode || op instanceof ScopeNode ) return false;
+ op instanceof ProjNode || op instanceof NewNode || op instanceof ScopeNode || op instanceof MemMergeNode ) return false;
+ // A bulk Phi must split its aliases before factoring precise Stores.
+ if( this instanceof BulkMemPhiNode ) return false;
+ // Bulk splitting currently identifies parallel slices by Region/alias.
+ // Factoring can introduce another slice at a different memory point;
+ // wait until that Region's bulk partitioning has finished.
+ if( op instanceof MemOpNode )
+ for( Node use : region()._outputs )
+ if( use instanceof BulkMemPhiNode ) {
+ use.addDepForwards(this);
+ return false;
+ }
Node busy = null;
for( int i=1; i<nIns(); i++ ) {
Node n = in(i);
@@ -135,7 +150,9 @@ public class PhiNode extends Node {
ins[i] = in(i).in(j);
t = t.meet(ins[i]._type);
}
- PhiNode phi = new PhiNode(_label,t.glb(),ins);
+ PhiNode phi = j==1 && op instanceof MemOpNode mem
+ ? new MemPhiNode(_label,mem._alias,ins)
+ : PhiNode.make(_label,t.glb(),ins);
x = phi.peephole();
}
cp.addDef(x);
diff --git a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
index d567feb5..b408eb2f 100644
--- a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
@@ -130,7 +130,7 @@ public class ScopeNode extends Node {
// Set real Phi in the loop head
// The phi takes its one input (no backedge yet) from a recursive
// lookup, which might have insert a Phi in every loop nest.
- : loop.setDef(idx,new PhiNode(name, lookupDeclaredType(name),loop.ctrl(),loop.update(name,null,nestingLevel),null).peephole());
+ : loop.setDef(idx,PhiNode.make(name, lookupDeclaredType(name),loop.ctrl(),loop.update(name,null,nestingLevel),null).peephole());
setDef(idx,old);
}
return n==null ? old : setDef(idx,n); // Not lazy, so this is the answer
@@ -208,7 +208,7 @@ public class ScopeNode extends Node {
if( in(i) != that.in(i) ) // No need for redundant Phis
// If we are in lazy phi mode we need to a lookup
// by name as it will trigger a phi creation
- setDef(i, new PhiNode(ns[i], this.lookupDeclaredType(ns[i]), r, this.lookup(ns[i]), that.lookup(ns[i])).peephole());
+ setDef(i, PhiNode.make(ns[i], this.lookupDeclaredType(ns[i]), r, this.lookup(ns[i]), that.lookup(ns[i])).peephole());
that.kill(); // Kill merged scope
IterPeeps.add(r);
return r.unkeep().peephole();
diff --git a/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/src/main/java/com/seaofnodes/simple/node/StoreNode.java
index 71eb0842..026d5c50 100644
--- a/src/main/java/com/seaofnodes/simple/node/StoreNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/StoreNode.java
@@ -2,6 +2,8 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.print.ExprPrinter;
+import com.seaofnodes.simple.Utils;
+import com.seaofnodes.simple.IterPeeps;
import com.seaofnodes.simple.type.*;
@@ -13,12 +15,12 @@ public class StoreNode extends MemOpNode {
/**
* @param name The struct field we are assigning to
- * @param memSlice The whole-memory node - this is updated after a Store
+ * @param memSlice The memory alias node - this is updated after a Store
* @param memPtr The ptr to the struct where we will store a value
* @param value Value to be stored
*/
- public StoreNode(String name, Node memSlice, Node memPtr, Node value) {
- super(name, memSlice, memPtr, value);
+ public StoreNode(String name, int alias, Node memSlice, Node memPtr, Node value) {
+ super(name, alias, memSlice, memPtr, value);
}
@Override
@@ -33,19 +35,25 @@ public class StoreNode extends MemOpNode {
}
@Override
- public Type compute() { return TypeMem.BOT; }
+ public Type compute() { return TypeMem.make(_alias); }
@Override
public Node idealize() {
+ if( mem() instanceof MemMergeNode merge ) {
+ setDef(1,IterPeeps.add(merge.alias(_alias)));
+ return this;
+ }
+
// Simple store-after-store on same address. Should pick up the
// required init-store being stomped by a first user store.
if( mem() instanceof StoreNode st &&
- ptr()==st.ptr() && _name.equals(st._name) && // Must check same object
+ ptr()==st.ptr() && _alias==st._alias && // Must check same object
ptr()._type instanceof TypeMemPtr && // No bother if weird dead pointers
// Must have exactly one use of "this" or you get weird
// non-serializable memory effects in the worse case.
st.checkNoUseBeyond(this) ) {
+ assert Utils.eq(_name,st._name); // Equiv class aliasing is perfect
setDef(1,st.mem());
return this;
}
diff --git a/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java
index 1980659e..feb9e90f 100644
--- a/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java
+++ b/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java
@@ -7,7 +7,7 @@ import com.seaofnodes.simple.node.Node;
import java.util.ArrayList;
import java.io.IOException;
-/** Chapter 10's compilation context; capture and event bookkeeping are shared. */
+/** Chapter 11's compilation context; capture and event bookkeeping are shared. */
public class SimpleGraphObserver extends GraphCapture<Node> {
private Parser _parser;
private Node _ret;
diff --git a/src/main/java/com/seaofnodes/simple/type/Type.java b/src/main/java/com/seaofnodes/simple/type/Type.java
index bd9cf458..93e27c76 100644
--- a/src/main/java/com/seaofnodes/simple/type/Type.java
+++ b/src/main/java/com/seaofnodes/simple/type/Type.java
@@ -34,7 +34,7 @@ public class Type {
static final byte TSIMPLE = 4; // End of the Simple Types
static final byte TINT = 5; // All Integers; see TypeInteger
static final byte TTUPLE = 6; // Tuples; finite collections of unrelated Types, kept in parallel
- static final byte TMEM = 7; // Whole-memory token
+ static final byte TMEM = 7; // All memory (alias 0) or A slice of memory - with specific alias
static final byte TMEMPTR = 8; // Memory pointer type
static final byte TSTRUCT = 9; // Structs; tuples with named fields
static final byte TFLD =10; // Fields into struct
diff --git a/src/main/java/com/seaofnodes/simple/type/TypeMem.java b/src/main/java/com/seaofnodes/simple/type/TypeMem.java
index d921bac3..b6fc8824 100644
--- a/src/main/java/com/seaofnodes/simple/type/TypeMem.java
+++ b/src/main/java/com/seaofnodes/simple/type/TypeMem.java
@@ -1,20 +1,66 @@
package com.seaofnodes.simple.type;
+import com.seaofnodes.simple.Utils;
+import java.lang.Long;
+import java.util.BitSet;
import java.util.ArrayList;
-/** A token for all memory; field values are not tracked in its type. */
+
+/**
+ * Represents a slice of memory corresponding to a set of aliases
+ */
public class TypeMem extends Type {
- private final boolean _high;
- private TypeMem(boolean high) { super(TMEM); _high = high; }
- public static final TypeMem TOP = new TypeMem(true ).intern();
- public static final TypeMem BOT = new TypeMem(false).intern();
-
- public static void gather(ArrayList<Type> ts) { ts.add(BOT); }
- @Override TypeMem xmeet(Type t) { return BOT; }
- @Override public Type dual() { return _high ? BOT : TOP; }
- @Override public Type glb() { return BOT; }
- @Override int hash() { return _high ? 1 : 2; }
- @Override boolean eq(Type t) { return _high==((TypeMem)t)._high; }
- @Override public StringBuilder print(StringBuilder sb) { return sb.append(_high ? "MemTop" : "MemBot"); }
+
+ // Which slice of memory?
+ // 0 means TOP, no slice.
+ // -1 means BOT, all memory.
+ // N means slice#N.
+ private final int _alias;
+
+ private TypeMem(int alias) { super(TMEM); _alias = alias; }
+
+ public static TypeMem make(int alias) { return new TypeMem(alias).intern(); }
+ public static final TypeMem TOP = make( 0);
+ public static final TypeMem BOT = make(-1);
+
+
+ public static void gather(ArrayList<Type> ts) { ts.add(make(1)); ts.add(BOT); }
+
+ @Override
+ TypeMem xmeet(Type t) {
+ TypeMem that = (TypeMem) t; // Invariant: TypeMem and unequal
+ return _alias==0 ? that : (that._alias==0 ? this : BOT);
+ }
+
+ @Override
+ public Type dual() {
+ if( _alias== 0 ) return BOT;
+ if( _alias==-1 ) return TOP;
+ return this;
+ }
+
+ @Override
+ public Type glb() { return TypeMem.BOT; }
+
+ @Override
+ int hash() { return 9876543 + _alias; }
+
+ @Override
+ boolean eq(Type t) {
+ TypeMem that = (TypeMem) t; // Invariant
+ return _alias == that._alias;
+ }
+
+ @Override
+ public StringBuilder print(StringBuilder sb) {
+ if( this==BOT ) return sb.append("MemBot");
+ if( this==TOP ) return sb.append("MemTop");
+ return sb.append("MEM#").append( switch(_alias) {
+ case 0 -> "TOP";
+ case -1 -> "BOT";
+ default -> ""+_alias;
+ });
+ }
+
@Override public String str() { return print(new StringBuilder()).toString(); }
}
diff --git a/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/src/test/java/com/seaofnodes/simple/Chapter10Test.java
index f39b1e78..9a5e4677 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter10Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter10Test.java
@@ -169,7 +169,7 @@ while (arg) {
return bar.a;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Loop10,0,(Phi_$mem+2));", stop.toString());
+ assertEquals("return Phi(Loop11,0,(Phi_a+2));", stop.toString());
}
@Test
@@ -221,7 +221,7 @@ if( bar ) bar.a = 1;
return bar;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Region15,null,new Bar);", stop.toString());
+ assertEquals("return Phi(Region16,null,new Bar);", stop.toString());
}
@Test
@@ -237,7 +237,7 @@ else bar.a = 1;
return rez;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Region32,4,3);", stop.toString());
+ assertEquals("return Phi(Region34,4,3);", stop.toString());
}
@Test
@@ -290,9 +290,8 @@ while( i.x < i.len ) {
return sum;
""");
StopNode stop = parser.parse().iterate();
- // The preceding len store keeps the initial x load on the bulk chain.
- assertEquals("return Phi(Loop13,0,(Phi(Loop,.x,(Phi_x+1))+Phi_sum));", stop.toString());
- assertEquals(10L, com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,5));
+ assertEquals("return Phi(Loop16,0,(Phi(Loop,0,(Phi_$2+1))+Phi_sum));", stop.toString());
+ assertEquals(10L,Evaluator.evaluate(stop,5));
}
@@ -312,7 +311,7 @@ while(arg) {
return ret;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Loop10,new s0,Phi(Region31,new s0,Phi_ret));", stop.toString());
+ assertEquals("return Phi(Loop11,new s0,Phi(Region34,new s0,Phi_ret));", stop.toString());
}
@Test
@@ -329,7 +328,7 @@ while(arg) {
return ret;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Loop13,new s0,Phi(Region32,new s0,Phi_ret));", stop.toString());
+ assertEquals("return Phi(Loop15,new s0,Phi(Region35,new s0,Phi_ret));", stop.toString());
}
@@ -346,7 +345,7 @@ while(arg < 10) {
return ret;
""");
StopNode stop = parser.parse().iterate();
- assertEquals("return Phi(Loop10,new s0,Phi(Region30,new s0,Phi_ret));", stop.toString());
+ assertEquals("return Phi(Loop11,new s0,Phi(Region32,new s0,Phi_ret));", stop.toString());
}
@Test
@@ -480,8 +479,7 @@ return 0;
assertEquals(2244L, com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,1));
}
- @Test public void testMemoryAcrossNestedLoops() {
- var stop = new Parser("""
+ static final String NESTED_MEMORY = """
struct S { int x; int y; int z; }
S s = new S;
s.x = 5; s.y = 7; s.z = 11;
@@ -498,7 +496,10 @@ return 0;
arg = arg - 1;
}
return s;
- """).parse().iterate();
+ """;
+
+ @Test public void testMemoryAcrossNestedLoops() {
+ var stop = new Parser(NESTED_MEMORY).parse().iterate();
for (int arg=0; arg<7; arg++) {
var obj = (com.seaofnodes.simple.evaluator.Evaluator.Obj)
com.seaofnodes.simple.evaluator.Evaluator.evaluate(stop,arg);
@@ -524,7 +525,7 @@ return 0;
}
}
- @Test public void testDropLoads() {
+ @Test public void testKeepLoadsAtMerge() {
StopNode stop = new Parser("""
struct S { int x; }
S a = new S; S b = new S;
@@ -535,7 +536,8 @@ return 0;
if (arg>1) v=p.x; else v=q.x;
return v;
""").parse().iterate();
- assertEquals(1,countMemoryNodes(stop,LoadNode.class,new BitSet()));
+ // The one-step safety check stops at the memory aggregate.
+ assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet()));
assertEquals(-1L,Evaluator.evaluate(stop,-1));
assertEquals(1L,Evaluator.evaluate(stop,0));
assertEquals(3L,Evaluator.evaluate(stop,3));
@@ -548,6 +550,7 @@ return 0;
if (arg) s.x=arg+1; else s.x=arg+2;
return s;
""").parse().iterate();
+ // Releasing the old aggregate exposes the overwritten initializing Store.
assertEquals(1,countMemoryNodes(stop,StoreNode.class,new BitSet()));
assertEquals(2L,((Evaluator.Obj)Evaluator.evaluate(stop,0)).fields()[0]);
assertEquals(4L,((Evaluator.Obj)Evaluator.evaluate(stop,3)).fields()[0]);
diff --git a/src/test/java/com/seaofnodes/simple/Chapter11Test.java b/src/test/java/com/seaofnodes/simple/Chapter11Test.java
new file mode 100644
index 00000000..5f92c9c1
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/Chapter11Test.java
@@ -0,0 +1,64 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.evaluator.Evaluator;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class Chapter11Test {
+ @Test public void testIndependentField() {
+ var stop = new Parser("""
+ struct S { int x; int y; }
+ S s = new S;
+ s.x = 42;
+ while (arg > 0) { s.y = s.y + arg; arg = arg - 1; }
+ return s.x;
+ """).parse().iterate();
+ assertEquals("return 42;",stop.toString());
+ assertEquals(42L,Evaluator.evaluate(stop,5));
+ assertNull(stop.walk(n -> n instanceof LoadNode ? n : null));
+ }
+
+ @Test public void testParallelMemoryPhis() {
+ var stop = new Parser("""
+ struct S { int x; int y; int z; }
+ S a = new S; S b = new S;
+ S p = a; if (arg) p = b;
+ while (arg > 0) {
+ a.x = p.x + arg;
+ a.y = p.y + 2;
+ a.z = p.z + 3;
+ arg = arg - 1;
+ }
+ return a;
+ """).parse().iterate();
+ int[] phis = {0};
+ stop.walk(n -> {
+ if (n instanceof MemPhiNode) phis[0]++;
+ if (n instanceof MemMergeNode m && m.in(1) instanceof BulkMemPhiNode b)
+ for(int a=b._aliases.nextSetBit(0); a>=0; a=b._aliases.nextSetBit(a+1))
+ assertTrue(a<m.nIns() && m.in(a)!=null);
+ assertFalse(n instanceof PhiNode && n.isMem() &&
+ !(n instanceof MemPhiNode) && !(n instanceof BulkMemPhiNode));
+ return null;
+ });
+ assertTrue(phis[0]>=3);
+ assertArrayEquals(new Object[]{1L,2L,3L},((Evaluator.Obj)Evaluator.evaluate(stop,4)).fields());
+ }
+
+ @Test public void testLateSliceWorklist() throws ReflectiveOperationException {
+ // Seed 97 exposed a store selecting a bulk predecessor without queuing
+ // it. Keep both the worklist assertion and the final heap check.
+ for (int seed : new int[]{0,97,123,456}) {
+ var parser = new Parser(Chapter10Test.NESTED_MEMORY);
+ var field = IterPeeps.class.getDeclaredField("WORK");
+ field.setAccessible(true);
+ Object work = field.get(null);
+ field = work.getClass().getDeclaredField("_R");
+ field.setAccessible(true);
+ ((java.util.Random)field.get(work)).setSeed(seed);
+ var stop = parser.parse().iterate();
+ assertArrayEquals(new Object[]{9L,25L,17L},((Evaluator.Obj)Evaluator.evaluate(stop,4)).fields());
+ }
+ }
+}
diff --git a/src/test/java/com/seaofnodes/simple/TypeTest.java b/src/test/java/com/seaofnodes/simple/TypeTest.java
index c9eb2747..f9c580cc 100644
--- a/src/test/java/com/seaofnodes/simple/TypeTest.java
+++ b/src/test/java/com/seaofnodes/simple/TypeTest.java
@@ -22,10 +22,23 @@ public class TypeTest {
Assert.assertNotEquals(s1, s1.dual());
Assert.assertEquals(s1, s1.dual().glb());
- assertEquals(TypeStruct.BOT, s1.meet(s2));
- assertEquals(TypeMem.BOT, TypeMem.TOP.meet(TypeMem.BOT));
- assertEquals(TypeMem.TOP, TypeMem.BOT.dual());
- assertEquals(TypeMem.BOT, TypeMem.TOP.dual());
+ TypeMem m1 = TypeMem.make(1);
+ TypeMem m2 = TypeMem.make(2);
+ TypeMem m3 = TypeMem.make(3);
+ TypeMem m4 = TypeMem.make(4);
+
+ Assert.assertNotEquals(m1, m2);
+ Assert.assertNotEquals(m2, m3);
+ Assert.assertNotEquals(m3, m4);
+
+ Assert.assertEquals(TypeStruct.BOT, s1.meet(s2));
+ Assert.assertEquals(TypeMem .BOT, m1.meet(m2));
+ Assert.assertEquals(TypeMem .BOT, m2.meet(m3));
+ Assert.assertEquals(TypeMem .BOT, m3.meet(m4));
+
+ Assert.assertEquals(TypeMem.BOT, m1.glb());
+ Assert.assertEquals(m1, m1.dual());
+ Assert.assertEquals(TypeMem.TOP, m1.glb().dual());
TypeMemPtr ptr1 = TypeMemPtr.make(s1);
Assert.assertEquals(s1, ptr1._obj);
diff --git a/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java
index bddcc596..ac7c5e09 100644
--- a/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java
+++ b/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java
@@ -138,6 +138,7 @@ public class Evaluator {
case CastNode cast -> val(cast.in(1));
case LoadNode load -> load(load);
case StoreNode store -> store(store);
+ case MemMergeNode merge -> MEMORY;
case NewNode alloc -> alloc(alloc);
case ProjNode proj -> ((Object[])val(proj.ctrl()))[proj._idx];
default -> throw new AssertionError("Unexpected node " + node);
diff --git a/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java b/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java
index 674d12f5..48ac0562 100644
--- a/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java
+++ b/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java
@@ -215,7 +215,7 @@ public class Scheduler {
private void refinePlacement(NodeData data, BasicBlock block) {
assert !isPinnedNode(data);
data.block = data.block == null ? block : dom(data.block, block);
- assert isValid(data);
+ assert isValid(data) : data.node.uniqueName()+" at "+data.block.entry.uniqueName();
}
/**
@@ -250,6 +250,12 @@ public class Scheduler {
// so do that now.
var mem = l.in(1);
for(var out : mem._outputs) {
+ // Aggregates describe partitions; they do not overwrite
+ // memory. Only users of this alias constrain the load.
+ if (out instanceof MemMergeNode || out instanceof ReturnNode) continue;
+ if (out instanceof StoreNode s && s._alias != l._alias) continue;
+ if (out instanceof MemPhiNode p && p._alias != l._alias) continue;
+ if (out instanceof BulkMemPhiNode p && p.isSplit(l._alias)) continue;
if (out instanceof PhiNode p) {
var r = p.in(0);
for (int i = 1; i < p.nIns(); i++) {
@@ -271,7 +277,7 @@ public class Scheduler {
// Store nodes have anti-deps to load nodes.
// So decrease the uses of these loads when the store is placed.
for (var out: s.in(1)._outputs) {
- if (out instanceof LoadNode) od(out).ifPresent(this::decUsers);
+ if (out instanceof LoadNode l && l._alias==s._alias) od(out).ifPresent(this::decUsers);
}
}
}
@@ -467,7 +473,8 @@ public class Scheduler {
var data = mem.pop();
node = data.node;
for(var out:node.in(1)._outputs) {
- if (out instanceof LoadNode) od(out).ifPresent(d->d.users++);
+ if (out instanceof LoadNode l && l._alias==((StoreNode)node)._alias)
+ od(out).ifPresent(d->d.users++);
}
}
}